7

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

Community
  • 1
  • 1
karephul
  • 1,473
  • 4
  • 19
  • 36
  • I mean, your update 4 is very nice and all, but isn't it a one liner with .NET SendKeys? – David Heffernan Mar 02 '11 at 09:03
  • 1
    What's more Hans has pointed out that you can just send a `WM_COMMAND` which will avoid showing the window if it is minimized – David Heffernan Mar 02 '11 at 09:04
  • @David : First of all I wanted a solution to send messages while the window is minimized .. I started with SendMessage, then PostMessage and as Hans suggested SendInput. As you suggested "SendKeys" really looks like a better solution to my Update 4. Update 4 is not the solution I was looking for, but if I have to settle down I will switch to SendKeys .. – karephul Mar 02 '11 at 15:21
  • @David : WM_COMMAND, can you please point me to some API documentation or reference how to use it to send accerlerator keys? I will really appreciate that. – karephul Mar 02 '11 at 15:26
  • You don't send an accelerator key. Notepad translates CTRL+S key press into a WM_COMMAND message. You just need to send the same WM_COMMAND as Notepad does. – David Heffernan Mar 02 '11 at 15:30
  • PostMessage(p.MainWindowHandle, 0x111, new IntPtr('A'), IntPtr.Zero); //opens "about notepad"
    2. PostMessage(p.MainWindowHandle, 0x111, new IntPtr('S'), IntPtr.Zero); // does nothing .. but I am trying to press "S" in the file menu ..
    – karephul Mar 02 '11 at 16:17

2 Answers2

5

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.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • I can see below messages in spy++ in sequence: 1. WM_KEYDOWN nVirtKey:VK_Control, 2. WM_KEYDOWN nVirtKey:'S' .. 3. WM_KEYUP nVirtKey:VK_Control. 4. WM_KEYUP nVirtKey:'S'.
    Sequence is little different what I initial thought but its on those lines. Thanks for the pointers
    – karephul Feb 28 '11 at 17:49
  • Put this in your question, not in a comment. I don't see WM_COMMAND, use SendInput(). – Hans Passant Feb 28 '11 at 17:56
  • I thought WM_KEYDOWN was a type of WM_COMMAND. I will look into SendInput() - which looks like something I can do on an Active window only. I want to send a CTRL S to an In Active window .. so that user don't realize that his notepad data is being saved. – karephul Feb 28 '11 at 18:07
  • Notepad *does* use WM_COMMAND, look at the frame window. This is otherwise a very good way to make the user accidentally save the file and never be able to find it back again. Ctrl+S brings up the save-as dialog for the 1st time, very bad when the user is busy typing. – Hans Passant Feb 28 '11 at 18:20
  • We are trying to control only few notepad windows, which will be created and opened for the user [this will prevent save-as dialog] also, Ctrl-S does not close the window so we are good there as well. So, you think sendmessage() API should work ? if yes, then I just need to find out right window handle to send these messages to [keyboard focus handle] – karephul Feb 28 '11 at 18:31
4

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.

critic
  • 309
  • 2
  • 4
  • why are you adding 0x20000000 to menuItemID ? – rodolfoprado Oct 07 '14 at 21:16
  • Sorry, it's been a while, seems I was using an accelerator with the 0x20000000 offset. https://msdn.microsoft.com/en-us/library/windows/desktop/ms647591(v=vs.85).aspx https://msdn.microsoft.com/en-us/library/windows/desktop/ms647575(v=vs.85).aspx – critic Mar 28 '15 at 19:13
  • @critic Could you please explain how you composed the "0xF100" part for ALT+F?, so we could get the idea about how to compose other composited keystrokes like for example CTRL+P or any other. someone else could explain this?. – ElektroStudios Sep 30 '17 at 18:11
  • Hi 0xF100 is the menu key-down (alt) event, 0x0046 is the hex value of the F key. https://msdn.microsoft.com/en-us/library/windows/desktop/ms646360(v=vs.85).aspx – critic Oct 01 '17 at 13:57
  • 2
    To those wondering: `0x0112 = WM_SYSCOMMAND`, `0x0102 = WM_CHAR`, `0x0111 = WM_COMMAND` – Joseph Lennox Dec 28 '21 at 20:06