德尔福:模拟按键以实现自动化

时间:2014-06-20 04:30:15

标签: python delphi keyboard ui-automation pywinauto

我想更改外部应用程序的编辑控件的文本。该应用程序是用Delphi编写的。它有几种形式。我从Python库pywinauto + sendkeys开始测试第一个表单TLoginForm。它完美地运作。这是伪代码:

helper = pywinauto.application.Application()
hwnd = pywinauto.findwindows.find_windows(class_name='TLoginForm')[0]
window = helper.window_(handle=hwnd)
ctrl = window[2]   # the second control is the edit control I want to access
ctrl.ClickInput()  # focus the control
ctrl.SetEditText('Hello world')  # text can be changed expectedly

作为第二步,我想为自动化工具制作一个UI。但由于缺乏Python UI知识并考虑到在Python中分发二进制文件的复杂性,我想做Delphi。但奇怪的是我无法使用Windows apis在Delphi中读/写编辑控件。以下是一些尝试:

SetForegroundWindow(EditControlHandle); // Works, the application will be brought to front, the edit control will be focused

// Attempt 1: Nothing happens
SetFocus(AnotherEditControlHandle);

// Attempt 2: Nothing happens
SetWindowText(EditControlHandle, 'Hello world');

// Attempt 3: Nothing happens
SendKeys32.SendKey('Hello world', {Wait=}True); 

// Attempt 4: Nothing happens
SendMessage(EditControlHandle, Ord('H'), WM_KEYDOWN, 0);
SendMessage(EditControlHandle, Ord('H'), WM_KEYUP, 0);

// Attempt 5: AttachThreadInput will return False, the reason is "Access Denied"
FocusedThreadID := GetWindowThreadProcessID(ExternalAppMainWindowHandle, nil);
if AttachThreadInput(GetCurrentThreadID, FocusedThreadID, {Attach=}True) then

因为它适用于Python,我认为我必须错过一些非常基本且非常重要的东西。但我现在很瞎到找不到问题。任何提示都非常感谢。

1 个答案:

答案 0 :(得分:5)

  

但奇怪的是我无法使用Windows apis在Delphi中读/写编辑控件。

pywinauto使用标准的Win32 API,所以它可以做任何事情,你可以在Delphi中做。

pywinauto is open source,以便了解ctrl.ClickInput()ctrl.SetEditText()的实施方式。

ctrl.ClickInput()来电SetCursorPos()SendInput()

ctrl.SetEditText()发送EM_SETSEL消息以突出显示编辑控件的当前文本,然后发送EM_REPLACESEL消息以使用新文本替换突出显示的文本。我的猜测是编辑控件的反输入保护"可能没有阻止这些消息。

要注意的是,pywinauto在执行其他窗口/进程中的操作后会调用WaitForInputIdle()Sleep(),以便为目标提供一些时间来处理操作。这可能是"反输入保护的一个因素"试图清除自动代码但允许用户活动。

  

SetForegroundWindow(EditControlHandle); // Works,应用程序将被置于前面,编辑控件将被聚焦

我从未听说SetForegroundWindow()将儿童控制带到前台。即使这样,SetForegroundWindow()也有许多限制,可能会阻止您的应用设置前景窗口。

  

的SetFocus(EditControlHandle); //没有任何反应,如果它专注于当前表单的另一个编辑控件

如果要将输入焦点更改为另一个进程中的窗口,则必须使用AttachThreadInput()将调用线程附加到目标窗口的线程。这在SetFocus() documentation明确说明。

  

SetText(EditControlHandle,' Hello world'); //什么都没发生

SetText()不是标准的Win32 API函数。你的意思是SetWindowText()吗? <{1}}无法在另一个进程中设置窗口的文本,文档也说明了这一点。

或者SetWindowText()SetText()的包装器吗?具有&#34;反输入保护的控件&#34;可能会阻止它自身不生成的WM_SETTEXT条消息。

  

SendKeys32.SendKey(&#39; Hello world&#39;,{Wait =} True); //什么都没发生

SendKeys只是将键击放入系统的键盘队列,让Windows将它们传送到焦点窗口。这应该有效,因为应用程序无法区分用户输入的击键和SendKeys注入的击键。除非目标应用程序挂钩WM_SETTEXTSendKeys()以检测注入的击键,否则就是这样。

您是否尝试过此代码?

http://www.experts-exchange.com/Programming/Languages/Pascal/Delphi/Q_27432926.html

  

SendMessage(EditControlHandle,Ord(&#39; H&#39;),WM_KEYDOWN,0); //什么都没发生   SendMessage(EditControlHandle,Ord(&#39; H&#39;),WM_KEYUP,0);

您向后有keybd_event()Msg个参数值。 wParam是72,这是Ord('H')消息。编辑控件不关心电态变化。

发送这些消息时还需要包含一些标志:

WM_POWER
  

FocusedThreadID:= GetWindowThreadProcessID(ExternalAppMainWindowHandle,nil);

如果使用var ScanCode: UINT; ScanCode := MapVirtualKey(Ord('H'), MAPVK_VK_TO_VSC); SendMessage(EditControlHandle, WM_KEYDOWN, Ord('H'), ScanCode shl 16); SendMessage(EditControlHandle, WM_KEYUP, Ord('H'), (ScanCode shl 16) or $C0000001); ,则需要附加到拥有Edit控件的线程,因此请使用Edit控件的HWND,而不是其父HWND。

  

如果AttachThreadInput(GetCurrentThreadID,FocusedThreadID,{Attach =} True)则//返回False

您使用的是哪个版本的Windows?在Vista及更高版本中,AttachThreadInput()会在GetLastError()失败时返回有效的错误代码。

更新:您显示的脚本的pywinauto源代码的粗略翻译在Delphi中看起来像这样:

AttachThreadInput()