I'm trying to convert a Word VBA procedure to an AppleScript and only having partial luck.
Here's the first version of the VBA procedure:
Public Sub postprocessMerges1()
Dim rng As Range
Selection.HomeKey unit:=wdStory
With Selection.Find
.ClearFormatting
.Forward = True
.Wrap = wdFindStop
.Format = False
Do
.Text = "..."
.Execute
If .Found Then
.Parent.Select
Set rng = Selection.Range
rng.MoveStart unit:=wdParagraph, Count:=-1
rng.MoveEndUntil cset:=Chr(13)
rng.Text = formatAmounts(rng.Text)
End If
Loop While .Found
End With
End Sub
And here's my corresponding AppleScript:
on postprocessMerges()
tell application "Microsoft Word"
home key selection move unit a story extend by moving
set selFind to find object of selection
clear formatting selFind
set foundIt to true
repeat while foundIt
set foundIt to execute find selFind find text "..." wrap find stop with match forward without find format
if foundIt then
set foundRng to text object of selection
set foundRng to move start of range foundRng by a paragraph item count -1
set foundRng to move range end until foundRng characters {return}
set tt to (content of foundRng)
set (content of foundRng) to my formatAmounts(tt)
end if
end repeat
end tell
end postprocessMerges
Okay, so that works just fine. However, I'd like it to be better. The way the script is currently written, it actually jumps from hit to hit, highlighting the found text and performing the replacement generated by the formatAmounts subroutine. Not bad, but when you're working with a 200+ page document, it gets a little tedious to see that happening onscreen.
So in VBA, I can do this:
Public Sub postprocessMerges2()
With ActiveDocument.Content.Find
.ClearFormatting
.Forward = True
.Wrap = wdFindStop
.Format = False
Do
.Text = "..."
.Execute
If .Found Then
With .Parent
.MoveStart unit:=wdParagraph, Count:=-1
.MoveEndUntil cset:=Chr(13)
.Text = formatAmounts(.Text)
.Collapse direction:=wdCollapseEnd
End With
End If
Loop While .Found
End With
End Sub
This will perform the exact same action as the first procedure, but it does so on the document's content range rather than the selection range and so I don't have to watch Word jump around from page to page to page. Much more preferable, but not something I've been able to emulate in AS.
Specifically, I can't seem to get the hit range each time through the loop without selecting it first. The find object in Word's AS dictionary doesn't have a Parent property I can access like I do in VBA.
Is there anything I'm missing? Is what I do in the second VBA proc actually replicable in AS?
This is using Word 2011 and AppleScript 2.3 on OS X 10.9.3.
This should probably be a comment, but...
Is what I do in the second VBA proc actually replicable in AS?
Let me put it this way. I think you will probably end up using the Selection object a lot more in AS than you are probably used to doing in VBA. In this case, in VBA, the Find.Execute should return a new Range, but in AS it just returns true/false. If you look it up in the AS dictionary, it looks as if the execute find should return a "text range/insertion point" but what it actually returns is what is stated in the description at the top of the entry for "execute find", i.e. a boolean. The find object does not have a range. So it is difficult to see how to get the range of the thing you just found. As far as I can tell, using execute find in AS with a find object derived from a range object is only useful in the situation where you can specify the replacment object and do the entire replace in the execute.
It might also be useful to read Matt Neuberg's comments in this conversation
Related
The title says it all...
For example, if I want to have a cell which displays the current time and auto updates minute by minute (well, I think we call that a clock), how do I do it?
Is there a simple function implemented already or should I create a macro and assign it to a specific event?
EDIT: Following the provided answer by #Jim K, I want to be more clear about what I want. The "clock" example above was here to make it simple to understand, but what I really want is in the title: a cell value which changes periodically, be it a string, a number, a date...
First enter =NOW() in a cell, and format the number by going to Format -> Cells.
Next, this Basic macro (from here) recalculates every minute. Go to Tools -> Customize and assign it to the Open Document event.
Sub RecalculatePeriodically
Const secs = 60
On Error Goto ErrorHandler
Do While True
Wait(1000 * secs)
ThisComponent.calculateAll()
Loop
ErrorHandler:
'Document was probably closed
End Sub
However, this crashes when exiting LibreOffice. So instead, use the following threaded Python macro (like here). Assign keep_recalculating_thread to the Open Document event.
import time
from threading import Thread
import uno
def keep_recalculating_thread(action_event=None):
t = Thread(target = keep_recalculating)
t.start()
def keep_recalculating():
oDoc = XSCRIPTCONTEXT.getDocument()
while hasattr(oDoc, 'calculateAll'):
oDoc.calculateAll()
time.sleep(60)
g_exportedScripts = keep_recalculating_thread,
Another idea is at https://ask.libreoffice.org/en/question/5327/how-can-i-run-a-macro-at-regular-time-interval/, although it requires linking to another file which seems cumbersome.
EDIT:
Maybe you are looking for something like this? Test it by starting with a blank spreadsheet and entering 1 in cell A1.
def keep_changing_cell(action_event=None):
t = Thread(target = keep_changing_thread)
t.start()
def keep_changing_thread():
oDoc = XSCRIPTCONTEXT.getDocument()
oSheet = oDoc.CurrentController.ActiveSheet
COLUMN_A = 0
FIRST_ROW = 0
oCell = oSheet.getCellByPosition(COLUMN_A, FIRST_ROW)
while hasattr(oDoc, 'calculateAll'):
oCell.setValue(oCell.getValue() + 1)
time.sleep(2)
tl;dr
Is there a simple function implemented already
No.
From LibreOffice and fairly recent:
(I don't know a 'clock' property applying to cells.)
There are simple ways to obtain the time, for example given suitable formatting, with date:
=NOW()
or Ctrl+;
Or, for example, without date:
=MOD(NOW(),1)
The first and last will update, but only when the sheet is recalculated.
For a cell that ticks away (eg second by second) I believe you will need a script.
I'm trying to run a spell check on a string using word, but I don't want to have to go through each word manually and determine the correct spelling. Is there a way to do this automatically, so that it corrects all the misspelled words on its own? Here is my code so far, this works but I have to go through each word and hit change...
If Address.Length > 0 Then
Dim word_server As New Word.Application
word_server.Visible = False
Dim doc As Word.Document = word_server.Documents.Add()
Dim rng As Word.Range
rng = doc.Range()
rng.Text = Address
doc.Activate()
doc.CheckSpelling()
Address = doc.Range().Text
doc.Close(SaveChanges:=False)
word_server.Quit()
doc = Nothing
rCell.Value = Address
End If
Use the GetSpellingSuggestions function to bypass the GUI. to see if there are any spelling suggestions, and then you can set it to the first suggestion (which could be a bad idea).
How do you want to determine what the correct spelling is? Should "spon" be spoon, span, spin, spun, or son? This code optimistically assumes that the first suggestion (if one exists) is the correct solution.
Declare:
Dim oError As Word.Range
And then replace:
doc.Activate()
doc.CheckSpelling()
with:
For Each oError In rng.SpellingErrors
If oError.GetSpellingSuggestions.Count > 0 Then
oError.Text = oError.GetSpellingSuggestions().Item(1).Name
Else
'Uh oh, no suggestions, do something here.
End If
Next
These websites might help. The example code in the website shows how to call Word to spell check. You should be able to modify it, to use with your code.
http://www.vb-helper.com/howto_net_spellcheck.html
http://www.vbforums.com/showthread.php?307151-SPELL-CHECK-and-WORD
I'm new to programming and I'm trying to copy the content of a form field to another form field in the same Word document like this:
Sub Copyfield()
Dim Temp As String
Temp = ActiveDocument.FormFields("Field1").Result
ActiveDocument.FormFields("Field2").Result = Temp
End Sub
My problem is that my "Field1" is a piece of text of more than 255 characters which seems to be a problem with "Result". I know there is a very similar topic here: Passing MS-Access string >255 characters to MS-Word field but I still don't have the 50 reputation to comment on that thread.
Could anyone please help me understand how to implement the changes in my code?
Well, here's one possibility. Since I don't have your environment it was easier for me to test text in the document rather than another form field with so much content. You'll need to adjust the code accordingly.
The key is to get the Selection "inside" the form field so that it doesn't hit the "protection barrier". Just using FormField.Select puts the focus at the beginning of the field, which VBA is seeing as "protected". Moving one character to the right corrects that and long text can then be assigned to the Selection. But the field needs to have content.
So what my code is doing is "slicing off" the first word of the text to go into the form field. That's short enough to assign to the Result property and lets the Selection move to its right. Then the rest - the long text - can be assigned to the Selection.
You'll probably want to assign the entire FormField.Result to a string variable, then manipulate that string.
Sub WriteLongTextToFormField()
Dim ffld As word.FormField
Dim doc As word.Document
Dim rng As word.Range
Dim s1 As String, s2 As String
Set doc = ActiveDocument
'Get the long text
Set rng = doc.Range(doc.Paragraphs(1).Range.Start, doc.Paragraphs(6).Range.End)
'Split off a bit to go into FormField.Result
s1 = rng.Words(1)
rng.MoveStart wdWord, 1
'The rest of the long text, to be assigned to Selection.Text
s2 = rng.Text
Set ffld = doc.FormFields("Text1")
ffld.result = s1
ffld.Select
Selection.MoveRight wdCharacter, 1
Selection.Text = s2
End Sub
Ok, after 3 days at the border of madness, finally thanks to the help of #Cindy Meister (and some serious personal digging), I made it work. Maybe it's not a big deal for you geniouses out there but believe me for me it was like seeing everything in Matrix code (from the movie guys, the movie).
I want to post it and share because I tried to find it in every corner of our Internet and part of the extraterrestrial one and I couldn't. So hopefully it will be useful for another programming illiterate / dumb person (as myself).
Here is the code:
Sub CopyField()
Dim ffld As Word.FormField
Dim doc As Word.Document
Dim rng As String
Dim s1 As String, s2 As String
Set doc = ActiveDocument
'Get the long text
rng = ActiveDocument.FormFields("Field1").Result
'Split off a bit to go into FormField.Result
s1 = Left(rng, 4) 'Keeps the first 4 characters of the rng string starting from left to right this can be adapted
'The rest of the long text, to be assigned to Selection.Text
s2 = Mid(rng, 5) 'Starting from the 5th character from the left keeps the rest of the string
Set ffld = doc.FormFields("Field2")
ffld.Result = s1
ffld.Select
Selection.MoveRight wdCharacter, 1
ActiveDocument.Unprotect 'Unprotects the document!
Selection.Text = s2
ActiveDocument.Protect Type:=wdAllowOnlyFormFields, NoReset:=True 'Protects the document again!
ActiveDocument.Bookmarks("Field1").Select ' "Sends cursor" back to Field1
End Sub
Big part of the code is originally by #Cindy Meister... I just adapted it to my situation where I had 2 form fields instead of paragraphs. I also had to add some lines to unprotect the document at a certain point in order to make it work (ask the Pros for the reason) and a final instruction to come back to "Field1" (which is some pages up) after the process. Finally just a note for my dumb fellows: I added the macro "on exit" on the "Field1" properties to automatize the process.
Huge thanks Cindy again and I hope you help in my dark programming moments again ! (please do)
:)
I've browsed the various questions already asked with this issue other users have faced and none of the solutions seem to fix the error code coming up.
I have a form which prompts the user for a reference number - they input this into the text field and then press OK
'OK button from form1
Public Sub CommandButton1_Click()
refInput = refTextBox.Value
InputRef.Hide
ExportShipForm.Show
End Sub
Once this has been pressed, the next form appears which I would like to be populated with data based on the reference number input on the first form. I have an update button which will update the "labels" on the form to show the data - this is where I am getting an error.
The first label to update is through a Vlookup:
Below the users clicks the update button the 2nd form:
Public Sub btnUpdate_Click()
Call ICS_Caption
lbl_ICS.Caption = Label_ICS
End Sub
This calls a function below:
Public Sub ICS_Caption()
Dim ws1 As Worksheet
refInput = InputRef.refTextBox.Value
Set ws1 = Worksheets("MACRO")
dataRef = Worksheets("Shipping Data").Range("A:K")
Label_ICS = WorksheetFunction.VLookup(refInput, dataRef, 7, False)
End Sub
The error continues to come up each time - I have ran the vlookup manually in a cell outside of VBA and it works fine.
I have typed the range in the Vlookup whilst also using named ranges but each variation shows the same error.
Eventually, I would want the label on form 2 to update with the result of the Vlookup.
Any ideas?
You need to Dim dataRef as Range and then Set it.
See code Below:
Dim DataRef as Range
Set dataRef = Worksheets("Shipping Data").Range("A:K")
Just like a Workbook or Worksheet you need to Set the Range
Just as Grade 'Eh' Bacon suggest in comments its always best to Dim every reference.
The best way to do so is to put Option Explicit all the way at the top of your code. This forces you to define everything which helps it preventing mistakes/typo's etc.
Update edit:
The problem was you are looking for a Reference number in your Sheet (thus an Integer) but refInput is set as a String this conflicts and .VLookup throws an error because it can't find a match.
I have reworked your code:
Your sub is now a function which returns the .Caption String
Function ICS_Caption(refInput As Integer)
Dim dataRef As Range
Set dataRef = Worksheets("Shipping Data").Range("A:K")
ICS_Caption = WorksheetFunction.VLookup(refInput, dataRef, 7, False)
End Function
The update Button calls your Function and provides the data:
Public Sub btnUpdate_Click()
lbl_ICS.Caption = ICS_Caption(InputRef.refTextBox.Value)
End Sub
By using a Function you can provide the Integer value and get a return value back without the need of having Global Strings or Integers.
Which would have been your next obstacle as you can only transfer Variables between Modules/Userforms by using a Global Variable.
I would even advice to directly use the function in the Initialize Event of your 2nd Userform to load the data before the Userform shows this is more user friendly then needing to provide data and then still needing to push an update button.
Verify that you have no missing libraries in VBA IDE > Tools > References
Try using a worksheet cell as the place to store and retrieve refTextBox.Value, rather than refInput (which I assume is a global variable):
Public Sub CommandButton1_Click()
...
Worksheets("Shipping Data").Range($M$1).Value=refTextBox.Value
End Sub
Public Sub ICS_Caption()
Dim refInput as Long'My assumption
...
refInput=Worksheets("Shipping Data").Range($M$1).Value
...
End Sub
Make sure you have Option Explicit at the top of all of your code windows.
I am trying to create a VB script that searches through a column of dates and returns the address of the cell with todays date.
For some reason I keep getting an "Object required: 'FoundCell'" error.
Could someone have a look at my code and correct me?
I can read out the date using WScript.Echo, but once I use it in the find function it immediately gives me the error.
Set oExcel = CreateObject("Excel.Application")
Set wshShell = CreateObject("Wscript.Shell")
File_Path = "D:\Work\Personal Timemanagement\test.xlsx"
Set oData = oExcel.Workbooks.Open(File_Path)
WHAT_TO_FIND = Date()
WScript.Echo WHAT_TO_FIND
Set FoundCell = oData.Worksheets("tst2").Range("A1:A40").Find(WHAT_TO_FIND)
oExcel.Cells(4,4) = FoundCell.Address
oExcel.ActiveWorkbook.SaveAs "D:\Work\Personal Timemanagement\test2.xlsx"
oExcel.ActiveWorkbook.Close
oExcel.Application.Quit
WScript.Quit
Thanks for the help!
WHAT_TO_FIND1 returns value like #14/10/2014#.So replace the # with nothing using WHAT_TO_FIND1=Replace(WHAT_TO_FIND,"#","Nothing").
Once replaced the above code will work
I tried your script, works perfect for me.
For testing purposes I suggest you add the following line to the script
oExcel.Visible = True
Also make sure all the privies instances of Excel are closed so your script could get write access. (open task manager and end all Excel process - unless you have other excel files open)
Now make sure the Worksheet is spelled correct "tst2" also ensure that the range is correct "A1:A40"
Keep us posted.
Find() returns Nothing when the given value isn't found in the given range. Make sure that the Range A1:A40 on sheet tst2 in the workbook D:\Work\Personal Timemanagement\test.xlsx actually contains a cell with the current date, and that the cell is also formatted as a date. Find() won't return a match if for instance you're looking for a date 7/11/2013 and the range contains a cell (formatted as text) with the string 7/11/2013. Modify your statement like this for finding "text" cells:
Set FoundCell = oData.Worksheets("tst2").Range("A1:A40").Find(CStr(WHAT_TO_FIND))