Python Delphi:模拟按键实现自动化

Python Delphi:模拟按键实现自动化,python,delphi,keyboard,ui-automation,pywinauto,Python,Delphi,Keyboard,Ui Automation,Pywinauto,我想更改外部应用程序的编辑控件的文本。应用程序是用Delphi编写的。它有几种形式。我从Python库pywinauto+sendkeys开始测试第一个表单tloginfo。它工作得很好。以下是伪代码: helper = pywinauto.application.Application() hwnd = pywinauto.findwindows.find_windows(class_name='TLoginForm')[0] window = helper.window_(handle=hw

我想更改外部应用程序的编辑控件的文本。应用程序是用Delphi编写的。它有几种形式。我从Python库
pywinauto
+
sendkeys
开始测试第一个表单
tloginfo
。它工作得很好。以下是伪代码:

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 API在Delphi中读/写编辑控件。以下是一些尝试:

setforegroundindow(EditControlHandle);//工作,应用程序将被带到前面,编辑控件将被集中
//尝试1:什么也没发生
设置焦点(另一个编辑控制手柄);
//尝试2:什么也没发生
SetWindowText(EditControlHandle,“Hello world”);
//尝试3:什么也没发生
SendKeys32.SendKey('helloworld',{Wait=}True);
//尝试4:什么也没发生
SendMessage(EditControlHandle,Ord('H'),WM_KEYDOWN,0);
SendMessage(EditControlHandle,Ord('H'),WM_KEYUP,0);
//尝试5:AttachThreadInput将返回False,原因是“访问被拒绝”
FocusedThreadID:=GetWindowThreadProcessID(ExternalAppMainWindowHandle,无);
如果AttachThreadInput(GetCurrentThreadID,FocusedThreadID,{Attach=}True),则
由于它在Python中工作,我想我一定错过了一些非常基本和非常重要的东西。但我现在对这个问题视而不见。如有任何提示,我们将不胜感激

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

pywinauto使用标准的Win32 API,所以它能做的任何事情都可以在Delphi中完成

,因此您可以看到如何实现
ctrl.ClickInput()
ctrl.SetEditText()

ctrl.单击输入()
调用和

ctrl.SetEditText()
发送一条消息以突出显示编辑控件的当前文本,然后发送一条消息以用新文本替换突出显示的文本。我猜想编辑控件的“反输入保护”可能不会阻止这些消息

另外需要注意的是,pywinauto倾向于在其他窗口/进程中执行操作后调用
WaitForInputle()
Sleep()
,以便给目标一些时间来处理这些操作。这可能是“反输入保护”试图剔除自动代码但允许用户活动的一个因素

SetForegroundWindow(EditControlHandle);//工作,应用程序将被带到前面,编辑控件将被集中

我从未听说过
SetForegroundWindow()
将子控件引入前台。即使是这样,
setforegroughindow()
也有许多限制,可能会阻止应用程序设置前台窗口

设置焦点(EditControlHandle);//如果它当前集中在窗体的另一个编辑控件上,则不会发生任何事情

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

SetText(EditControlHandle,“Hello world”);//什么也没发生

SetText()
不是标准的Win32 API函数。你是说相反吗
SetWindowText()
无法在另一个进程中设置窗口的文本,文档中也这么说

还是
SetText()
WM\u SetText
的包装器?具有“反输入保护”的控件可能会阻止它自己不生成的
WM_SETTEXT
消息

SendKeys32.SendKey('Hello world',{Wait=}True);//什么也没发生

SendKeys只是将击键放入系统的键盘队列中,让Windows将它们传递到焦点窗口。这应该行得通,因为应用程序无法区分用户输入的击键和SendKeys注入的击键。除非目标应用程序正在钩住
SendKeys()
keybd_event()
以检测注入的击键,也就是说

你试过这个代码了吗

SendMessage(EditControlHandle,Ord('H'),WM_KEYDOWN,0);//什么也没发生
SendMessage(EditControlHandle,Ord('H'),WM_KEYUP,0)

您将
Msg
wParam
参数值向后设置
Ord('H')
是72,这是
WM\u 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);
FocusedThreadID:=GetWindowThreadProcessID(ExternalAppMainWindowHandle,无)

如果使用
AttachThreadInput()
,则需要附加到拥有编辑控件的线程,因此使用编辑控件的HWND,而不是其父HWND

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

您使用的是什么版本的Windows?在Vista和更高版本上,如果
AttachThreadInput()
失败,则
GetLastError()
返回有效的错误代码

更新:您显示的脚本的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;
使用
…,窗户;
程序等待线程空闲(wnd:HWND);
变量
进程id:DWORD;
hproces:THandle;
开始
GetWindowThreadProcessId(wnd,进程id);
HPProcess:=OpenProcess(进程查询信息,0,进程id);
WaitForInputIdle(hprocess,1000);
CloseHandle(hprocess);
结束;
函数SndMsgTimeout(wnd:HWND;Msg:UINT;wParam:wParam;lParam:lParam):DWORD_PTR;
开始
SendMessageTimeout(wnd、Msg、wParam、lParam、SMTO_NORMAL、1、@Result);
结束;
变量
wnd,ctrl,cur_前景:HWND;
当前前线程、控制线程:DWORD;
r:TRect;
输入:TInput的数组[0..1];
i:整数;
贝吉