是否可以使用 IntPtr 在另一个程序中激活选项卡?

Posted

技术标签:

【中文标题】是否可以使用 IntPtr 在另一个程序中激活选项卡?【英文标题】:Is it possible to activate a tab in another program using an IntPtr? 【发布时间】:2015-07-09 04:38:31 【问题描述】:

提前致谢。

如果是这样,怎么做?

SendKeys 不是一个选项。

也许我需要的是钓鱼课。我已经筋疲力尽了 Google 和我的首席开发人员。 我将不胜感激一个彻底的解决方案或建议以继续我的 Google 工作。

基本流程是:

我将快捷方式图标拖到启动器

这会打开目标应用程序(Notepad++)并获取 IntPtr 等。

我想以编程方式选择 Notepad++ 中的各种项目,例如编辑、编辑下的菜单项或文档选项卡。

我运行的基本代码是:

“斑点”

项目 1:项目的 IntPtr 第 2 项:itemsChild 的 IntPtr 第 3 项:第 1 项的控制文本 item 4:是item 1的矩形参数

root 包含类似的信息:

【问题讨论】:

我建议您查看UI Automation API。 Notepad++ 看起来支持它,所以通过它来控制 UI 可能比 Windows API 更容易。 【参考方案1】:

正如其他人指出的那样,执行此操作的标准方法是使用UI Automation。 Notepad++ 确实支持 UI 自动化(在某种程度上,因为它是由 UI 自动化 Windows 层自动提供的)。

这是一个示例 C# 控制台应用程序,演示了以下场景(您需要引用 UIAutomationClient.dll、UIAutomationProvider.dll 和 UIAutomationTypes.dll):

1) 获取第一个运行的notepad++进程(你必须至少启动一个)

2)打开两个文件(注意notepad++中可能已经有其他打开的标签页)

3) 在无限循环中选择所有选项卡

class Program

    static void Main(string[] args)
    
        // this presumes notepad++ has been started somehow
        Process process = Process.GetProcessesByName("notepad++").FirstOrDefault();
        if (process == null)
        
            Console.WriteLine("Cannot find any notepad++ process.");
            return;
        
        AutomateNpp(process.MainWindowHandle);
    

    static void AutomateNpp(IntPtr handle)
    
        // get main window handle
        AutomationElement window = AutomationElement.FromHandle(handle);

        // display the title
        Console.WriteLine("Title: " + window.Current.Name);

        // open two arbitrary files (change this!)
        OpenFile(window, @"d:\my path\file1.txt");
        OpenFile(window, @"d:\my path\file2.txt");

        // selects all tabs in sequence for demo purposes
        // note the user can interact with n++ (for example close tabs) while all this is working
        while (true)
        
            var tabs = GetTabsNames(window);
            if (tabs.Count == 0)
            
                Console.WriteLine("notepad++ process seems to have gone.");
                return;
            

            for (int i = 0; i < tabs.Count; i++)
            
                Console.WriteLine("Selecting tab:" + tabs[i]);
                SelectTab(window, tabs[i]);
                Thread.Sleep(1000);
            
        
    

    static IList<string> GetTabsNames(AutomationElement window)
    
        List<string> list = new List<string>();

        // get tab bar
        var tab = window.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Tab));
        if (tab != null)
        
            foreach (var item in tab.FindAll(TreeScope.Children, PropertyCondition.TrueCondition).OfType<AutomationElement>())
            
                list.Add(item.Current.Name);
            
        
        return list;
    

    static void SelectTab(AutomationElement window, string name)
    
        // get tab bar
        var tab = window.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Tab));

        // get tab
        var item = tab.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, name));
        if (item == null)
        
            Console.WriteLine("Tab item '" + name + "' has been closed.");
            return;
        

        // select it
        ((SelectionItemPattern)item.GetCurrentPattern(SelectionItemPattern.Pattern)).Select();
    

    static void OpenFile(AutomationElement window, string filePath)
    
        // get menu bar
        var menu = window.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.MenuBar));

        // get the "file" menu
        var fileMenu = menu.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, "File"));

        // open it
        SafeExpand(fileMenu);

        // get the new File menu that appears (this is quite specific to n++)
        var subFileMenu = fileMenu.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Menu));

        // get the "open" menu
        var openMenu = subFileMenu.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.NameProperty, "Open..."));

        // click it
        ((InvokePattern)openMenu.GetCurrentPattern(InvokePattern.Pattern)).Invoke();

        // get the new Open dialog (from root)
        var openDialog = WaitForDialog(window);

        // get the combobox
        var cb = openDialog.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.ComboBox));

        // fill the filename
        ((ValuePattern)cb.GetCurrentPattern(ValuePattern.Pattern)).SetValue(filePath);

        // get the open button
        var openButton = openDialog.FindFirst(TreeScope.Children, new AndCondition(
            new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Button),
            new PropertyCondition(AutomationElement.NameProperty, "Open")));

        // press it
        ((InvokePattern)openButton.GetCurrentPattern(InvokePattern.Pattern)).Invoke();
    

    static AutomationElement WaitForDialog(AutomationElement element)
    
        // note: this should be improved for error checking (timeouts, etc.)
        while(true)
        
            var openDialog = element.FindFirst(TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Window));
            if (openDialog != null)
                return openDialog;
        
    

    static void SafeExpand(AutomationElement element)
    
        // for some reason, menus in np++ behave badly
        while (true)
        
            try
            
                ((ExpandCollapsePattern)element.GetCurrentPattern(ExpandCollapsePattern.Pattern)).Expand();
                return;
            
            catch
            
            
        
    

如果您想知道这是如何实现的,那么您必须阅读 UI 自动化。所有工具之母称为 Inspect:https://msdn.microsoft.com/library/windows/desktop/dd318521.aspx 确保您获得的版本至少为 7.2.0.0。注意还有一个叫做 UISpy 但检查更好。

请注意,不幸的是,notepad++ 选项卡文本内容 - 因为它基于自定义闪烁编辑器控件 - 不能正确支持自动化(我们无法轻松读取它,我想我们必须为此使用闪烁 Windows 消息),但它可以添加到它(嘿,闪烁的家伙,如果你读到这个...... :)。

【讨论】:

虽然不是对我的问题的准确答案,但 UI 自动化评论推动我们进行 UI 测试,我们将使用它来处理非 SendKey 事件。再次感谢!【参考方案2】:

如果 SendKeys 不是一个选项,这几乎是不可能的但阅读更多

现在更重要的部分是问题 - 为什么:

我们必须看看 win32 应用程序是如何工作的:它有一个 WndProc/WindowProc 方法,它负责处理来自 UI 的“事件”。 所以windows应用程序中的每个事件都必须经过上述方法。 SendKeys 方法是SendMessage (MSDN) 的一个特殊方法,因此您可以使用SendMessage 来控制除您之外的其他exe。

简单代码如下所示:

IntPtr hwnd = FindWindow("Notepad++", null);
SendMessageA(hwnd, WM_COMMAND, SOMETHING1, SOMETHING2);

*** 上已经有如何使用 chrome 来做到这一点的示例:C# - Sending messages to Google Chrome from C# application,但这只是一个开始。您将必须找出您要发送的确切消息。

在您描述的确切情况下,我将尝试将 WM_MOUSE 和 WM_KEYBORD 事件发送到 Notepad++ 事件,但这只是一个想法 :)

【讨论】:

【参考方案3】:

除了 Garath 的回答之外,您可能还想研究 Windows 自动化 API,即用于为 GUI 应用程序实现编码 UI 测试的技术。作为常规功能测试的一部分,我经常使用这些 API 从一组 NUnit 测试中控制外部应用程序。

UIAVerify 之类的工具会告诉您应用程序中可用的控件,您可以使用Invoke Pattern(和许多其他工具)在运行时与控件进行交互。

如果您想了解如何使用自动化 API 的详细示例,开源 TestStack White 项目非常方便。

【讨论】:

以上是关于是否可以使用 IntPtr 在另一个程序中激活选项卡?的主要内容,如果未能解决你的问题,请参考以下文章

C++,MFC MDI,激活特定选项卡

Pinvoke SetFocus 到特定控件

检查 Windows 激活状态返回错误值

IntPtr、SafeHandle 和 HandleRef - 解释

如何从引导程序中的另一个页面激活动态选项卡?

是否可以在另一个过程中打开包中的游标?