单击其他程序工具栏中的按钮

Click Button in Toolbar of Other Program

我正在尝试在我没有源代码的遗留应用程序上自动化一些东西。所以我实际上是在尝试使用 Windows API 来单击我需要的按钮。

有一个 msvb_lib_toolbar 类型的工具栏,如下所示:

我可以通过使用此代码来处理它(我认为):

IntPtr window = FindWindow("ThunderRT6FormDC", "redacted");
IntPtr bar = FindWindowEx(window, IntPtr.Zero,"msvb_lib_toolbar",null);

查看文档,似乎我应该可以使用 SendMessageTB_PRESSBUTTON 消息来单击这些按钮:

[DllImport("user32.dll")]
public static extern int SendMessage(int hWnd, uint Msg, int wParam, int lParam);

但是,我不确定如何设置 wParamlParam 以单击栏上的所需按钮。该文档似乎也没有太大帮助。

能请教一下吗?


根据评论,我也尝试过 UIAutomation。我可以使用以下代码找到工具栏:

AutomationElement mainWindow = AutomationElement.RootElement.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, "Migration Expert"));
AutomationElement toolbar = mainWindow.FindFirst(TreeScope.Subtree, new PropertyCondition(AutomationElement.ClassNameProperty, "msvb_lib_toolbar"));

但是从这里开始,我不确定该怎么做,因为 Spy++ 没有显示该对象的其他子对象:

看着这个 AutomationElementCurrent 属性 我没有看到任何东西跳到我身上但是 BoundingRectangle 似乎表明我已经找到了正确的元素。

使用 inspector.exe 也不会在工具栏上指示任何子项。

这不是一个理想的解决方案,但我使用 pywinautopyautogui 的组合得到了一些快速而肮脏的工作。

import pyautogui
import subprocess
import sys
import time
import os
from os import path
from glob import glob
from subprocess import check_output


from pywinauto import application


def click_at_image(image):
    location = pyautogui.locateOnScreen(image)
    buttonx, buttony = pyautogui.center(location)
    pyautogui.click(buttonx, buttony)

def get_dcf_filepaths():
    files = []
    start_dir = redacted
    pattern = "*.DCF"
    for dir, _, _ in os.walk(start_dir):
        files.extend(glob(os.path.join(dir, pattern)))
    return files

def get_csv_paths(paths):
    csv_paths = []
    for p in paths:
        csv_paths.append(p.replace(redacted,redacted).replace("DCF","csv").replace("dcf","csv"))
    return  csv_paths


def main():
    app = application.Application().start(redacted)
    files = get_dcf_filepaths()
    csv_paths = get_csv_paths(files)
    time.sleep(3)
    click_at_image("new_button.png") #Open new project
    for i in range(0, len(files)):
        if (path.exists(csv_paths[i])):
            #os.remove(csv_paths[i])
            continue
        time.sleep(1)
        # Click on nxt icon in dialog
        click_at_image("nxt_button.png")
        # Enter file path into OFD
        app.Open.Edit.SetText(files[i])
        pyautogui.press('enter')
        pyautogui.press('enter')
        time.sleep(1)
        # Click on m2c icon in toolbar
        click_at_image("m2c_button.png")
        # Wait for Excel to open
        time.sleep(6)
        # Open Save as dialog and browse
        pyautogui.press('alt')
        pyautogui.press('f')
        pyautogui.press('a')
        pyautogui.press('o')
        time.sleep(2)
        pyautogui.press('backspace')
        # Enter file path
        pyautogui.write(csv_paths[i], interval=0.01)
        #click_at_image("dummy.png")
        # Change file type to CSV and ignore any popups
        click_at_image("dd.png")
        time.sleep(1)
        
        click_at_image("csv.png")
        pyautogui.press('enter')
        pyautogui.press('enter')
        pyautogui.press('enter')
        time.sleep(2)
        # Kill excel
        pyautogui.hotkey('alt', 'f4')
        # Pull main window back to top
        app.top_window().set_focus()
        time.sleep(1)
        # New project
        click_at_image("new_button.png")
        time.sleep(0.50)
        # Don't save last one
        click_at_image("no.png")

if __name__ == "__main__":
    main()

基本上我不得不求助于使用屏幕抓取来点击不可访问的按钮。如果这是为了需要更强大的东西,我会在 C# 中使用 Win32 API 直接对除屏幕抓取之外的所有内容进行此操作并等待一些额外的检查windows 出现而不是使用哑计时器。

话虽这么说,这是有效的,可能对未来的读者有所帮助。