I'm trying to click on a menu item inside a program called Media Subtitler and whatever I'm trying to do it's not working.
First, I tried to use the function GetMenu but it returned IntPtr.Zero.
Then, I tried using the ALT key + using the first letter of my menu (F stands for file) but it did nothing.
Then, I tried using a simple MOUSEDOWN and MOUSEUP messages but again, it did nothing (I also tried creating a loop that clicks on everything in that range but there was no click in that area).
What I clearly know is that I'm working on the correct window.
What am I doing wrong?
If someone wants to test it out you can download Media Subtitler for free and it doesn't weight that much.
Also, Here's the code I've been testing:
Process p = Process.Start(#"C:\Program Files\DivXLand\Media Subtitler\MediaSub.exe");
p.WaitForInputIdle(1500);
Thread.Sleep(3000);
SetForegroundWindow(p.MainWindowHandle);
ShowWindow(p.MainWindowHandle, SW_MAXIMIZE);
IntPtr handle = p.MainWindowHandle;
SendMessage(handle, WM_NCHITTEST, 0, MakeLParam(18, 29));
//for (int i = 0; i < 200; i++)
//{
// for (int x = 0; x < 200; x++)
// {
// SendMessage(p.MainWindowHandle, WM_LBUTTONDOWN, 0, MakeLParam(i, x));
// SendMessage(p.MainWindowHandle, WM_LBUTTONUP, 0, MakeLParam(i, x));
// }
//}
//IntPtr menuItems = GetMenu(p.MainWindowHandle);
return;
//SendMessage(p.MainWindowHandle, WM_COMMAND, 6, 0);
SendMessage(p.MainWindowHandle, WM_KEYDOWN, VK_MENU, 0);
SendMessage(p.MainWindowHandle, WM_KEYUP, VK_MENU, 0);
SendMessage(p.MainWindowHandle, WM_KEYDOWN, VK_F, 0);
SendMessage(p.MainWindowHandle, WM_KEYUP, VK_F, 0);
Thanks for any help!
By monitoring the messages sent to the main window of the application, I extracted the menu identifiers for the menu items. You can post WM_COMMAND message to the window, with the ID of the menu items as the wParam:
[DllImport("user32.dll")]
public static extern IntPtr PostMessage(IntPtr hWnd, Message msg, int wParam, int lParam);
PostMessage(handle, WM_COMMAND, 2, 0); // File->New subtitle
PostMessage(handle, WM_COMMAND, 3, 0); // File->New from clipboard
PostMessage(handle, WM_COMMAND, 5, 0); // File->Open text or subtitle
PostMessage(handle, WM_COMMAND, 6, 0); // File->Open video
...
I've tested the code with Media Subtitler, and it works like a charm! The only situation that this will not work, is when on windows Vista or Seven, your target program is running as Administrator and you C# program is not. Be aware of that!
The menu IDs can be easily examined by monitoring the WM_COMMAND message (using Spy++).
You can also use SendMessage instead of PostMessage, but then your program freezes until the user closes the window opened by the menu action.
You can use the same approach to send other command to other windows of the application. For example, clicking the 'Open' button of the 'Open video' window.
You can also do all of this using the System.Windows.Automation namespace: http://msdn.microsoft.com/en-us/library/ms590934.aspx
Using this namespace, you do not need to do any interop with the Win32 API. Here's an example of how to get a window by searching for a string that its name contains:
public static AutomationElement GetWindowByName(string name)
{
AutomationElement root = AutomationElement.RootElement;
foreach (AutomationElement window in root.FindAll(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Window)))
{
if (window.Current.Name.Contains(name) && window.Current.IsKeyboardFocusable)
{
return window;
}
}
return null;
}
After you have the window as an AutomationElement object, you can search it for controls and perform operations on those controls, etc.
Hope this helps!
In Visual Studio there is a tool Spy++ with which you should be able to see handle of objects.
If you see it there, you should be able to get to it using user32.dll function
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string className, string lpszWindow);
to get to them(handle will be different everytime you run your app)
If you get the right handle, you should be able to use SendMessage to send enter, or click.
Tried this only on one app, and I used it only to read and write text, so sorry if it wont work
Related
I am trying to do one of the following
1. open desired program and press a key programmatically
2. find open window of program and press a key programmatically
(either is fine)
I have tried numerous implementations of SendKeys.SendWait(), PostMessage(), and SendMessage() unsuccessfully. Below are my code snippets
//included all these for attempts
[DllImport("User32.dll")]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("User32.dll")]
static extern int SetForegroundWindow(IntPtr hWnd);
[DllImport("User32.Dll", EntryPoint = "PostMessageA")]
static extern bool PostMessage(IntPtr hWnd, uint msg, int wParam, int lParam);
[DllImport("user32.dll")]
static extern byte VkKeyScan(char ch);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
Get handle of window, variables used by sendmessage/postmessage/sendkeys
IntPtr ptrOBS = proc.Handle;//this works properly, proc is instantiated properly
//IntPtr ptrOBS = FindWindow(null, "Open Broadcaster Software v0.472b");
SetForegroundWindow(ptrOBS);
const UInt32 WM_CHAR = 0x0102;
const uint WM_KEYDOWN = 0x100;
const int VK_R = 0x52; // taken from http://msdn.microsoft.com/en-us/library/dd375731(v=vs.85).aspx
const int VK_S = 0x53;
SendMessage attempt:
SendMessage(ptrOBS, WM_KEYDOWN, (IntPtr)VK_R, (IntPtr)1);//tried both WM_CHAR and WM_KEYDOWN
PostMessage attempt:
string message = "rs";
bool sent = PostMessage(ptrOBS, WM_KEYDOWN, VkKeyScan(message[0]), 0);
SendKeys attempt:
SendKeys.SendWait("{r}");
Tried SetFocus on the parent window (application) and child window (button triggered by keypress im trying to send):
static void SetFocus(IntPtr hwndTarget, string childClassName)
{
// hwndTarget is the other app's main window
// ...
IntPtr targetThreadID = GetWindowThreadProcessId(hwndTarget, IntPtr.Zero); //target thread id
IntPtr myThreadID = GetCurrentThread(); // calling thread id, our thread id
try
{
bool lRet = AttachThreadInput(myThreadID, targetThreadID, -1); // attach current thread id to target window
// if it's not already in the foreground...
lRet = BringWindowToTop(hwndTarget);
SetForegroundWindow(hwndTarget);
// if you know the child win class name do something like this (enumerate windows using Win API again)...
IntPtr hwndChild = (IntPtr)1183492;//(IntPtr)EnumAllWindows(hwndTarget, childClassName).FirstOrDefault();
if (hwndChild == IntPtr.Zero)
{
// or use keyboard etc. to focus, i.e. send keys/input...
// SendInput (...);
return;
}
// you can use also the edit control's hwnd or some child window (of target) here
SetFocus(hwndChild); // hwndTarget);
SendKeys.SendWait("{r}");
}
finally
{
SendKeys.SendWait("{r}");
bool lRet = AttachThreadInput(myThreadID, targetThreadID, 0); //detach from foreground window
SendKeys.SendWait("{r}");
}
}
For NSGaga:
string windowName = "Open Broadcaster Software v0.472b";
IntPtr outerPtr = FindWindow(null, windowName);
IntPtr ptrOBS = (IntPtr)527814;//button that im trying to trigger keypress on
SetForegroundWindow(outerPtr);
SetForegroundWindow(ptrOBS);
SetFocus(outerPtr, "OBSWindowClass");//SetFocus(ptrOBS, "Button");
const UInt32 WM_CHAR = 0x0102;
const int VK_R = 0x52; // taken from http://msdn.microsoft.com/en-us/library/dd375731(v=vs.85).aspx
const int VK_S = 0x53;
//SetForegroundWindow(ptrOBS);
System.Threading.Thread.Sleep(3000);
SendKeys.SendWait("{r}");
SendMessage(outerPtr, WM_KEYDOWN, (IntPtr)VK_R, (IntPtr)1);
PostMessage(outerPtr, WM_KEYDOWN, VkKeyScan('r'), 0);
You cannot reliably use SendMessage and PostMessage for synthesizing keyboard input. They are just not designed for this. These messages (WM_CHAR, WM_KEYDOWN, etc.) are notifications raised by lower-level subsystems when keyboard input has been received, processed, and forwarded on to the appropriate recipient. Sending or posting these messages yourself is like prank-calling someone.
SendKeys (like all other input synthesizer methods, including the SendInput function which was explicitly designed for synthesizing keyboard input and in at least some implementation is what SendKeys actually uses under the hood) works only when the window you wish to receive the keyboard input has the focus. In Windows, only focused (active) windows receive input events.
So SendKeys is probably the way to go if you're ever going to get this to work (either that or P/Invoking SendInput and all of its associated structures), but you do need to respect the caveat that the recipient window must have the focus. Otherwise, it's not going to get anything.
It looks like from your sample code that you're trying to use the SetForegroundWindow function to meet this precondition. Unfortunately, you're passing it an invalid value, and not doing any error checking that might alert you to this mistake. Specifically, this code is wrong:
IntPtr ptrOBS = proc.Handle;//this works properly, proc is instantiated properly
SetForegroundWindow(ptrOBS); // WRONG, ptrOBS is not a window handle
Even if I trust you on ptrOBS being initialized correctly, that makes it a valid handle to a process, which is a very different thing than a valid handle to a window. Aside from the obvious nominal differences, processes can have multiple windows and only a single window can have the focus (i.e., be "in the foreground").
You will need to obtain the handle to a particular window before calling SetForegroundWindow, and given that we know a process can have multiple windows, that can be tricky. You need some reliable way of determining which window you want. Lots of people accomplish this by hard-coding the name of the window as a string, which works great until the target app is recompiled and this implementation detail changes. The only bulletproof way that I can think of is to have the user click the target window and your code to retrieve the handle of the window that is currently under the mouse pointer.
And of course all of this assumes that you've observed the restrictions on the use of SetForegroundWindow, enumerated in the "Remarks" section of the linked SDK documentation.
There is lot of trial and error with that, to get it working
Here is a bit of code I posted before, you might wanna give a try (and there is some more info attached)...
Pinvoke SetFocus to a particular control
Try setting focus first (using the mechanism mentioned) - and then using SendKeys or SendInput.
Here is some detailed code for SendInput...
How to send a string to other application including Microsoft Word
I would like to simulate keystrokes within an embedded System.Windows.Controls.WebBrowser. Various techniques for simulating keystrokes are documented already here on StackOverflow, however they do not seem to work for the WebBrowser control.
Knowing that the control wraps another window/hwnd, I would have expected the following to work however it's not:
[DllImport("user32.dll")]
private static extern int SendMessage(IntPtr hWnd, uint msg, int wParam, int lParam);
...
SendMessage(myWebBrowser.Handle, WM_CHAR, key, 0);
I am already using SendMessage to forward simulated keystrokes to other parts of the WPF application, and would prefer a consistent solution; however this is failing for the WebBrowser.
How can I forward simulated keystrokes to WebBrowser?
My solution was to use SendInput() instead of SendMessage().
The import:
[DllImport("user32.dll", SetLastError = true)]
public static extern uint SendInput(uint nInputs, User32.Input[] pInputs, int cbSize);
For the additional types and constants see here: http://pinvoke.net/default.aspx/user32/SendInput.html
For the expected behavior see here: http://msdn.microsoft.com/en-us/library/windows/desktop/ms646310(v=vs.85).aspx.
My virtual keypress method:
private void VirtualKeypress(Key keyCode, bool shift, char keyChar)
{
User32.Input[] inputSequence;
if (keyChar == '\0' && keyCode == Key.None)
{
throw new ArgumentException("Expected a key code or key char, not both.");
}
else if (keyChar != '\0')
{
inputSequence = KeyboardUtils.ConvertCharToInputArray(keyChar);
}
else
{
inputSequence = KeyboardUtils.ConvertKeyToInputArray(keyCode, shift);
}
User32.SendInput(
(uint)inputSequence.Length,
inputSequence,
Marshal.SizeOf(typeof(User32.Input))
);
}
I have two helper methods, ConvertCharToInputArray() and ConvertKeyToInputArray(), which return an array of length 2 or 4 depending if we need to tell windows that the shift key is depressed. For example:
'A' -> [] { shift down, A down, A up, shift up }
while just
'a' -> [] { A down, A up }
.
You were so close! The handle reported by WebBrowser.Handle is the outter most handle, while all of the input is directed to the inner most handle:
var hwnd = _browser.Handle;
hwnd = FindWindowEx(hwnd, IntPtr.Zero, "Shell Embedding", null);
hwnd = FindWindowEx(hwnd, IntPtr.Zero, "Shell DocObject View", null);
hwnd = FindWindowEx(hwnd, IntPtr.Zero, "Internet Explorer_Server", null);
SendMessage(hwnd, WM_CHAR, new IntPtr(0x0D), IntPtr.Zero);
FindWindowEx definition from pinvoke.net:
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
Highlighted is the WebBrowser control:
Well i'm only used to using this in VB6 but Try sending to myWebBrowser.object.Handle or myWebBrowser.object.HWND is what i see in VB6, but you probably have .Handle in your .net version.
try the .object and let me know how it goes!!
I found I could do movement around an HTML form (Chrome Browser) from within a C# program by using sendmessage to the process #.
However, I couldn't insert text into an input field. Tried most everything (from pure C#).
While hacking, I noticed I could pop-up a context editing menu while the cursor was on the input I was trying to set, and one of the items on the menu was paste! WhatDoYouKnow! I could interact with that!
Here are the codes I used, once I had tabbed to the input I wanted to set:
Clipboard.SetText("52118"); // from C#, put the input value onto the clipboard
chrome.SendKey((char)93); // char 93 opens pop-up menu that includes paste
System.Threading.Thread.Sleep(30);
chrome.SendKey((char)0x28); // down to the first menu item
System.Threading.Thread.Sleep(30);
chrome.SendKey((char)0x28); // down to the second menu item (paste)
System.Threading.Thread.Sleep(100);
chrome.SendKey((char)0x0D); // fire the paste
Check here for the code used for the ChromeWrapper (Thanks for that!):
Sending keyboard key to browser in C# using sendkey function
You have have to use PostMessage instead of SendMessage, then it should work.
PS: 9 years late I know
In this below code, hWnd is the "URL Handle" in Spy++:
'WorkerW -> ReBarWindow32 -> Address Band Root -> Edit'
The URL is what I want to open.
I use the same method to open tabs in IE7 and send hWnd appropriately. I see that this works fine for IE7 and not for IE8. In IE8, it only opens 4 tabs and then IE8 stops honoring the SendMessage request; however, I can still press CTRL+T OR ALT+Enter to open new tabs in IE8 (so IE8 is still responsive).
/**
* Open URL in IE (open new tab when newTab is true)
* hWnd is found at runtime
**/
private void LaunchURLinIE(IntPtr hWnd, String url, bool newTab = false)
{
StringBuilder ob = new StringBuilder(url);
// Type text in the URL window
SendMessage(hWnd, WM_SETTEXT, 0, ob);
if (!newTab)
{ // Press Enter
SendMessage(hWnd, WM_KEYDOWN, VK_RETURN, 1);
}
else
{ // Press ALT Enter to open new tab
SendMessage(hWnd, WM_SYSKEYDOWN, VK_RETURN, 1 << 29);
}
}
My environment is: Windows XP Service Pack 3 [32-bit OS] , IE8 version 8.0.6001.18702
So, is it IE8 or something I am missing?
UPDATE - 1
I have updated comments on the code so that its clear what code does. The above code works perfectly fine for IE7 (tested upto 15 tabs) but with IE8 it only opens upto 4 tabs.
Update - 2
I was able to sove this by using PostMessage instead of SendMessage.
private void LaunchURLinIE(IntPtr hWnd, String url, bool newTab = false)
{
StringBuilder ob = new StringBuilder(url);
// Type text in the URL window
SendMessage(hWnd, WM_SETTEXT, 0, ob);
if (!newTab)
{ // Press Enter
PostMessage(hWnd, WM_KEYDOWN, VK_RETURN, 1);
}
else
{ // Press ALT Enter to open new tab
PostMessage(hWnd, WM_SYSKEYDOWN, VK_RETURN, 1
You might want to try using the COM exposed by the ShDocVw object which can be found as a .dll named Interop.ShDocVw.dll this contains an InternetExplorerClass interface which allows you to do most IE automation reliably.
I am a contributor to SWAT http://sourceforge.net/projects/ulti-swat/, if you would like some good examples feel free to use our source for IE automation located in SWAT.Core/InternetExplorer.cs
There are alot of problems with your code actually, window handles are hardcoded (why?) also you are only sending a key down, the application usually expects a keydown/keyup combo or sometimes a keydown/keypress/keyup sequence in order for it to be valid. Your application makes it seem as if you are holding down the keys you are sending. You may want to use the FindWindow windows API call which can help you find the window handle at runtime for the window you want to send the messages to.
Should you not send WM_KEYUP as well?
VK_RETURN? Not sure what it does. Would it be better to send CTRL+T keystrokes instead?
KEYDOWN CTRL
KEYDOWN T
KEYUP T
KEYUP CTRL
You can find key codes with KeyInterop.VirtualKeyFromKey.
I also think you need to use PostMessage:
[return: MarshalAs(UnmanagedType.Bool)]
[DllImport("user32.dll", SetLastError = true)]
private static extern bool PostMessage(IntPtr hWnd, uint Msg, int wParam, int lParam);
private const UInt32 WM_KEYDOWN = 0x0100;
private const UInt32 WM_KEYUP = 0x0101;
public static void IENewTab(IntPtr HWnd)
{
PostMessage(HWnd, WM_KEYDOWN, KeyInterop.VirtualKeyFromKey(System.Windows.Input.Key.LeftCtrl), 0);
PostMessage(HWnd, WM_KEYDOWN, KeyInterop.VirtualKeyFromKey(System.Windows.Input.Key.T), 0);
PostMessage(HWnd, WM_KEYUP, KeyInterop.VirtualKeyFromKey(System.Windows.Input.Key.T), 0);
PostMessage(HWnd, WM_KEYUP, KeyInterop.VirtualKeyFromKey(System.Windows.Input.Key.LeftCtrl), 0);
}
I want to save a TextPad window using C# code; I can find out the handle of the window but not sure how to send CTRL - S to that window. I want to use P/Invoke API to achieve this. Also, that TextPad window will be inactive, because my application will be active at that time.
[DllImport("user32.dll")]
private static extern int SendMessage(IntPtr hwnd, int msg, int wParam, int lParam);
Send Ctrl+Up to a window
I looked at this discussion which is very similar to my problem. I understand logically that I have to do 4 send messages like below
KeyDown CTRL key
KeyDown S key
KeyUp S key
KeyUp CTRL key
I am not sure how to send the right parameters to the SendMessage. To close the window I use
SendMessage(hWnd, 0x0010, 0, 0);
I got this from MSDN library.
Can you please direct me to some link which tells me the hexadecimal for keys in the keyboard and explains what the last two parameters represent?
UPDATE - 1
Using spy++ I find these events generated with I press CTRL-S on Notepad window
1. WM_KEYDOWN nVirtKey:VK_Control,
2. WM_KEYDOWN nVirtKey:'S' .. some other messates ..
3. WM_KEYUP nVirtKey:VK_Control.
4. WM_KEYUP nVirtKey:'S'.
UPDATE - 2
private IntPtr startnotepad() {
ProcessStartInfo psi = new ProcessStartInfo();
psi.FileName = #"notepad.exe";
String fileName = baseDirectory + textbox1.Text;
psi.Arguments = #"""" + fileName + #"""";
psi.WindowStyle = ProcessWindowStyle.Minimized;
Process p = Process.Start(psi);
return p.MainWindowHandle;
}
private void SaveNotepad(IntPtr handle)
{
IntPtr handle = GetWindow(handle, 5); // get the keyboard focus handle
// verified the handle with Spy ++.
SendMessage(handle, 0x0100, 0x11, 0); // keydown ctrl
SendMessage(handle, 0x0100, 0x53, 0); // keydown S
//SendMessage(handle, 0x0101, 0x11, 0); --- I tried keeping "Keyup Ctrl" here as well.
SendMessage(handle, 0x0101, 0x53, 0); // keyup s
SendMessage(handle, 0x0101, 0x11, 0); // keyup ctrl
}
This code does not work (save part of it) even though I am able to see the WM_KEYDOWN - CTRL .. WM_KEYDOWN - 'S' .. KEYUP 'S' and KEYUP CTRL being sent to the right window in spy++. Can anyone comment what is wrong with this code? Or any suggestions if I am doing something really stupid.
UPDATE 3
I should be using PostMessage instead of SendMessage as #Hans suggested in his comments.
[DllImport("user32.dll")]
public static extern IntPtr PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
private void Save(IntPtr mainWindowHandle)
{
IntPtr handle = GetWindow(mainWindowHandle, 5); // getChild window
IntPtr CTRL_KEY = new IntPtr(0x11);
uint KEY_DOWN = 0x0100;
uint KEY_UP = 0x0101;
IntPtr S_KEY = new IntPtr(0x53);
//SetForegroundWindow(p.MainWindowHandle);
PostMessage(handle, KEY_DOWN, CTRL_KEY, IntPtr.Zero);
PostMessage(handle, KEY_DOWN, S_KEY, IntPtr.Zero);
PostMessage(handle, KEY_UP, S_KEY, IntPtr.Zero);
PostMessage(handle, KEY_UP, CTRL_KEY, IntPtr.Zero);
}
The above code instead of CTRL+S sends CTRL and S to Notepad window. So, I can see two "S" being thrown at Notepad. Looking at the real message generated when we press CTRL+S, I see that last parameter of my Postmessage is wrong. It should be something like (for KEY UP CTRL key "C01F0001")
Can you please tell me how to pass this hexadecimal as last parameter of postmessage?
// does not work
PostMessage(handle, KEY_UP, CTRL_KEY, new IntPtr(0xC01F0001);
Update 4 : I think we cannot send "hot key" messages to a minimized window. using Post Message.
This below code works with SendInput: but it will pop up the window, which kind a mar the purpose. Anyways, just wanted to update this thread.
private void Save_Notepad(IntPtr mainWindowHandle) {
//SetActiveWindow(mainWindowHandle);
//SetFocus(GetWindow(mainWindowHandle, 5));
ShowWindow(mainWindowHandle, 1); // show window
SetForegroundWindow(mainWindowHandle);
//SetActiveWindow(mainWindowHandle);
//SetFocus(GetWindow(mainWindowHandle, 5));
//IntPtr returnvalue = SetFocus(mainWindowHandle);
uint intReturn;
INPUT structInput;
structInput = new INPUT();
structInput.type = 1;// keyboard input
// key down
structInput.ki.wScan = 0x1D;
structInput.ki.time = 0;
structInput.ki.dwFlags = 0;
// key down Ctrl
structInput.ki.wVk = 0x11; //0x1D; //
intReturn = SendInput(1, ref structInput, Marshal.SizeOf(new INPUT()));
// key down S
structInput.ki.wScan = 0x1F;
structInput.ki.wVk = 0x53; //0x41;//0x53; //0x1F;//
intReturn = SendInput(1, ref structInput, Marshal.SizeOf(new INPUT()));
// key up
structInput.ki.dwFlags = 0x0002; // key up
// key up S
intReturn = SendInput(1, ref structInput, Marshal.SizeOf(typeof(INPUT)));
// key up CTRL
structInput.ki.wVk = 0x11; //0x1D; //
intReturn = SendInput(1, ref structInput, Marshal.SizeOf(new INPUT()));
//ShowWindow(mainWindowHandle, 2); // minimize it again
}
After CTRL + S I can minimize the window but then it will create a flash.
Can anyone suggest if I can achieve this functionality without the "FLASH" (at least)?
UPDATE 5 : using WM_SYSCOMMAND
IntPtr handle = GetWindow(mainWindowHandle, 5);
// send Alt F message to Notepad.
// http://msdn.microsoft.com/en-us/library/ms646360(v=VS.85).aspx
// I can see this is working.
PostMessage(handle, 0x0112, 0xF100, 'f'); // send WM_SYSCOMMAND
// how to send s on this window ??
// I have tried things which I have learned so far.
I just need to send S messages to file menu which will pop up as a result of WM_SYSCOMMAND. Can anyone point me to a documentation to how to do that?
PostMessage(handle, KEY_DOWN, S_KEY, 0x1F0001); // POST does not work
// tried with mainwindowhandle as well
PostMessage(handle, 0x111, 'S', 0); // WM_COMMAND does not work
PostMessage(handle, 0x0112, 0xF100, 's'); // WM_SYSCOMMAND does not work (and does not make sence either)
Final Update
I was not able to send ^S message to a minimized window. I am using sendkeys(^s) every 2-3 seconds when the focus is on an editor.
-- Karephul
You cannot use SendMessage (or PostMessage, the correct one) to simulate the state of the modifiers keys, like CTRL. You must use SendInput().
A keystroke like Ctrl+S is not untypically translated into a WM_COMMAND message. Use the Spy++ tool to see what happens when you type Ctrl+S by hand. If you see WM_COMMAND then you're golden, you can use SendMessage() to send that message. This will of course only work on a specific process.
I needed to do exactly what you need to do, call a Ctrl+S in some background window, after about a day of research there seem to be many ways of achieving this.
For instance to press Alt-F then a 'S' you can call:
PostMessage(handle, 0x0112, 0xF100, 0x0046);
PostMessage(handle, 0x0102, 0x0053, 0);
The Alt-F part works on background windows, while the subsequent 'S' does not.
Another way of doing this is with WM_COMMAND and WM_MENUSELECT, MF_MOUSESELECT:
IntPtr menu = GetMenu(handle);
IntPtr subMenu = GetSubMenu(menu, 0);//0 = first menu item
uint menuItemID = GetMenuItemID(subMenu, 2);//2 = second item in submenu
SendMessage(handle, 0x0111, 0x20000000 + menuItemID, menu);
Finally and somewhat ironically the simples solution is to call WM_COMMAND with the menuItemID:
PostMessage(handle, 0x0111, 0x0003, 0x0);
The 0x0003 (save in notepad) is determined by the app and it's menu structure, you can get this code by listening to WM_COMMAND in spy++ on the window while you either use the combo key combination or press the mouse button on the menu command.
It seems it's also possible to use WM_SYSKEYUP/DOWN and WM_MENUCOMMAND, also keys like Ctrl-C, Ctrl-V have constant defined values and can be passed as one character, but only to apps that listen to these hardcoded chars...
Thanks for the starting point.
I have a WPF TaskBar like application that i am developing for fun and i want to be able to open the System Menu for the application (that is in my custom taskbar) that i right click on, preserving any custom menu that the app might create (i.e. Google Chrome). I have the following code that works for a borderless window app i made previously, but it doesn't seem to work and i am wondering why? And what do i need to do to get it to work?
public static void ShowContextMenu(IntPtr hWnd)
{
SetForegroundWindow(hWnd);
IntPtr wMenu = GetSystemMenu(hWnd, false);
// Display the menu
uint command = TrackPopupMenuEx(wMenu,
TPM.LEFTBUTTON | TPM.RETURNCMD, 0, 0, hWnd, IntPtr.Zero);
if (command == 0)
return;
PostMessage(hWnd, WM.SYSCOMMAND, new IntPtr(command), IntPtr.Zero);
}
Tip: It appears that TrackPopupMenuEx(...) returns immediately with the value of 0, instead of waiting for a response...
It appears there is an issue with giving TrackPopupMenuEx the owner window handle of the Menu... instead i used the handle of my wpf window and then when posting the message, i used the Menu's owner... seems a bit strange to me but it works!
public static void ShowContextMenu(IntPtr hAppWnd, Window taskBar, System.Windows.Point pt)
{
WindowInteropHelper helper = new WindowInteropHelper(taskBar);
IntPtr callingTaskBarWindow = helper.Handle;
IntPtr wMenu = GetSystemMenu(hAppWnd, false);
// Display the menu
uint command = TrackPopupMenuEx(wMenu,
TPM.LEFTBUTTON | TPM.RETURNCMD, (int) pt.X, (int) pt.Y, callingTaskBarWindow, IntPtr.Zero);
if (command == 0)
return;
PostMessage(hAppWnd, WM.SYSCOMMAND, new IntPtr(command), IntPtr.Zero);
}