Introduction
The article, Interoperating with Windows Media Player using P/Invoke and C# by Alexander Kent, helped me enormously in understanding how to control a Windows application via the Windows API in C#. However, I had to modify the code in order to manipulate a different application than Windows Media Player. The application I wanted to manipulate:
- didn't have either a consistent name or class, so using
FindWindow()
was not an option
- had dynamic placement of menu items, so using Spy++ to find
wParam
/lParam
was not an option
This article addresses these two deficiencies.
Background
This code simply builds upon Kent's article (the source files are just the ones I modified from the original project), so it is just a matter of cutting-and-pasting the code snippets shown here in the original source. While the following code is not necessary for Windows Media Player (since the two bullet points in the introduction are not relevant for WMP), it is still instructive to demonstrate on WMP.
The two places for cutting-and-pasting:
- Replace
FindWindow()
with GetWmpHandle()
- Replace the hard-coded
wParam
s with member variables you get from GetMenuItemID()
The one place for pasting:
- Add the relevant Windows API methods and constants into your
Win32
class.
Using the code
Let's start with substituting FindWindow()
with GetWmpHandle()
. Again, the reason we're doing this is that the application that you want to manipulate might not have a consistent window name or class name. For example, when you're using Visual Studio, the name of the window is "Microsoft Development Environment" plus the name of the file on which you are working. In my case, both names had a consistent prefix, so I could do the following:
private System.Int32 GetWmpHandle()
{
System.Int32 window_handle =
Win32.GetTopWindow(Win32.GetDesktopWindow());
System.Text.StringBuilder text_stringBuilder =
new System.Text.StringBuilder(0x20);
String wmpText_string = "Windows Media Player";
System.Text.StringBuilder class_stringBuilder =
new System.Text.StringBuilder(0x20);
String wmpClass_string = "WMPlayerApp";
try {
while (true) {
Win32.GetWindowText(window_handle,
text_stringBuilder, 0x20);
if (text_stringBuilder.ToString().StartsWith(
wmpText_string)) {
Win32.RealGetWindowClass(window_handle,
class_stringBuilder, 0x20);
if (class_stringBuilder.ToString().StartsWith(
wmpClass_string)) {
return window_handle;
}
}
if ((window_handle = Win32.GetWindow(window_handle,
Win32.GW_HWNDNEXT)) == 0) {
return 0;
}
}
}
catch (Exception e) { return 0; }
}
The next step is to find the right menu item, in this case, "Play/Pause" and "Stop". I'm assuming you know the name of the pull-down menu (in this case, "Play"). In the extreme case, you might not know in which menu the command is located: I will leave that one up to the reader as an exercise. Here's the GetMenuItemID()
method:
private System.UInt32 GetMenuItemId(int hWnd,
String menuItem_string, String submenuItem_string)
{
System.Int32 hMenu = Win32.GetMenu(hWnd);
int count = Win32.GetMenuItemCount(hMenu);
int menuItemIndex;
System.Text.StringBuilder menuItem =
new System.Text.StringBuilder(0x20);
menuItemIndex = -1;
for (int i = 0; i < count; i++) {
Win32.GetMenuString(hMenu, (uint)i, menuItem,
0x20, Win32.MF_BYPOSITION);
if (menuItem.ToString().StartsWith(menuItem_string)) {
menuItemIndex = i;
break;
}
}
if (menuItemIndex < 0) { return 0; }
hMenu = Win32.GetSubMenu(hMenu, menuItemIndex);
count = Win32.GetMenuItemCount(hMenu);
menuItemIndex = -1;
for (int i = 0; i < count; i++) {
Win32.GetMenuString(hMenu, (uint)i,
menuItem, 0x20, Win32.MF_BYPOSITION);
if (menuItem.ToString().StartsWith(submenuItem_string)) {
menuItemIndex = i;
break;
}
}
if (menuItemIndex < 0) { return 0; }
return Win32.GetMenuItemID(hMenu, menuItemIndex);
}
Now, you need to replace the hard-coded wParam
values with member variables wParam_play
and wParam_stop
:
wParam_play = (int) GetMenuItemId(iHandle, "&Play", "&Play/Pause");
wParam_stop = (int) GetMenuItemId(iHandle, "&Play", "&Stop");
Finally, you need to define more constants and entry points to the DLL in your Win32
class:
...
public const int GW_HWNDNEXT = 2;
public const int GW_HWNDPREV = 3;
public const int GW_CHILD = 5;
public const int MF_BYPOSITION = 0x400;
[DllImport("User32.dll")]
public static extern int GetDesktopWindow();
[DllImport("User32.dll")]
public static extern int GetTopWindow(int hwndParent);
[DllImport("User32.dll")]
public static extern int GetWindow(int hwndSibling,
int wFlag);
[DllImport("User32.dll")]
public static extern int GetWindowText(int hWnd,
System.Text.StringBuilder text, int count);
[DllImport("User32.dll")]
public static extern UInt32 RealGetWindowClass(int hWnd,
System.Text.StringBuilder text, UInt32 count);
[DllImport("User32.dll")]
public static extern int SetParent(int hWndChild,
int hWndNewParent);
[DllImport("User32.dll")]
public static extern int GetMenu(int hWnd);
[DllImport("User32.dll")]
public static extern int GetSubMenu(int hMenu, int nPos);
[DllImport("User32.dll")]
public static extern uint GetMenuItemID(int hMenu, int nPos);
[DllImport("User32.dll")]
public static extern int GetMenuItemCount(int hMenu);
[DllImport("User32.dll")]
public static extern int GetMenuString( int hMenu, uint uIDItem,
System.Text.StringBuilder lpString,
int nMaxCount, uint uFlag );
...
And that's it!!!