How can I get the name of a Visual Basic control given its HWND? - vb6

I'm working on a little macro record/replay tool which can automate a few very old Visual Basic 6 GUIs we have. To do so, I'm identifying the controls by their name (the value of the name property of a control, that is).
One part of this tool needs to determine the name of a control given its HWND. For newer Visual Basic applications which were done using VB.NET, I can use the WM_GETCONTROLNAME window message. This works nicely.
However, this message is not understood by older windows. Is there any way to do this for controls of Visual Basic 6 applications? A solution which does not require being in the process of the GUI would be preferrable, but if I had a solution which only works inside the GUI process then that would be acceptable as well (since I can do the injection myself).
UPDATE: One thing I just tried, this moderate success: I used the AccessibleObjectFromWindow to check for implementations of the IAccessible interface of the object which shows the given HWND. In case I get an implementation (it seems that many [all?] Visual Basic controls implement this interface), I use the accName property to read out the "accessible name". Sometimes this does yield a useful string, but usually it doesn't.

I believe the only way would be getting inside the process and obtaining a pointer to the Form object, yet I have no idea how to do it from outside.
Is it possible you add support for the WM_GETCONTROLNAME to those older applications?
Or maybe, you could identify the controls by some other, natively-available properties?
Other that that, as Raymond is saying, there isn't much you can do.

Can you modify the vb6 apps? if so in each form load event you could iterate me.controls and use the SetProp(ctrl.hwnd, "MYNAME:" & ctrl.name, 0) api to add the name to the window's own property list, then in your other app you can EnumProps(ctrl_HWND) looking for the one that begins with MYNAME: and parse out the value.

Related

Allow selection in Explorer-style list view to start in the first column

In Windows 7's Windows Explorer list view (what allegedly is not list view at all) in the Details view, you can start selection marquee in the first (Name) column. You just need to start outside the actual name.
The same is true for default-style list view control in Details view.
But if you set the list view control to the Explorer style (using the SetWindowTheme), what should mimic the Windows Explorer, this does not work anymore. You can start selection in the second and later columns only.
SetWindowTheme(listView1.Handle, "explorer", null);
Is there any way to make list view mimic the Explorer selection style?
I suppose there's no settings to enable such behavior and this would have to be coded. Like handling the mouse down and triggering selection. But I have no idea how to do that.
Thanks.
Ntb, I'm using C++Builder, but this should be purely Win32 issue. I've tested this with WinForms too (hence the C# sample above).
Is there any way to make list view it mimic the Explorer selection style?
No, SysListView32 in explorer theme does not behave that way. The control used by the modern Explorer is actually DirectUIHwnd. And you are not able to use one of them.
The only way to get the behaviour of DirectUIHwnd is to code it yourself. I expect that's possible to do but I'd also expect it to be very difficult to achieve.
Actually there is a way to mimic Explorer selection behavior. It requires a lot of additional declarative work, but it is possible.
You need to get undocumented IListView interface via undocumented LVM_QUERYINTERFACE message (note that interface declaration and GUIDs are different for Windows Vista and Windows 7+). Details about constants and declarations can be found here:
IListView at Geoff Chappell - Software Analyst or
Undocumented List View Features at Code Project.
After acquiring the interface all you need is simply a call to SetSelectionFlags(1, 1) method. Voila you are done.

IsWindow(activeX.GetSafeHwnd()) always false after upgrade to VS2010

I have an MFC application that uses an ancient (circa 1999) third-party ActiveX control.
Since upgrading the project from VS2008 to VS2010, I'm having problems...
In the OnSize handler of the parent dialog IsWindow always returns false for the handle returned by control.GetSafeHwnd(), even when GetSafeHwnd() returns a non-NULL value. The rest of the control's parent dialog is displayed fine, but it doesn't seem to respond to any input.
I've seen this article, but GetSafeHwnd() isn't returning NULL in this case (after the first time that it is called, which is before the control is instantiated).
The control does cause the trace message "Control wants to be windowless" to be output when it's loaded. However it also does this when compiled in VS2008, so this may be a red herring. Searching for this message points me to creating a class derived from COleControlSite, and denying the control windowless-ness, but it seems there are no good example of this available, and as I say, it's not clear that this is really the cause of the problem.
I've also found this issue mentioned on MSDN's VS2010 porting page:
"An ActiveX control compiled by using Visual C++ 6.0, when embedded in
a dialog box in a project developed by using Visual C++ 2010, may
cause your program to assert at run time. In this situation, open the
ATL or MFC project associated with the ActiveX control in Visual C++
2010, and recompile it.. The assert will be in the file occcont.cpp,
on this line in source: ASSERT(IsWindow(pTemp->m_hWnd))."
I assume that there's something about VS6-compiled ActiveX controls that causes the window handles to be treated as invalid by the current Win32 implementation of IsWindow. The suggested solution is of course unhelpful as it's a third-party control, and we can't recompile it.
Has anyone managed to get around this?
I've already found solutions for VS2010 projects not running on Windows 2000, and errors linking to ODBC, but don't seem to be able to find anything on this one.
Thanks,
Chris
I didn't find a solution to this in the end - upgraded the controls to a VS2010-compatible version.
For what it's worth: if you don't care whether the control will appear transparent or not, you may force the control to have a window anyway - even though it can operate without a window.
You see, the ActiveX control must first ask the container (the window which will host the control) if it's okay to be activated without a window. This is simply because not all containers support windowless activation.
If this interface (IOleInPlaceSiteWindowless) returns okay then it proceeds with this special windowless activation, if not a window will be created for the control as normal.
Disclaimer:
I don't know if this 'unnecessary' window will make the assertion failure go away. In other words: I don't know if the window handle is passed down 'deep' enough into the AX control.
More about the IOleInPlaceSiteWindowless interface:
https://msdn.microsoft.com/en-us/library/windows/desktop/ms682300(v=vs.85).aspx

Where is where is microsoft.office.tools.word.factory?

I'm in VS2010, in a new Word Add-In project. This is my first attempt at Word development using VSTO. The example I'm trying has this line:
Document vstoDoc = Globals.Factory.GetVstoObject(this.Application.ActiveDocument);
But when I add this line Visual Studio says it can't find "Factory". Indeed, it's not in Intellisense.
I've got references to:
Accessibility
Microsoft.Office.Interop.Word
Microsoft.Office.Tools.Common.v9.0
Microsoft.Office.Tools.v9.0
Microsoft.Office.Tools.Word.v9.0
Microsoft.VisualStudio.Tools.Applications.Runtime.v9.0
Office
and all the usual System references.
Where am I going wrong and why can't I get to "Factory"?
stdole
That example looks a bit weird to me. Never seen that sort of reference before.
Generally, with Vsto, you hook into EVENTS on, say, the main Word App object.
Then, from within the event, you usually are passed a reference to the particular DOC object that the event is occurring for (say, being opened or saved, etc). In that way, there shouldn't be any need for using the "globals" object or the "factory" object, whereever they might be.
What method is that code in? A little more context might help.
I think the recommended way of doing this is:
Globals.ThisAddin.Application.ActiveDocument

Does a Win32 control's control ID change with each run?

In other words, can I count on a control ID as a reliable identifier?
From some reading I've done, it sounds like .NET controls can have control IDs that change with every run, is this so for Win32 apps as well, or are they something that's hardcoded in the source?
The window/control in question is actually an Internet Explorer dialog if that helps.
In general win32 dialog resource IDs do not change when you run the app. However, they are internal implementation details and as such they are subject to change whenever an update (patch, service pack, major release) to the application is made.
In general all of IE's dialogs use hard-coded control IDs. There may be some that are dynamic. If you give the specific control I might be able to give you a better answer.
The answer is "it depends on the circumstances, but in the majority of programs, these will not change across multiple executions." In general, a control ID or resource ID will be the same across every execution of the same program.
In most implementations, resources are stored in the resource section of the PE executable and are assigned a resource ID within that data structure. Usually, the developer specifies the resource in a .rc file.
The exceptional case is via APIs such as CreateDialogIndirect() which allow IDs specified through the API at runtime. Such dynamic creation is uncommon, however. Consistency of resource IDs and control IDs is the expected condition, so even in the case of the CreateXXXIndirect() API, users of the API would be ill-advised to chose a varying ID.
Microsoft has spent years trying to deal with applications which embed assumptions about internal windows implementation details. One of the biggest causes of compatibility problems for IE8 was caused by applications which made assumptions about the window order of IE controls. When the UI was changed in IE8, the controls moved and a number of browser plugins broke hideously.
In general you should never ever make assumptions about controls in an application that you didn't write - your code WILL break in the future (not might break, will break).
As a general rule, no they don't change between runs. Control IDs are usually specified with a dialog template, and that's a static resource compiled into an .exe or .dll. Controls can also be created using a regular call to CreateWindow or CreateWindowEx. In such cases, the ID is usually a constant, but it could be anything in principal (even a random value).
In any case, if you're planning to muck around with a dialog in another application, then you are asking for trouble. Control IDs can and do change between different versions of a program.
It depends on the control. Controls that are dynamically created could have a different ID every time they are created. Controls based on static dialog resources will have static IDs.
Even if a dialog is backed by a dialog resource template, controls can be added dynamically at runtime and those controls could have dynamically generated IDs.
Depending on your intent, it may be acceptable to store and reuse it for future lookups or traces; but in general it isn't safe.
If the window is based upon a dialog template, items declared in the template generally don't change. It is perfectly safe under typical circumstances to use these as identifiers.
Sometimes the window class name or a portion of it can be used as an identifier instead, depending on the window host.
If the dialog is one of the standard prompts from internet explorer, you may want to use text stored in adjacent controls or the dialog caption as additional verification info (if localized versions of IE are not an issue). If the dialog is a window that embeds an instance of MSHTML/IE; none of these options may be viable- but you can use OLE accessibility to get at the document shown and then browse the DOM from there.

Subscribe to Vista Events in .NET (e.g. Window Opened)

I am trying to build my own little toolbox for Vista.
One of the features is a "window placeing tool" which places the windows at saved position. Another tool I could imagine are extensions to firefox or thunderbird...
For these tools to work, I need them to be able to capture "Events" in Vista.
To give you a concrete example:
Explorer Opened New Window
User started Firefox
Mouse moved
For the mouse case, there are some examples for C#.
I also know about the directory watcher, nice little helper.
Want I now need is the "new window opened event"
Any idea how to monitor this, without iterating the current window list every 5 seconds (I already know how to get Windows using the DLLImports, and getting Processes using managed code. But I have no Event when the explorer process opens a new windows)
Thanks for your help,
Chris
What you're talking about doing is not simple by any stretch.
You're going to need to register a hook, and you're going to have to build a callback procedure that gets called within another process's execution context -- this is not going to be .NET code (probably C instead), and will have to be in a DLL. That callback procedure will get called every time a certain class of events happens. It will examine the events it receives and filter out the ones you're interested, then send your application the notifications you want (probably via PostMessage). You'll then tap in to your application's main message loop to intercept those messages, and from there you can fire a .NET Event, or whatever you want.
Writing hook callbacks is tricky stuff because the code gets run within another process, not your own, and the memory management and concurrency issues take a bit of forethought. For that same reason, it's not going to be done in C#. Ideally, though, this callback code will be very small and very fast, since it's going to get called so often.
Also note that while perfectly "legal" in Win32, these system hooks have an immense amount of power and are commonly used by malware to change the way your system works. For that reason, you may run afoul of antivirus software if you attempt to do this sort of thing on a customer's computer.
Also note that the far-reaching effects of system hooks also means that simple programming mistakes can take down your whole system, which you will probably discover for yourself at some point while debugging; so save everything before you hit "run".
Good luck!
EDIT
Did a bit more search to see if there's any way to write the hook proc in C#, and came up with this:
How to set a Windows hook in Visual C# .NET
This is almost what you're looking for, but not quite. Hook procedures can either be global (which means that they run on every application) or thread (only runs within your application). The document states that:
Global hooks are not supported in the .NET Framework
Except for the
WH_KEYBOARD_LL low-level hook and the
WH_MOUSE_LL low-level hook, you cannot
implement global hooks in the
Microsoft .NET Framework. To install a
global hook, a hook must have a native
DLL export to inject itself in another
process that requires a valid,
consistent function to call into. This
behavior requires a DLL export. The
.NET Framework does not support DLL
exports. Managed code has no concept
of a consistent value for a function
pointer because these function
pointers are proxies that are built
dynamically.
Which means, again, to monitor things that go on outside your application's view, you need to set a global hook, which can't be written in .NET.
I have exactly the same issue as this, and I think I have a workable solution. Initially I looked into a similar option to the one mentioned by 'tylerl'. In my case however, instead of using 'SetWindowsHookEx', I attempted to use the similar function 'RegisterShellHookWindows'.
Unfortunately, this only succeeded in providing me with notifications of when a subset of windows are created/destroyed. The only windows which it provided notifications for are those shown on the taskbar.
Since I didn't fancy hacking into other processes, or writing the native code which would be required for SetWindowHookEx, I tried digging into the .NET automation APIs introduced in .NET 4.0, and I think this has the answer to your problem (at least as far as detecting when windows are opened / closed).
Here's a code snippet for using this API to detect windows being opened/closed:
using System.Windows.Automation;
private void StartMonitoringForWindowEvents()
{
Task.Factory.StartNew(() =>
{
AutomationEventHandler windowOpenedHandler = new AutomationEventHandler(OnWindowOpened);
System.Windows.Automation.Automation.AddAutomationEventHandler(
WindowPattern.WindowOpenedEvent, AutomationElement.RootElement,
TreeScope.Descendants, windowOpenedHandler);
});
}
private void OnWindowOpened(object source, AutomationEventArgs eventArgs)
{
try
{
AutomationElement sourceElement = (AutomationElement)source;
string message = string.Format(
"Automation.WindowOpened PID: {0}, Handle: {1}, Name:{2}",
sourceElement.Current.ProcessId,
sourceElement.Current.NativeWindowHandle,
sourceElement.Current.Name);
Debug.WriteLine(message);
// for each created window, register to watch for it being closed
RegisterForClosedWindowEvent(sourceElement);
}
catch
{
}
}
private void RegisterForClosedWindowEvent(AutomationElement element)
{
try
{
string elementName = element.Current.Name;
int processId = element.Current.ProcessId;
int nativeHandle = element.Current.NativeWindowHandle;
AutomationEventHandler windowClosedHandler = new AutomationEventHandler(
(ignoreSource, ignoreArgs) => OnWindowClosed(nativeHandle, processId, elementName));
System.Windows.Automation.Automation.AddAutomationEventHandler(
WindowPattern.WindowClosedEvent, element,
TreeScope.Element, windowClosedHandler);
}
catch
{
}
}
private void OnWindowClosed(int nativeHandle, int processId, string elementName)
{
string message = string.Format(
"Automation.WindowClosed PID: {0}, Handle: {1}, Name:{2}",
processId,
nativeHandle,
elementName);
Debug.WriteLine(message);
}
You will need to add a reference to the assemblies 'UIAutomationClient' and 'UIAutomationClientTypes'.
Here's a link to the MSDN documentation (you'll probably particularly want to take a look at the information on events):
http://msdn.microsoft.com/en-us/library/ms747327.aspx
Important implementation Notes:
1.) Notice that in the sample, I used a task factory to register for reception of the automation events. It's particularly important to avoid using the UI thread when registering for automation events or generally interacting with the automation APIs. Doing so can (and usually quickly does) result in a deadlock. Therefore, I use the task factory to ensure registration is done via the thread pool.
This also means, that the events will be received on the thread pool... So, if you need to perform any UI updates, you will have to marshal these across to the UI thread.
2.) You'll also note, that I capture any needed information on the element which may be closed, at the time of registration (using a closure). This is because, once the element is closed, we will no longer have access to this information - since the element has been destroyed.
Phil
The answer is not C# (or .Net) specific. You'll need to call SetWindowsHookEx( WH_CBT, ... ). This will allows to know when a window is created, destroyed, moved, sized, etc. You'll also need to get the relevant information from the window to identify if its one you need to do something about. Perhaps GetClassInfo, GetWindowLong, and GetWindowText.
The problem with the SetWindowsHookEx is that in order to get events from every window you need to have a separate win32 dll with the function in question exported. Although you might have success with the procedure outlined here.
To expand upon Joel Lucsy's answer, you need to use the Win32 API. However, there's a nice library, the Managed Windows API, that provides an object-oriented wrapper over common APIs. It may have what you need.

Resources