在C#中向应用程序发送按键(sendkeys、postmessage、sendmessage都无效)

7

我想要做以下事情之一:

1.通过程序自动打开所需程序并按下一个键

2.查找程序的已打开窗口并按下一个键(任意一种方式均可)

我尝试了许多SendKeys.SendWait()、PostMessage()和SendMessage()实现,但都不成功。以下是我的代码片段:

    //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);

获取窗口句柄,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(ptrOBS, WM_KEYDOWN, (IntPtr)VK_R, (IntPtr)1);//tried both WM_CHAR and WM_KEYDOWN

PostMessage尝试:

string message = "rs";
bool sent = PostMessage(ptrOBS, WM_KEYDOWN, VkKeyScan(message[0]), 0);

SendKeys尝试:

SendKeys.SendWait("{r}");

我尝试在父窗口(应用程序)和子窗口(由按键触发的按钮)上使用SetFocus:

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}");
        }
    }

针对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);

目标进程是否已提升权限? - dotcomslashnet
如果那是真的,我可能会砸墙了。但实际上并没有什么区别。 - SPillai
请参见:http://stackoverflow.com/questions/4944621/create-an-on-screen-keyboard - tenfour
2个回答

6

您不能可靠地使用SendMessagePostMessage来合成键盘输入。它们并不是为此设计的。这些消息(WM_CHARWM_KEYDOWN等)是由较低级别的子系统在接收、处理和转发键盘输入时引发的通知,并发送或发布这些消息就像恶作剧电话一样。

SendKeys(像所有其他输入合成器方法一样,包括SendInput函数,它专门用于合成键盘输入,在至少某些实现中是SendKeys实际上在幕后使用的)仅在您希望接收键盘输入的窗口具有焦点时才起作用。在Windows中,只有具有焦点(活动的)窗口才会接收输入事件。

如果你想让这个程序工作,SendKeys可能是最好的选择(或者使用SendInput和所有相关结构的P/Invoke),但你需要遵守一个注意事项:接收窗口必须具有焦点,否则它将什么也得不到。

从你的示例代码中看来,你正在尝试使用SetForegroundWindow函数来满足这个前提条件。不幸的是,你传递了一个无效值,并且没有进行任何错误检查,可能会导致错误。具体来说,以下代码是错误的:

IntPtr ptrOBS = proc.Handle;//this works properly, proc is instantiated properly
SetForegroundWindow(ptrOBS); // WRONG, ptrOBS is not a window handle

即使我相信你正确地初始化了 ptrOBS,这使它成为一个指向 进程 的有效句柄,这与指向 窗口 的有效句柄非常不同。除了明显的名义上的差异外,进程可能有多个窗口,而只有一个窗口可以拥有焦点(即处于“前景”)。在调用 SetForegroundWindow 之前,您需要获取特定窗口的句柄,并且由于我们知道进程可以有多个窗口,因此这可能会很棘手。您需要一种可靠的方法来确定您想要的窗口。许多人通过将窗口的名称作为字符串硬编码来实现这一点,这在目标应用程序重新编译并更改此实现细节时非常有效。我能想到的唯一可靠的方式是让用户单击目标窗口,然后您的代码检索当前鼠标指针下的窗口的句柄。
当然,所有这些都假设您遵守使用 SetForegroundWindow 的限制,这些限制在链接的SDK文档的“备注”部分中列出。

IntPtr ptrOBS = FindWindow(null, "Open Broadcaster Software v0.472b"); 这个也不起作用,我猜FindWindow返回的是实际窗口句柄,而不是进程句柄?但感谢您指出我需要查看和更改的事项。现在我将继续使用真实的窗口句柄来尝试SendKeys方法。 - SPillai
@AHeinen 你确认 FindWindow 函数确实返回了一个句柄吗?而且是正确的句柄吗?可以使用 Spy++ 工具来验证,将其与你感兴趣的窗口的实际句柄进行比较。 - Cody Gray
它确实返回应用程序的实际父窗口。我还尝试了硬编码IntPtr十六进制=>十进制值,用于包含我想要通过按键运行操作的子窗口(热键按下的按钮)。使用任一/两个窗口都没有起作用。我尝试实现SetFocus(将在问题描述中更新我的尝试)。 - SPillai

3

你是否成功使用我提出的方法在目标窗口上进行了SetFocus操作 - 这是第一步,必须严格按照我的建议执行。我已经多次成功实现了这个功能。但是,有些应用程序是特殊的。如果有必要,也可以尝试将你的应用程序提升为管理员权限。 - NSGaga-mostly-inactive
或者您可以尝试发布您完成的最终代码(使用该方法)-我将复制并在此处运行(进行调整),如果时间允许,我会尝试查看。 - NSGaga-mostly-inactive
我尝试过的setfocus实现在上面;你想让我发布我的win32 api函数声明还是只调用setfocus然后sendkeys? - SPillai
我看到了 :) 我一定会去看看(等我找到时间,以后或明天)- 告诉我你有没有尝试过像我提到的“记事本”这样的其他程序。我认为你在按键发送方面做错了什么 - 那是一个棘手的部分。 - NSGaga-mostly-inactive
是的 :) 这既是好消息也是坏消息 - 你有多确定 OBSWindowClass 是你需要接收笔画的实际 target 窗口。这通常是核心所在。尝试从应用程序的主窗口向下遍历层次结构,进入子窗口 - 并将它们全部列出(转储等)。分析并找到最佳候选项(我认为可以转储窗口/控件长名称(如果不在我的代码中,则会找到该信息/方法)- 你还可以“绘制”矩形(getwinrect或类似),getwindowsunderpoint等(有许多这样有用的函数你需要用到)。你的目标是定位实际的控件以进行聚焦。 - NSGaga-mostly-inactive
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接