VSTO Windows Hook keydown event called 10 times - c#

so, I've been developing a class to handle Kwyboard input in a VSTO add-in, so far I've been using Windows hooks to do so with relative success.
Having this code:
//.....
private const int WH_KEYBOARD = 2;
private const int WH_MOUSE = 7;
private enum WM : uint {
KEYDOWN = 0x0100,
KEYFIRST = 0x0100,
KEYLAST = 0x0108,
KEYUP = 0x0101,
MOUSELEFTDBLCLICK = 0x0203,
MOUSELEFTBTNDOWN = 0x0201,
MOUSELEFTBTNUP = 0x0202,
MOUSEMIDDBLCLICK = 0x0209,
MOUSEMIDBTNDOWN = 0x0207,
MOUSEMIDBTNUP = 0x0208,
MOUSERIGHTDBLCLK = 0x0206,
MOUSERIGHTBTNDOWN = 0x0204,
MOUSERIGHTBTNUP = 0x0205
}
private hookProcedure proc;
private static IntPtr hookID = IntPtr.Zero;
//Enganches
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
private static extern IntPtr SetWindowsHookEx(int hookId, hookProcedure proc, IntPtr hInstance, uint thread);
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
private static extern bool unHookWindowsHookEx(int hookId);
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
private static extern IntPtr CallNextHookEx(IntPtr hookId, int ncode, IntPtr wparam, IntPtr lparam);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr GetModuleHandle(string name);
[DllImport("kernel32", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int GetCurrentThreadId();
public CPInputListener() {
proc = keyBoardCallback;
hookID = setHook(proc);
}
private IntPtr setHook(hookProcedure procedure){
ProcessModule module = Process.GetCurrentProcess().MainModule;
uint threadId = (uint)GetCurrentThreadId();
return SetWindowsHookEx(WH_KEYBOARD, procedure, IntPtr.Zero, threadId);
}
public void stopListeningAll() {
unHookWindowsHookEx(WH_KEYBOARD);//For now
}
private IntPtr keyBoardCallback(int ncode, IntPtr wParam, IntPtr lParam) {
if (ncode >= 0) {
//LPARAM pretty useless
Keys key = (Keys)wParam;
KeyEventArgs args = new KeyEventArgs(key);
onKeyDown(args);//for now
}
return CallNextHookEx(hookID, ncode, wParam, lParam);
}
//....
I do successfully receive keyboard input, but here is the big mistery; each time a key is pressed, no matter how fast it was, the event (onKeyDown) is called 10 times exactly, no more no less.
If the key is long pressed, the event keep being called but 10 by 10 times instead of calling just once.
So far I've tried
Using wParam to call the required event on Key Up: Doesn't seem to work, in all codes I've seen dealing with Key down and up events, IntPtr wParam is used, but from that variable I can only retrieve the keycode which doesn't help.
Using lParam or nCode: These vars are giving unconsistent values between those 10 calls, ncode tends to retrieve 0's and 3's and lParam some values which seem to be unmanaged memory adresses...
What do I expect
I do expect for onKeyDown to be called just once when the key is pressed or in the other hand being able to call the method by on key up which i do expect to be called just once per key releasing.
How to bypass this
If I can't find a reasonable answer, I was thinking on using a custom made timer to discard all those callings and use only the last one, would you recommend this if everything else fails?
Thanks a lot! Be happy and be kind! :D

First you have to filter for the correct ncode to get only the keystrokes you are supposed to process. (For example, you are not supposed to process HC_NOREMOVE.)
Then you have to check if it was a KeyDown or KeyUp event using a flag in lParam.
If the key was long-pressed, multiple KeyDown events are already combined to one call by Win32, so you don't have to do anything special here. But if you want to get only the last KeyUp event then you have to check another flag in lParam as well.
So, here's the code you need to change:
private IntPtr keyBoardCallback(int ncode, IntPtr wParam, IntPtr lParam)
{
// Feel free to move the const to a private field.
const int HC_ACTION = 0;
if (ncode == HC_ACTION)
{
Keys key = (Keys)wParam;
KeyEventArgs args = new KeyEventArgs(key);
bool isKeyDown = ((ulong)lParam & 0x40000000) == 0;
if (isKeyDown)
onKeyDown(args);
else
{
bool isLastKeyUp = ((ulong)lParam & 0x80000000) == 0x80000000;
if (isLastKeyUp)
onKeyUp(args);
}
}
return CallNextHookEx(hookID, ncode, wParam, lParam);
}
Edit as requested in the comment:
Unfortunately the documentation of these parameters is pretty sparse.
One "hint" not to process anything other then HC_ACTION can be found here, stating:
if (nCode < 0) // do not process message
return ...;
// ...
switch (nCode)
{
case HC_ACTION:
// ... do something ...
break;
default:
break;
}
// ...
return CallNextHookEx(...);
Another supporting statement is made here:
Why does my keyboard hook receive the same key-up and key-down events multiple times?
The content of the lParam is defined as follows:
typedef struct tagKBDLLHOOKSTRUCT {
DWORD vkCode;
DWORD scanCode;
DWORD flags;
DWORD time;
ULONG_PTR dwExtraInfo;
}
(Just as a reminder: DWORD here is 4 bytes in size on x86 as well as on x64 platforms.)
The documentation of the lParam flags can be found here and here.
In this links it's described that
bit 30 (=0x40000000) is the previous key state
(1 if the key was down and 0 if the key was up before the new key state that caused this call)
bit 31 (=0x80000000) is the transition state
(0 on key press and 1 on key release now)
The term "previous key state" is rather confusing but effectively it's just the opposite of the current state (because there's only up or down and no third state).
The transition state is especially relevant when the "keyboard's automatic repeat feature" is activated, i.e. when the key is pressed down long enough.
Another sample (using VC7) can be found here:
if (HIWORD (lParam) & 0xC000)
// Key up without autorepeat
else
// Key down
Where 0xC000 just is 0x4000 || 0x8000 and defines that the key was released and has created a key up event.
All in all pretty confusing but nonetheless true.
Maybe there are other links out there that can describe this situation better, but I guess in times like these where new app development "should be done" in tiny sandboxes (like UWP) and VSTO is on its sure way to die to make way for newer Office add-ins that are written in HTML and JavaScript, nobody cares all that much about low-level hooks anymore.

Related

How to get / set Unity Editor main window title?

I'm creating a tool that shows your current branch in Unity window title.
But I cannot get or set the title. So far I managed to set title property, I used Internal_SetTitle (using Reflection), but the values does not show up in the actual window title.
On the contrary, I can set position property of main window just fine using the same method.
Did anybody manage to alter the title using UnityEditor classes?
(Probably I could work the issue around by creating native plugins for that, but I'd prefer a cross-platform / Mono solution if there is any)
I know this isn't quite what you're after but could complement your solution, or help others in a similar situation, here is Windows only PInvoke approach to get the window handle and title of that window, associated with your current thread.
You could add use of the SetWindowText WinAPI as well to set the title, although you may need to do this whenever there is a configuration change as you've mentioned.
[DllImport("user32.dll")]
private static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
[DllImport("user32.dll", SetLastError = true)]
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
[DllImport("user32.dll", EntryPoint = "GetWindowText")]
private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpWindowText, int nMaxCount);
delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
private static string GetUnityEditorWindowName()
{
var processId = (uint)Process.GetCurrentProcess().Id;
var windowHandle = IntPtr.Zero;
string result = "";
EnumWindows((hWnd, lParam) =>
{
GetWindowThreadProcessId(hWnd, out var windowProcessId);
if (windowProcessId != processId)
return true;
var titleBuilder = new StringBuilder(256);
GetWindowText(hWnd, titleBuilder, titleBuilder.Capacity);
if (titleBuilder.Length <= 0)
return true;
result = titleBuilder.ToString();
windowHandle = hWnd;
return false;
}, IntPtr.Zero);
if (windowHandle == IntPtr.Zero)
{
Debug.LogError("Failed to find Unity Editor Window");
}
return result;
}

TranslateAccelerator succeeds, but WM_COMMAND messages are not showing up

I am writing a .NET wrapper around Win32 hooks which buffers WM_CHAR messages and allows events such as key presses, key releases, and accelerator keystrokes to be subscribed to. Everything is in working order except apparently my call to TranslateAccelerator. It returns true when I expect it to (when it finds an accelerator in the given table which I constructed earlier) but the WM_COMMAND messages don't seem to be showing up ever. Here is some relevant code.
[StructLayout(LayoutKind.Sequential)]
struct MSG {
IntPtr hWnd;
WindowsMessages message;
IntPtr wParam;
IntPtr lParam;
UInt32 time;
POINT pt;
}
delegate IntPtr HOOKPROC(HookCodes nCode, IntPtr wParam, ref MSG lParam);
[DllImport("user32.dll")]
extern IntPtr CallNextHookEx(IntPtr hhk, HookCodes nCode, IntPtr wParam, ref MSG lParam);
IntPtr HookProcedure(HookCodes nCode, IntPtr wParam, ref MSG lParam) {
IntPtr result = IntPtr.Zero;
if(nCode < HookCodes.ACTION) {
result = CallNextHookEx(hHook, nCode, wParam, ref lParam);
} else if(nCode == HookCodes.ACTION && (PeekMessageOptions)wParam == PeekMessageOptions.REMOVE) {
/*
* Under these conditions, each message will only be passed onto the switch below once.
*/
switch(lParam.message) {
case WindowsMessages.KEYDOWN:
//fire keydown events and call TranslateAccelerator/TranslateMessage
break;
case WindowsMessages.KEYUP:
//fire keyup events
break;
case WindowsMessages.COMMAND:
//fire accelerator events Ex: Ctrl+F, ALT+M, SHIFT+L
break;
case WindowsMessages.CHAR:
//place char in buffer
break;
default:
break;
}
}
return result; //Will be zero if action was taken on the message by this procedure.
}
}
In KeyPressed(MSG), messages are translated like so:
[DllImport("user32.dll")]
extern bool TranslateAccelerator(IntPtr hWnd, IntPtr hAccTable, ref MSG lpMsg);
[DllImport("user32.dll")]
extern bool TranslateMessage(ref MSG lpMsg);
if(!TranslateAccelerator(hWnd, hAccel, ref msg)){
TranslateMessage(ref msg);
}
The hook and accelerator table are created like this:
[StructLayout(LayoutKind.Sequential)]
struct ACCEL {
byte fVirt;
ushort key;
ushort cmd;
}
[DllImport("user32.dll")]
extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr lpdwProcessId);
[DllImport("user32.dll")]
extern IntPtr SetWindowsHookEx(WindowsHooks hook, HookProcedure lpfn, IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll")]
extern IntPtr CreateAcceleratorTable(ACCEL[] lpaccl, int cEntries);
HOOKPROC proc = HookProcedure;
uint pid = GetWindowThreadProcessId(/*IntPtr*/hWnd, IntPtr.Zero);
IntPtr hHook = SetWindowsHookEx(WindowsHooks.GETMESSAGE, proc, IntPtr.Zero, pid);
IntPtr hAccel = CreateAcceleratorTable(/*ACCEL[]*/accelerators, accelerators.Length);
Everything works fine (such as attaching events to key-downs and buffering WM_CHAR messages) except I can't find WM_COMMAND or WM_SYSCOMMAND messages anywhere in the queue. They just don't seem to be visible to my hook procedure, even though TranslateAccelerator returns true when I expect it to. Note that I am really pretty clueless when it comes to Win32 so I'm probably missing something fairly obvious to the trained eye. But I am at my wits end. I've tried attaching the hook to an XNA window and a Windows Form without success.
You won't catch those WM_COMMAND messages with a GETMESSAGE hook.
TranslateAccelerator "sends the WM_COMMAND or WM_SYSCOMMAND message directly to the specified window procedure" (see the documentation) and hence bypasses the message queue.
You'll need a CALLWNDPROC hook instead.

global keyboard hook slowing down computer

I am using the code from this article :
private const int WH_KEYBOARD_LL = 13;
private const int WM_KEYDOWN = 0x0100;
private static LowLevelKeyboardProc _proc = HookCallback;
private static IntPtr _hookID = IntPtr.Zero;
public static void Main()
{
_hookID = SetHook(_proc);
//Application.Run();
//UnhookWindowsHookEx(_hookID);
}
private static IntPtr SetHook(LowLevelKeyboardProc proc)
{
using (Process curProcess = Process.GetCurrentProcess())
using (ProcessModule curModule = curProcess.MainModule)
{
return SetWindowsHookEx(WH_KEYBOARD_LL, proc,
GetModuleHandle(curModule.ModuleName), 0);
}
}
private delegate IntPtr LowLevelKeyboardProc(
int nCode, IntPtr wParam, IntPtr lParam);
private static IntPtr HookCallback(
int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
{
int vkCode = Marshal.ReadInt32(lParam);
***custom code***
}
return CallNextHookEx(_hookID, nCode, wParam, lParam);
}
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr SetWindowsHookEx(int idHook,
LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode,
IntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr GetModuleHandle(string lpModuleName);
with a little modification but sometimes when i press a couple of keystrokes too fast then the computer "slows" down a bit and gets a little "laggy".
So when it hits asyncronosly the event HookCallback it sometimes get a little laggy, but i guess its the code in this method "HookCallback" that makes it laggy or is it the hooking it self? I had an idea about creating a new thread everytime i enter "HookCallback" , that maybe will help but do i want to start a new thread everytime i press a key? I am getting an asyncronosly call already so i dont know if i should start another thread besides that.
So my question is simple, where and why does it slow down the computer, is it the hooking it self or is it the custom code ? Or should i put the custom code in a different thread?
And i have another question, sometimes after a few keypresses "HookCallback" doesnt get called, as if i have "unhooked" the event, and it wont capture any keystrokes at all. How can i make sure that it never unhooks the event unless i do it in manually?
Thank you.
I've spent a fair amount of time doing keyboard hooks, so the following is just my own experience. Hooks need to be efficient. I wrote a small POS app a few years ago that had to capture keystrokes. It was written in C#, and slowed the input to the point of becoming noticeable. After a lot of headaches I finally realized that the big lag was coming from the translation from native code to managed code. I re-wrote small pieces of the hook code in C++ (native) and saw improvements that I would deem "good enough".
Just remember that speed is fully determined by how long your code takes to process it. Try your very best to make it efficient.
There is another factor about "unhook" problem. If your low level global hook codes can not finish in certain amount of time, the OS will remove your hook automatically without notifying you. So make your codes run as fast as possible
Cheers
If you want to capture keystrokes within your application only, things can be faster then; use WH_KEYBOARD instead of WH_KEYBOARD_LL, this will make much more noticeable performance increase.
private const int WH_KEYBOARD = 2;
I had the same symptoms, but it turned out my issue was due to Thread.Sleep();
I had recently changed my application from a Console to Windows Application. My code had been:
static void Main( string[] args )
{
hook_keys();
AppDomain.CurrentDomain.ProcessExit += CurrentDomainOnProcessExit;
while (true) { System.Threading.Thread.Sleep( new TimeSpan(0,10,0) ); }
}
private static void CurrentDomainOnProcessExit(object sender, EventArgs eventArgs )
{
unhook_keys();
}
This made windows slow down incredibly, even though all my hook does is increment a global variable whenever the keyboard is used. When I changed it to the following, my lag issue was resolved:
static void Main( string[] args )
{
hook_keys();
var application = new Application();
application.Run();
unhook_keys();
}

How can I disable mouse click event system wide using C#?

I have a laptop with a very sensitive touch pad, and wanted to code a small program that could block the mouse input when I was typing a paper or something.
I didn't think it would be hard to do, considering everything I've seen on low-level hooks, but I was wrong (astounding, right?).
I looked at a few examples, but the examples I've seen either block both keyboard and mouse, or just hide the mouse.
Any help with this would be great.
As you mentioned, you can do this using a low-level mouse hook (WH_MOUSE_LL), albeit somewhat incorrectly. What happens when you set a hook is that you'll receive notifications on each mouse input event (WM_MOUSEMOVE, WM_LBUTTONDOWN, WM_RBUTTONDOWN, WM_MBUTTONDOWN, WM_XBUTTONDOWN, WM_NCXBUTTONDOWN, the equivalent up events for each of those, WM_MOUSEWHEEL, and WM_MOUSEHWHEEL). Once you've finished processing each event, you're supposed to call the CallNextHookEx function, which passes the event information on to the next application in the hook chain. However, if you want to prevent any other program from getting mouse input information, you can just skip calling that function at the end of your hook procedure. The "Remarks" section of the above-linked documentation explains it thusly:
Calling CallNextHookEx is optional,
but it is highly recommended;
otherwise, other applications that
have installed hooks will not receive
hook notifications and may behave
incorrectly as a result. You should
call CallNextHookEx unless you
absolutely need to prevent the
notification from being seen by other
applications.
And as it turns out, low-level mouse hooks aren't actually that difficult in C#. I just coded one up myself, actually. But rather than posting that monstrosity of a library, I'll refer you to the simpler code snippet posted on Stephen Toub's blog, which I've reprinted here with syntax highlighting for convenience:
class InterceptMouse
{
private static LowLevelMouseProc _proc = HookCallback;
private static IntPtr _hookID = IntPtr.Zero;
public static void Main()
{
_hookID = SetHook(_proc);
Application.Run();
UnhookWindowsHookEx(_hookID);
}
private static IntPtr SetHook(LowLevelMouseProc proc)
{
using (Process curProcess = Process.GetCurrentProcess())
using (ProcessModule curModule = curProcess.MainModule)
{
return SetWindowsHookEx(WH_MOUSE_LL, proc,
GetModuleHandle(curModule.ModuleName), 0);
}
}
private delegate IntPtr LowLevelMouseProc(int nCode, IntPtr wParam, IntPtr lParam);
private static IntPtr HookCallback(
int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0 &&
MouseMessages.WM_LBUTTONDOWN == (MouseMessages)wParam)
{
MSLLHOOKSTRUCT hookStruct = (MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT));
Console.WriteLine(hookStruct.pt.x + ", " + hookStruct.pt.y);
}
return CallNextHookEx(_hookID, nCode, wParam, lParam);
}
private const int WH_MOUSE_LL = 14;
private enum MouseMessages
{
WM_LBUTTONDOWN = 0x0201,
WM_LBUTTONUP = 0x0202,
WM_MOUSEMOVE = 0x0200,
WM_MOUSEWHEEL = 0x020A,
WM_RBUTTONDOWN = 0x0204,
WM_RBUTTONUP = 0x0205
}
[StructLayout(LayoutKind.Sequential)]
private struct POINT
{
public int x;
public int y;
}
[StructLayout(LayoutKind.Sequential)]
private struct MSLLHOOKSTRUCT
{
public POINT pt;
public uint mouseData;
public uint flags;
public uint time;
public IntPtr dwExtraInfo;
}
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr SetWindowsHookEx(int idHook,
LowLevelMouseProc lpfn, IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode,
IntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr GetModuleHandle(string lpModuleName);
}
As I explained above, you'll want to modify his HookCallback method not to call CallNextHookEx once you've finished processing the mouse event, but return something like new IntPtr(1) instead.
EDIT: And yeah, as others mentioned, there are probably other easier, cleaner solutions to this problem. Your trackpad drivers are a great place to look for an option like "Ignore accidental mouse input while typing". If you don't have this option, you're probably using the standard Windows mouse drivers. Try to download the drivers from your trackpad's manufacturer from the laptop manufacturer's website (for what it's worth, most of the non-Apple trackpads I've seen are Synaptics).
A lot of touchpad drivers have this as an option. I.e. When you are typing, it ignores touchpad input. You could also turn off the tap-click, relying on the actual touchpad buttons to click.
First try the driver's configuration utility, before you try to write your own.

Capture Highlighted Text from any window using C#

How to read the highlighted/Selected Text from any window using c#.
i tried 2 approaches.
Send "^c" whenever user selects some thing. But in this case my clipboard is flooded with lots of unnecessary data. Sometime it copied passwords also.
so i switched my approach to 2nd method, send message method.
see this sample code
[DllImport("user32.dll")]
static extern int GetFocus();
[DllImport("user32.dll")]
static extern bool AttachThreadInput(uint idAttach, uint idAttachTo, bool fAttach);
[DllImport("kernel32.dll")]
static extern uint GetCurrentThreadId();
[DllImport("user32.dll")]
static extern uint GetWindowThreadProcessId(int hWnd, int ProcessId);
[DllImport("user32.dll") ]
static extern int GetForegroundWindow();
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)]
static extern int SendMessage(int hWnd, int Msg, int wParam, StringBuilder lParam);
// second overload of SendMessage
[DllImport("user32.dll")]
private static extern int SendMessage(IntPtr hWnd, uint Msg, out int wParam, out int lParam);
const int WM_SETTEXT = 12;
const int WM_GETTEXT = 13;
private string PerformCopy()
{
try
{
//Wait 5 seconds to give us a chance to give focus to some edit window,
//notepad for example
System.Threading.Thread.Sleep(5000);
StringBuilder builder = new StringBuilder(500);
int foregroundWindowHandle = GetForegroundWindow();
uint remoteThreadId = GetWindowThreadProcessId(foregroundWindowHandle, 0);
uint currentThreadId = GetCurrentThreadId();
//AttachTrheadInput is needed so we can get the handle of a focused window in another app
AttachThreadInput(remoteThreadId, currentThreadId, true);
//Get the handle of a focused window
int focused = GetFocus();
//Now detach since we got the focused handle
AttachThreadInput(remoteThreadId, currentThreadId, false);
//Get the text from the active window into the stringbuilder
SendMessage(focused, WM_GETTEXT, builder.Capacity, builder);
return builder.ToString();
}
catch (System.Exception oException)
{
throw oException;
}
}
this code working fine in Notepad. But if i try to capture from another applications like Mozilla firefox, or Visual Studio IDE, it's not returning the text.
Can anybody please help me, where i am doing wrong? First of all, i have chosen the right approach?
That's because both Firefox and Visual Studio don't use the built-in Win32 controls for displaying/editing text.
It is not possible in general to be able to get the value of "any" selected text, because of the fact that programs can re-implement their own version of the Win32 controls any way they see fit, and your program cannot possibly expect to work with all of them.
However, you can use the UI Automation APIs which will allow you to interact with the majority of 3rd-party controls (at least, all the good ones - such as Visual Studio and Firefox - will likely work with the UI Automation APIs since it's a requirement for accessibility)

Categories