python - Delphi:模拟按键以实现自动化

标签 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 api 在 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 中工作,我想我一定错过了一些非常基本和非常重要的东西。但是我现在很盲目地发现问题。非常感谢任何提示。

最佳答案

But the weird thing is I am not able to read/write the edit control in Delphi by using Windows apis.

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, the application will be brought to front, the edit control will be focused

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

SetFocus(EditControlHandle); // Nothing happens, if it is focused at another edit control of the form currently

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

SetText(EditControlHandle, 'Hello world'); // Nothing happens

SetText() 不是标准的 Win32 API 函数。你是说 SetWindowText()反而? SetWindowText() 无法在另一个进程中设置窗口的文本,文档说明了这一点。

或者 SetText()WM_SETTEXT 的包装器?具有“反输入保护”的控件可能会阻止它不会自行生成的 WM_SETTEXT 消息。

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

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

你试过这段代码了吗?

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

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

MsgwParam 参数值倒过来了。 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);

FocusedThreadID := GetWindowThreadProcessID(ExternalAppMainWindowHandle, nil);

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

if AttachThreadInput(GetCurrentThreadID, FocusedThreadID, {Attach=}True) then // Returns 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;

关于python - Delphi:模拟按键以实现自动化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/24319987/

相关文章:

python - scikit-neuralnetwork 中神经网络的反向传播和结构

python - 我如何确定给定网址中是否存在任何内容

python - 如何使用多个窗口自动化 GNU screen

delphi - TTCPServer onAccept 上的 ShowModal 导致应用程序挂起?

c++ - 如何注册 Windows 服务但避免它被列在服务控制台中?

javascript - WkWebView 无法打开输入字段的键盘

intellij-idea - Intellij 以插入键开始

python - Django 模型 sqlmigrate - 参数太少

sql - 在sql server 2008中使用delphi插入数据的最佳方法

IOS - 触摸 UIPickerView 时关闭键盘