VBA MS-WORD Trouble with sendkeys - vba

I'm having some issue with this code
Private Sub CortarSobrantes()
'Procedimiento que llama al comando "Comprimir imágenes" con parámetros
With Application.CommandBars.FindControl(ID:=6382)
SendKeys "%T%n%C{ENTER}", False ' Las letras equivalen a los accesos de teclado en la ventana, ~ para ejecucion
.Execute
End With
End Sub
If I hit Run (F5) inside the project, it runs OK, but when I try to call it from a button shortcut it doesn't catch the SendKeys. This also happens if I try to run it step by step (F8)
BTW %T%n%C is for Spanish command combination (all images, not compress, without resolution change and deleting cropped areas)
The reason I'm using SendKeys is that I'm trying to remove cropped areas within a function in order to call it from a button, so I could skip marking the options. As far as I know, there's nothing in the object model that allows this.
Am I missing something with the focus?

Not sure what you're actually trying to achieve, but in general the SendKeys() method is usually avoided in VBA because it's unreliable and buggy at best.
SendKeys() will send virtual keystrokes to whichever window has focus at the time of execution - so timing is everything.
If you know the exact text in the caption of your window you can use the AppActivate() method to force focus just before using SendKeys()
Moreover, SendKeys() is more widely regarded as a "final attempt" or workaround because 90% of the time you can use winapi to get the same result reliably although more advanced knowledge of VBA/programming is required when using Win API

I'm also having trouble with the SendKeys-statement.
I'm trying to send Alt= to a Word document. Thereby changing some mahematical formula in an equation field. For instance: ((x_1 q+x_2 p)/(p+q),(y_1 q+y_2 p)/(p+q))
After some time I found that the SendKeys-statement works as advertised only and ONLY if there's no further statement in my macro.
I know this is a bit awkward, but there it is.
Consider the following:
Public Sub test()
Dim test As String
test = ActiveWindow.Selection.Text
SendKeys "%=", True
' MsgBox test
End Sub
This works fine.
Copy/paste the formula above in a Word-document, open VBA in Word, make a new Module and copy/paste the Sub above into it.
Select the formula.
You must start the Sub form the Macro's menu (Alt+F8).
You'll notice that the formula will change in an equation-field and gets centered in the middle.
Run the macro again and the formula is changed back in standard text-format.
Now activate the MsgBox-statement and you'll notice that the SendKeys-statement doesn't work anymore.
I know this is hardly a workaround for the problem, but maybe someone will find this information useful!

Related

In VB.NET use a textbox as a log for which if statement it is beeing proccesed inside a sub

Hi i have a Sub that has multiple if statements in it.
Each if statement has a large loop that searches for specific files and text inside files.
I tried various ways to use a text box in order to get the information which if is currently proccessing at the time and i see that for some reason the ui is not refreshed until the sub finishes and so i see everytime in the textbox the last proceesed if message.
What do you think is the best way to handle it?
I hope that this has nothing to do with threads because threads are something that i am not familiar with !
I think using Application.DoEvents() is an easy choice. But I don't know if that would be the desired behavior.
If the use of Application.DoEvents() fails, another thread should handle it.
https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.application.doevents?view=netframework-3.5
I use this:
Public Sub logWithCrLf(tx As TextBox, s As String)
tx.AppendText(s & vbCrLf)
tx.Select(tx.TextLength - 1, 0)
tx.ScrollToCaret()
tx.Refresh()
End Sub
I see that it scrolls a bit smoother using tx.AppendText(s) than tx.Text &= s, which scrolls up to 0 then down to caret again.
(I write this as an answer to contribute with the tx.AppendText() recommendation)

Programming VBA in an Outlook form

I created my own Outlook form to use it as standard surface to enter certain orders instead of the normal message form. The creation, editing and sending works perfectly fine and in the next step I want to insert some code via VBA.
My problem is that I can´t access the objects of my form in the VBA editor. E.g. I want to show a message box when a certain checkbox is checked. According code would be:
Sub example()
If CheckBox1.Value = True Then
MsgBox("Checkbox 1 is checked.")
End If
End Sub
When I run the code I get the error that the object could not be found. The same goes for every other object, like textboxes or labels etc.
I guess the solution is pretty simple, like putting Item. or sth. like that in front of each object. But so far I wasn't able to find the solution.
I´m using Outlook 2010.
I know this is a year too late but you'll want to do something like this example below. It's kinda a work around but you can get whatever value was selected.
Sub ComboBox1_Click()
Set objPage = Item.GetInspector.ModifiedFormPages("Message")
Set Control = objPage.Controls("ComboBox1")
MsgBox "The value in the " & Control.Name & _
"control has changed to " & Control.Value & "."
End Sub
You should be able to get the value, just get a handle on the object you want using the Inspector
The following is an excerpt from here
When you use a custom form, Outlook only supports the Click event for
controls. This is a natural choice for buttons but not optimal for
controls like the combo box. You write the code by inserting it into a
form’s VBScript editor. You need to have the Outlook form open in the
Form Designer and click the View Code button found in the Form group
of the Developer tab.
Sub CheckBox1_Click()
msgbox "Hello World"
End Sub
The code page is fairly minimal with no syntax highlighting. I just tried this now and it does work. Dont forget to Publish your form to pick up the new changes.
I know this is almost 6 years late but, in VB and VBA, simply start with the form name. (And if that doesn't work, just keep going up a parent object and you'll get there.) So, your code becomes:
Sub example()
If MYFORMNAME.CheckBox1.Value = True Then
MsgBox("Checkbox 1 is checked.")
End If
End Sub
Of course, after typing "MYFORMNAME." you'll know if it will work because typomatic will kick in when the system recognizes "MYFORMNAME" after you hit the period.

Wait until ActiveWorkbook.RefreshAll finishes - VBA

I have a sub that calls on ActiveWorkbook.RefreshAll to bring new data in from an XML source, and then performs multiple modifications to it. The problem is that not enough time is given for the RefreshAll command to finish, so the following subs and functions end up not executing correctly, which result in repeated rows not being correctly erased.
I have tried using Application.Wait and the Sleep function, but they seem to pause the refresh process too. I simply want the rest of the code to wait until the refresh process finishes before executing the rest of the code.
Any ideas on how to implement this? Right now I was only able to fix it by not calling on RefreshAll, which gives me the idea of implementing a second flow to be executed afterwards, but that's not a good workaround.
Please let me know if any of this wasn't clear. Thanks
EDIT
So I tried a few suggestions from the posts below, and this is what I was able to come up with.
Doing a "record macro" and then UNCHECKING the "Enable background refresh" in the table properties did not result in anything. I did a refresh as well afterwards. This was the result of the recorded macro:
With ActiveWorkbook.Connections("XMLTable")
.Name = "XMLTable"
.Description = ""
End With
ActiveWorkbook.Connections("XMLTable").refresh
The class ActiveWorkbook.Connections does NOT have a BackgroundQuery option so that I can set it to False. Any ideas?
Just to be clear. This is an XML file hosted on a website which Excel goes and imports into a table. I then call that data into a pivot and other things. The goal here is to allow the import process from the website to the table to finish BEFORE executing any other commands.
Thanks
EDIT2:
After a little more research, I have found this page: http://www.mrexcel.com/forum/excel-questions/564959-execute-code-after-data-connection-refresh-finished.html
It appears that an XML type of connection does not have a BackgroundQuery boolean. That option is only available for ODBC and OLEDB connections, which are types xlConnectionTypeODBC and xlConnectionTypeOLEDB, respectively. The XML connection I am using is of type xlConnectionTypeXMLMAP which does not have a BackgroundQuery option.
Does anyone have any idea on where to go from here? The only solution I have in mind right now is to make two seperate macro buttons on the excel sheet, one for refreshing and one for data modification, but I'd rather keep that option to the very last.
I had the same issue, however DoEvents didn't help me as my data connections had background-refresh enabled. Instead, using Wayne G. Dunn's answer as a jumping-off point, I created the following solution, which works just fine for me;
Sub Refresh_All_Data_Connections()
For Each objConnection In ThisWorkbook.Connections
'Get current background-refresh value
bBackground = objConnection.OLEDBConnection.BackgroundQuery
'Temporarily disable background-refresh
objConnection.OLEDBConnection.BackgroundQuery = False
'Refresh this connection
objConnection.Refresh
'Set background-refresh value back to original value
objConnection.OLEDBConnection.BackgroundQuery = bBackground
Next
MsgBox "Finished refreshing all data connections"
End Sub
The MsgBox is for testing only and can be removed once you're happy the code waits.
Also, I prefer ThisWorkbook to ActiveWorkbook as I know it will target the workbook where the code resides, just in case focus changes. Nine times out of ten this won't matter, but I like to err on the side of caution.
EDIT: Just saw your edit about using an xlConnectionTypeXMLMAP connection which does not have a BackgroundQuery option, sorry. I'll leave the above for anyone (like me) looking for a way to refresh OLEDBConnection types.
Though #Wayne G. Dunn has given in code. Here is the place when you don't want to code. And uncheck to disable the background refresh.
DISCLAIMER: The code below reportedly casued some crashes! Use with care.
according to THIS answer in Excel 2010 and above CalculateUntilAsyncQueriesDone halts macros until refresh is done
ThisWorkbook.RefreshAll
Application.CalculateUntilAsyncQueriesDone
You must turn off "background refresh" for all queries. If background refresh is on, Excel works ahead while the refresh occurs and you have problems.
Data > Connections > Properties > (uncheck) enable background refresh
Here is a solution found at http://www.mrexcel.com/forum/excel-questions/510011-fails-activeworkbook-refreshall-backgroundquery-%3Dfalse.html:
Either have all the pivotcaches' backgroundquery properties set to False, or loop through all the workbook's pivotcaches:
Code:
For Each pc In ActiveWorkbook.PivotCaches
pc.BackgroundQuery = False
pc.Refresh
Next
this will leave all pivotcaches backgroundquery properties as false. You could retain each one's settings with:
Code:
For Each pc In ActiveWorkbook.PivotCaches
originalBGStatus = pc.BackgroundQuery
pc.BackgroundQuery = False
pc.Refresh
pc.BackgroundQuery = originalBGStatus
Next
This may not be ideal, but try using "Application.OnTime" to pause execution of the remaining code until enough time has elapsed to assure that all refresh processes have finished.
What if the last table in your refresh list were a faux table consisting of only a flag to indicate that the refresh is complete? This table would be deleted at the beginning of the procedure, then, using "Application.OnTime," a Sub would run every 15 seconds or so checking to see if the faux table had been populated. If populated, cease the "Application.OnTime" checker and proceed with the rest of your procedure.
A little wonky, but it should work.
Try executing:
ActiveSheet.Calculate
I use it in a worksheet in which control buttons change values of a dataset. On each click, Excel runs through this command and the graph updates immediately.
This worked for me:
ActiveWorkbook.refreshall
ActiveWorkbook.Save
When you save the workbook it's necessary to complete the refresh.
Here is a trick that has worked for me when some lines of VBA code have trouble executing because preceding lines haven't completed doing their thing. Put the preceding lines in a Sub. The act of calling the Sub to run those lines may help them finish before subsequent lines are executed. I learned of this trick from https://peltiertech.com/ and it has helped me with timing issues using the Windows clipboard.
If you're not married to using Excel Web Query, you might try opening the URL as a separate Workbook instead. Going that route lets you work on the resulting data once the web request completes, just like if you turn off "Enable background refresh."
The nice thing is though, Excel displays a progress bar during the request, instead of just freezing up / showing a load message in the destination cell.
See my answer on this question: How can I post-process the data from an Excel web query when the query is complete?
The tradeoff of that approach is you have to manage processing the data you get back yourself - Excel won't put it in a given destination for you.
We ended up going this route after we tried something pretty similar to what you seem to have been doing.
I was having this same problem, and tried all the above solutions with no success. I finally solved the problem by deleting the entire query and creating a new one.
The new one had the exact same settings as the one that didn't work (literally the same query definition as I simply copied the old one).
I have no idea why this solved the problem, but it did.
I tried a couple of those suggestions above, the best solution for me was to disable backgroundquery for each connection.
With ActiveWorkbook.Connections("Query - DL_3").OLEDBConnection
.BackgroundQuery = False
End With
For Microsoft Query you can go into Connections --> Properties and untick "Enable background refresh".
This will stop anything happening while the refresh is taking place. I needed to refresh data upon entry and then run a userform on the refreshed data, and this method worked perfectly for me.
I have had a similar requirement. After a lot of testing I found a simple but not very elegant solution (not sure if it will work for you?)...
After my macro refresh's the data that Excel is getting, I added into my macro the line "Calculate" (normally used to recalculate the workbook if you have set calculation to manual).
While I don't need to do do this, it appears by adding this in, Excel waits while the data is refreshed before continuing with the rest of my macro.
For me, "BackgroundQuery:=False" did not work alone
But adding a "DoEvents" resolved problem
.QueryTable.Refresh BackgroundQuery:=False
VBA.Interaction.DoEvents
I know, that maybe it sounds stuppid, but perhaps it can be the best and the easiest solution.
You have to create additional Excel file. It can be even empty.
Or you can use any other existing Excel file from your directories.
'Start'
Workbooks.Open("File_where_you_have_to_do_refresh.xlsx")
Workbooks("File_where_you_have_to_do_refresh.xlsx").RefreshAll
Workbooks.Open("Any_file.xlsx)
'Excell is waiting till Refresh on first file will finish'
Workbooks("Any_file.xlsx).Close False
Workbooks("File_where_you_have_to_do_refresh.xlsx").Save
or use this:
Workbooks("File_where_you_have_to_do_refresh.xlsx").Close True
It's working properly on all my files.
What I've done to solve this problem is save the workbook. This forces it to refresh before closing.
The same approach works for copying many formulas before performing the next operation.

Is there a DocumentAfterPrint event in MS Word?

I am working on a project where I need to return a word document back to a certain state after it is printed. I have found a DocumentBeforePrint event but I cannot find a DocumentAfterPrint event.
Is it poorly documented or is there some other workaround that exists?
Here is one workaround based on subroutine names. I do not believe there is a specific DocumentAfterPrint event like you desire. Here's the code:
Sub FilePrint()
'To intercept File > Print and CTRL-P'
MyPrintSub
End Sub
Sub FilePrintDefault()
'To intercept the Standard toolbar button'
MyPrintSub
End Sub
Sub MyPrintSub()
Dialogs(wdDialogFilePrint).Show
'Your code here, e.g:'
MsgBox "I am done printing."
End Sub
UPDATE: Please note the gotchas in Will Rickards' answer below.
Looking at the application events I don't see it.
I don't see it in the document events either.
Please note on the workaround provided above, that is using the FilePrint and FilePrintDefault methods, you should read this site. These methods replace the built-in function. So you actually need to add code in there or have it generated for you to get word to actually print.
Also background printing can cause your code to execute before it has finished printing. If you really must run something after it has printed you'll need to disable background printing.
I don't believe any of the suggested work-arounds will work in Word 2010. However, I have had success by using the application.onTime() method at the end of the documentBeforePrint event to cause another procedure to be executed a few seconds later.

Profiling VBA code for microsoft word

I have some legacy code that uses VBA to parse a word document and build some XML output;
Needless to say it runs like a dog but I was interested in profiling it to see where it's breaking down and maybe if there are some options to make it faster.
I don't want to try anything until I can start measuring my results so profiling is a must - I've done a little searching around but can't find anything that would do this job easily. There was one tool by brentwood? that requires modifying your code but it didn't work and I ran outa time.
Anyone know anything simple that works?
Update: The code base is about 20 or so files, each with at least 100 methods - manually adding in start/end calls for each method just isn't appropriate - especially removing them all afterwards - I was actually thinking about doing some form of REGEX to solve this issue and another to remove them all after but its just a little too intrusive but may be the only solution. I've found some nice timing code on here earlier so the timing part of it isn't an issue.
Using a class and #if would make that "adding code to each method" a little easier...
Profiler Class Module::
#If PROFILE = 1 Then
Private m_locationName As String
Private Sub Class_Initialize()
m_locationName = "unknown"
End Sub
Public Sub Start(locationName As String)
m_locationName = locationName
MsgBox m_locationName
End Sub
Private Sub Class_Terminate()
MsgBox m_locationName & " end"
End Sub
#Else
Public Sub Start(locationName As String)
'no op
End Sub
#End If
some other code module:
' helper "factory" since VBA classes don't have ctor params (or do they?)
Private Function start_profile(location As String) As Profiler
Set start_profile = New Profiler
start_profile.Start location
End Function
Private Sub test()
Set p = start_profile("test")
MsgBox "do work"
subroutine
End Sub
Private Sub subroutine()
Set p = start_profile("subroutine")
End Sub
In Project Properties set Conditional Compilation Arguments to:
PROFILE = 1
Remove the line for normal, non-profiled versions.
Adding the lines is a pain, I don't know of any way to automatically get the current method name which would make adding the profiling line to each function easy. You could use the VBE object model to inject the code for you - but I wonder is doing this manually would be ultimately faster.
It may be possible to use a template to add a line to each procedure:
http://msdn.microsoft.com/en-us/library/aa191135(office.10).aspx
Error handler templates usually include an ExitHere label of some description.. The first line after the label could be the timer print.
It is also possible to modify code through code: "Example: Add some lines required for DAO" is an Access example, but something similar could be done with Word.
This would, hopefully, narrow down the area to search for problems. The line could then be commented out, or you could revert to back-ups.
Insert a bunch of
Debug.Print "before/after foo", Now
before and after snippets that you think might run for long terms, then just compare them and voila there you are.
My suggestion would be to divide and conquer, by inserting some timing lines in a few key places to try to isolate the problem, and then drill down on that area.
If the problem is more diffused and not obvious, I'd suggest simplifying by progressively disabling whole chunks of code one at a time, as far as is possible without breaking the process. This is the analogy of finding speed bumps in an Excel workbook by progressively hard coding sheets or parts of sheets until the speed problem disappears.
About that "Now" function (above, svinto) ...
I've used the "Timer" function (in Excel VBA), which returns a Single.
It seems to work just fine. Larry