Excel VBA: Why is API better than SendKeys? - vba

I am currently working on a VBA project in Excel where I need to unlock a VBProject and also lock another VBProject. So far, I have been doing this with SendKeys, but I keep reading that it is not a good method, and that API is better? (For example in this thread: Unprotect VBProject from VB code)
However, I could not find any detailed information as to why during my research.
Could someone please tell me why exactly SendKeys is bad? What are the things that could go wrong? (Please note that my SendKeys sequence is only 1.5 seconds long at most.)
Also, why is API the better approach?
Thanks! :)

WinAPI uses things like window handles (you may have seen hWnd in code before?) to target a specific window. Once you have this you can send and receive messages to that window regardless of it's window state (active/inactive) etc.
You are working directly with the object, which is the way programming should be.
The SendKeys() method just emulates a user hitting keys on a keyboard, irrespective of what window is open and where - so it naturally sends the output to whatever object is active and able to receive it.
Another way to think about it
If you're coding to place a value in a cell on a certain sheet in VBA you can do the following:
Range("A1").Value = "Foo"
This is all well and good, but it assumes that the sheet we want is the active sheet at that moment in time. If it isn't, the wrong cell on the wrong sheet will be populated instead. This is effectively what you are doing with SendKeys()
This on the other hand:
Workbooks("Target Workbook.xlsx").Sheets("Target Sheet").Range("A1").Value = "Foo"
Specifies the exact cell, in the exact sheet, in the exact workbook that we want to target - so if that sheet isn't active at that point in time then no worries! It will still go to the right place (this is kind of what you're doing with API)
A WORD OF CAUTION
Playing with WinAPI in VBA can be risky if you don't know what you're doing - the code for these methods is pre-compiled in an external library which means your VBE error handler isn't going to be of any use. If you make a mistake with API you run the risk of corrupting your workbook (or worse depending on what you're actually doing).
You also need to look at conditional compilation in VBA, because you have to declare functions and parameters differently depending on whether you're using a 32-bit or 64-bit version.

Related

VBA code example on Microsoft's website destroyed the clipboard for the rest of the computer. Why did this happen and how can I prevent it?

This is something I didn't think was even possible, but here goes. I was trying to learn how to use the Windows API in Visual Basic to use system calls, and this tutorial (yes, I had to type out the link manually to ask this question, more on that later) showed me how to use the clipboard to retrieve text that the user copied with Ctrl+C. Out of curiosity, and under the assumption that all user input is bad input, I tried pressing Print Screen and then running the code just to see what would happen. I got some error message (can't remember what) but what's very strange is, now the clipboard no longer works! Any attempt I make to paste after a cut or copy, no matter what program I'm using, either does nothing or returns an error message in the program I'm using it in. Yes, it's my fault for intentionally trying to break the code example, but let's be honest - there's no excuse for the OS to fall apart so easily. If it matters, I'm using a PC running Windows 10.
EDIT: Settings won't let me clear the clipboard, and when I try to view the clipboard history, it shows nothing is there. Unfortunately I wasn't able to screenshot the clipboard history because it closes by itself when I try to open Snipping Tool.
Sounds like you've missed a CloseClipboard(), keeping the clipboard locked since Windows thinks a program is reading to it or writing from it. This will prevent other programs from working with the clipboard, since only one program can access it at a time. If Access is still open, you can try running CloseClipboard in the immediate window, else, I recommend a reboot.
On code like this, always add an error handler that calls CloseClipboard() to prevent leaving the clipboard open if something unexpected happens. Note that when working with WinAPI, you might encounter hard crashes that may not call the error handler, so always triple-check your pointers and expect crashes and reboots.
The code you've found is also not adjusted for 64-bit use, so beware. If you've got it to work by just slapping PtrSafe on the functions, you may end up with invalid pointers which can crash Access, leaving the clipboard open and unusable.
The code you've found, while written by Microsoft, is not of particularly good quality. I recommend first checking if there's text on the clipboard using EnumClipboardFormats, then only requesting text if there actually is text on the clipboard.
Beware that using WinAPI through VBA is tough, it's not beginner stuff, especially regarding the clipboard.
Note that there's no excuse for the OS to fall apart so easily is not the attitude to have when working with WinAPI. You're directly interfacing with the OS without any of the securities that managed languages offer, and manually working with pointers. It can and will break if you do something invalid. There's a reason most people use libraries that abstract the dangerous stuff away, if you don't, all bets are off.
similar problem for me. I did not have a CloseClipboard() but when I looked in another module there was already a EmptyClipboard() so used that before the DupRec() call at each instance and no more problems with clipboard. just FYI

Causes for random break points unknown

This has been a long and unresolved problem with Excel VBA code that runs a repeated loop via the Application.OnTime Now + TimeSerial(x,x,x). Users will find that their code is running fine for a few days and may, or may not, mysteriously stop.
I'm facing such a situation and hoping to resolve it. I've read over 30 forum answers, some written by experienced developers, and came to this understanding. My conclusion and question follow.
The conclusion is that Excel randomly goes into a break mode but no one knows the reason why this occurs, more so, why it randomly occurs.
Shall we then conclude that in fact this randomly occurs? And that Excel VBA isn't as robust as other languages.
Some notes:
I know this can be resolved by pressing Ctrl+Break twice. It doesn't explain why we have to do it in the first place.
I realize that this error is code independent. It'll occur with both simple and involved programs.
After trying multiple ways to simulate this error, and I mean a lot - long and multiple ADODB SQL connections and queries, cell editing while macro is running, using multiple and 1 second Application.OnTime recursive calling, I can't replicate the error. It is truly random.
I'm running only one workbook and one Excel instance.
Some say, for each break point we did in the debugging, that break point remains in memory. And then when we run a macro in the future, a write to that part of memory triggers this random break. This is a plausible explanation and does conclude that this error is random. No way can we inspect memory in VBA.
I need something to go by, even if it isn't to solve this problem, to act as proof for my boss.
I've solved the bug! I've not read this answer to this problem before so it is pivotal that the Excel community receives this.
Excel fails to call its sub routines in the Application OnTime queue whenever this happens. You have started to edit a cell, stayed in edit mode, and then switched away from Excel either by minimizing the window or by clicking onto another window.
All the sub routines in the Application OnTime queue will wait until the cell is finished editing. So once you switch back to Excel, the cell switches edit mode off, and then all the sub routines will run.
I'm actually quite impressed I solved this myself.

Best Practices to handle multiple open workbooks in an Excel VSTO add-in

I'm working on an Excel VSTO add-in in VB.Net. I am trying to address the question of knowing which workbook my add-in should be effecting given that a user may open or close many workbooks before, after, or during use of my main form.
The main functions all center around editing a main template file, which the user presumably the saves with useful names, and might come back to for reference or further editing. The add-in opens a form and performs various functions like adding information from a database to a worksheet, or adding sheets to a workbook (and lots of stats). Much of what I'm doing relies on calling a particular workbook:
CurrentRun = Marshal.GetActiveObject("Excel.Application")
CurrentBook = CurrentRun.Workbooks(CurrentIndex)
Where CurrentIndex is the index from a combobox which is populated/updated with names of the currently open workbooks:
...
ControlCurrentWBComboBox.Items.Clear()
CurrentRun = Marshal.GetActiveObject("Excel.Application")
For n = 1 To CurrentRun.Workbooks.Count()
ControlCurrentWBComboBox.Items.Add(CurrentRun.Workbooks(n).Name)
Next
CurrentRun = Nothing
...
This requires the user to select the workbook (by name) that they want to be working with. This seemed like a good idea until I put it in front of 8 different users today. Most eventually got used to having select the workbook, but not without a lot of grumbling and throwing errors (which were well handled). 2 users never got it, and were continually accidentally editing the wrong workbook, and throwing errors an hour into use.
Is there a better way to handle this? I'm thinking of trying to make two versions: one with the above and one for people who need fewer options. Is there a way to force a sort of 1-to-1 relationship you can get by putting VBA code in the workbook itself?
Am I way of base by even trying to deal with this or referencing anything other than ActiveWorkbook?
[Note: I'm a little scared by the blue warning that's telling me this is a subjective question. I'm not meaning for it to be; perhaps I need help with the wording?]
If your Add-In does not "just work" with any open workbook, then you probably should not be using an Application Add-In; rather, you should be using a Document Add-In.
In this scenario, if the user has 3 workbooks open (that each have the Add-In), they will each operate in their own domain -- as If the others don't exist.
If this is an option in your environment, I would strongly recommend it as a Best Practice.

How to access the variable to which OS copies

An interesting feature, I have seen in jDownloader software is any links I copy in the browser window (i.e., Ctrl+c), the copied content links automatically appears ( i.e., with out me actually pasting it) in their UI and starts downloading the content from the links, if they are valid.
I would like to program the same but am puzzled as how to access the variable to which OS copies. Please share your ideas.
Thanks.
Adam Robinson's answer is on the right track, but is not entirely correct. I'm going to try and provide the "long" version (in contrast to his "short" version), and explain along the way where/why I think the solution that he proposes falls short of achieving your ultimate goal.
As the documentation he links to explains, there are three different ways of monitoring changes to the Windows clipboard, each with their own caveats:
Creating a clipboard viewer window that hooks into the clipboard viewer chain and receives notification messages when the contents of the clipboard have been changed by the user. (Available on all versions of Windows, but generally more difficult to code and thus discouraged for newer applications that don't have a specific need for its features.)
Querying the clipboard sequence number, which is a 32-bit value that changes each time the clipboard's contents are changed. Your program calls the Windows API function GetClipboardSequenceNumber once and caches its value, then each time you want to check if the clipboard's contents have changed, you call that same function again and compare its return value to the value you've cached. There are two important caveats here:
This function is only available in Windows 2000 and newer. This is not likely to be a problem if you're writing .NET apps, as versions of the Framework as early as 3.0 dropped W2K support.
This is not a notification method, and you should not call this function repeatedly in a polling loop. That means that you have to manually call the appropriate function and compare the clipboard sequence number. You cannot use this method if you want to "listen in" and be immediately notified whenever the clipboard's contents change, as you describe in your question. The documentation is very explicit here:
This method is more suitable to programs which cache results based on the current clipboard contents and need to know whether the calculations are still valid before using the results from that cache. Note that this is a not a notification method and should not be used in a polling loop. To be notified when clipboard contents change, use a clipboard format listener or a clipboard viewer.
Creating a clipboard format listener, which registers to be notified whenever the clipboard's contents change. This is the ideal solution in your case, because it avoids the complexities of creating a clipboard viewer window (option 1), but also allows you listen in and be notified each time the clipboard's contents are changed (in contrast to option 2).
The problem is that this is only available under Windows Vista and later. If you still have any need to target Windows XP (as most of us do), this is really not an option for you.
Therefore, from the example you provide in the question, it sounds to me like the only option available to you is option 1, creating a clipboard viewer window. The documentation goes into the gory details of how you'd set this up using the SetClipboardViewer function and listening for the WM_DRAWCLIPBOARD and WM_CHANGECBCHAIN messages. Getting this to work right can be a difficult task to do on your own, but fortunately for us .NET developers, others have already done the hard work for us. ("Others", I say, despite having been one of those others myself.)
This article on CodeProject is a good example. It implements three different types of hooks: a mouse hook, a keyboard hook, and a clipboard hook. The only thing you're interested in is the clipboard hook, but you can just add a reference to the DLL in your project to start using its functionality immediately.
If you are interested in the internals of how this works and want to try coding it up yourself, this article appears to be a fantastic description of the specific steps involved.
Use the My.Computer.Clipboard.GetText() function
Also see the msdn page
Check out this MSDN link regarding the clipboard. In particular, this link's anchor should take you to the section of the document about monitoring the clipboard contents.
The short version is that you can monitor either by polling for the sequence number and checking to see if it has changed, or you can register to listen for changes for specific clipboard contents formats. Note that the latter is only available on Vista and above, so you depending on your target platform you may have to stick with polling.
In order to use these functions, you'll have to declare a P/Invoke to the unmanaged function. Here's the PInvoke.net page on the GetClipboardSequenceNumber function, though the declaration here is C#, not VB.NET. The VB.NET syntax should be (I don't have VS in front of me to check):
<DllImport("user32.dll")>
Public Shared Function GetClipboardSequenceNumber() as UInt32
End Function

How do i create a routine that would constantly run in the background?

I am making a primitive database in Excel and need a routine to run in the background constantly. I will be able to fill in the actual actions it needs to do, but I don't know how to make something run independent of a key press or some sort of Macro. If someone can give me an example of code that runs independently which I can simply fill with contents, that would be much appreciated.
Try this http://www.ozgrid.com/Excel/run-macro-on-time.htm
I haven't worked with VBA for quite some time now, but you would have to create a thread in VBA, maybe by COM-Threading, but I am not too sure if Excel is threadsafe or if you even can use Threads of some kind in Excel. Another alternative would be a Timer. If it is not implemented in VBA and I don't remember it to be, you would have to create the Timer yourself and react to the Windows Message yourself, if you can do that in VBA.
All in all I think this uses case might be to big for an Excel VBA Macro. If I were you I would consider doing this in a different way.