sta*_*005 0 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
Run Code Online (Sandbox Code Playgroud)
作为第二步,我想为自动化工具制作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
Run Code Online (Sandbox Code Playgroud)
因为它在Python中工作,我想我一定错过了一些非常基础和非常重要的东西.但我现在很瞎到找不到问题.非常感谢任何提示.
但奇怪的是我无法使用Windows apis在Delphi中读/写编辑控件.
pywinauto使用标准的Win32 API,所以它可以做任何事情,你可以在Delphi中做.
pywinauto是开源的,所以你可以看到如何ctrl.ClickInput()和ctrl.SetEditText()实现.
ctrl.ClickInput()电话SetCursorPos()和SendInput().
ctrl.SetEditText()发送EM_SETSEL消息以突出显示编辑控件的当前文本,然后发送EM_REPLACESEL消息以使用新文本替换突出显示的文本.我的猜测是Edit控件的"反输入保护"可能不会阻止这些消息.
还有一点需要注意,pywinauto倾向于调用WaitForInputIdle()并Sleep()在其他窗口/进程中执行操作后,给目标一些时间来处理操作.这可能是"反输入保护"中的一个因素,试图清除自动代码但允许用户活动.
SetForegroundWindow(EditControlHandle); // Works,应用程序将被带到前面,编辑控件将被聚焦
我从来没有听说过SetForegroundWindow()将儿童控制带到前台.即使它确实存在,SetForegroundWindow()也有许多限制可能会阻止您的应用设置前景窗口.
SetFocus的(EditControlHandle); //没有任何反应,如果它专注于当前表单的另一个编辑控件
如果要将输入焦点更改为另一个进程中的窗口,则必须使用调用线程将其附加到目标窗口的线程AttachThreadInput().这在SetFocus()文档中有明确说明.
SetText(EditControlHandle,'Hello world'); // 什么都没发生
SetText()不是标准的Win32 API函数.你的意思是SetWindowText()吗? SetWindowText()不能在另一个进程中设置窗口的文本,文档也说了.
或者是SetText()一个包装WM_SETTEXT?具有"反输入保护"的控件可能会阻止WM_SETTEXT它自身不生成的消息.
SendKeys32.SendKey('Hello world',{Wait =} True); // 什么都没发生
SendKeys只是将击键放入系统的键盘队列,让Windows将它们传递到聚焦窗口.这应该有效,因为应用程序无法区分用户输入的击键和SendKeys注入的击键.除非目标应用程序挂钩SendKeys()并keybd_event()检测注入的击键,否则就是这样.
你试过这段代码吗?
http://www.experts-exchange.com/Programming/Languages/Pascal/Delphi/Q_27432926.html
SendMessage(EditControlHandle,Ord('H'),WM_KEYDOWN,0); //没有任何事情发生
SendMessage(EditControlHandle,Ord('H'),WM_KEYUP,0);
你有Msg和wParam参数值向后.Ord('H')是72,这是WM_POWER消息.编辑控件不关心电态变化.
发送这些消息时还需要包含一些标志:
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);
Run Code Online (Sandbox Code Playgroud)
FocusedThreadID:= GetWindowThreadProcessID(ExternalAppMainWindowHandle,nil);
如果使用AttachThreadInput(),则需要附加到拥有Edit控件的线程,因此请使用Edit控件的HWND,而不是其父HWND.
如果AttachThreadInput(GetCurrentThreadID,FocusedThreadID,{Attach =} True)则//返回False
您使用的是哪个版本的Windows?在Vista及更高版本中,GetLastError()如果AttachThreadInput()失败则返回有效的错误代码.
更新:您显示的脚本的pywinauto源代码的粗略翻译在Delphi中看起来像这样:
uses
..., Windows;
procedure WaitGuiThreadIdle(wnd: HWND);
var
process_id: DWORD;
hprocess: THandle;
begin
GetWindowThreadProcessId(wnd, process_id);
hprocess := OpenProcess(PROCESS_QUERY_INFORMATION, 0, process_id);
WaitForInputIdle(hprocess, 1000);
CloseHandle(hprocess);
end;
function SndMsgTimeout(wnd: HWND; Msg: UINT; wParam: WPARAM; lParam: LPARAM): DWORD_PTR;
begin
SendMessageTimeout(wnd, Msg, wParam, lParam, SMTO_NORMAL, 1, @Result);
end;
var
wnd, ctrl, cur_foreground: HWND;
cur_fore_thread, control_thread: DWORD;
r: TRect;
input: array[0..1] of TInput;
i: Integer;
begin
// hwnd = pywinauto.findwindows.find_windows(class_name='TLoginForm')[0]
wnd := FindWindow('TLoginForm', nil);
// window = helper.window_(handle=hwnd)
// ctrl = window[2] # the second control is the edit control I want to access
wnd := GetWindow(wnd, GW_CHILD);
ctrl := GetWindow(wnd, GW_HWNDNEXT);
// ctrl.ClickInput() # focus the control
cur_foreground := GetForegroundWindow();
if ctrl <> cur_foreground then
begin
cur_fore_thread := GetWindowThreadProcessId(cur_foreground, nil);
control_thread := GetWindowThreadProcessId(ctrl, nil);
if cur_fore_thread <> control_thread then
begin
AttachThreadInput(cur_fore_thread, control_thread, True);
SetForegroundWindow(ctrl);
AttachThreadInput(cur_fore_thread, control_thread, False);
end
else
SetForegroundWindow(ctrl);
WaitGuiThreadIdle(ctrl);
Sleep(60);
end;
GetWindowRect(ctrl, r);
SetCursorPos((r.Width div 2) + r.Left, (r.Height div 2) + r.Top);
Sleep(10);
for I := 0 to 1 do
begin
input[I].Itype := INPUT_MOUSE;
input[I].mi.dx := 0;
input[I].mi.dy := 0;
input[I].mi.mouseData := 0;
input[I].mi.dwFlags := 0;
input[I].mi.time := 0;
input[I].mi.dwExtraInfo := 0;
end;
if GetSystemMetrics(SM_SWAPBUTTON) = 0 then
begin
input[0].mi.dwFlags := MOUSEEVENTF_LEFTDOWN;
input[1].mi.dwFlags := MOUSEEVENTF_LEFTUP;
end else
begin
input[0].mi.dwFlags := MOUSEEVENTF_RIGHTDOWN;
input[1].mi.dwFlags := MOUSEEVENTF_RIGHTUP;
end;
for I := 0 to 1 do
begin
SendInput(1, @input[I], Sizeof(TInput));
Sleep(10);
end;
// ctrl.SetEditText('Hello world') # text can be changed expectedly
SndMsgTimeout(ctrl, EM_SETSEL, 0, -1);
WaitGuiThreadIdle(ctrl);
Sleep(0);
SndMsgTimeout(ctrl, EM_REPLACESEL, 1, LPARAM(PChar('Hello world')));
WaitGuiThreadIdle(ctrl);
Sleep(0);
end;
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
2624 次 |
| 最近记录: |