2

基本上我已经编写了一些代码来监听“另存为”对话框以在应用程序内弹出,当它完成时,它通过代码按下“保存”。这很好用,但是我需要能够在保存之前将文件路径设置为我想要的。

到目前为止,这是我的代码:

using System;
using System.Diagnostics;
using System.ComponentModel;
using System.Collections.Generic;



using System.Runtime.InteropServices;
using HWND = System.IntPtr;
using System.Text;

/// <summary>Contains functionality to get all the open windows.</summary>
public static class OpenWindowGetter
{
    private const int BN_CLICKED = 245;
    /// <summary>Returns a dictionary that contains the handle and title of all the open windows.</summary>
    /// <returns>A dictionary that contains the handle and title of all the open windows.</returns>
    public static IDictionary<HWND, string> GetOpenWindows()
    {

        HWND shellWindow = GetShellWindow();
        Dictionary<HWND, string> windows = new Dictionary<HWND, string>();

        EnumWindows(delegate (HWND hWnd, int lParam)
        {

            if (hWnd == shellWindow) return true;
            if (!IsWindowVisible(hWnd)) return true;

            int length = GetWindowTextLength(hWnd);
            if (length == 0) return true;

            StringBuilder builder = new StringBuilder(length);
            GetWindowText(hWnd, builder, length + 1);


            if (builder.ToString() == "Export Selection") //Check for the export selection window
            {
                //Press the "save" button through code here
                IntPtr hwndChild = FindWindowEx((IntPtr)hWnd, IntPtr.Zero, "Button", "&Save");
                SendMessage((HWND)(int)hwndChild, BN_CLICKED, (HWND)0, IntPtr.Zero);
            }


            windows[hWnd] = builder.ToString();
            return true;

        }, 0);

        return windows;
    }

    private delegate bool EnumWindowsProc(HWND hWnd, int lParam);


    [DllImport("USER32.DLL")]
    private static extern int SetWindowText(HWND hWnd, String lpString);

    [DllImport("USER32.DLL")]
    private static extern bool EnumWindows(EnumWindowsProc enumFunc, int lParam);

    [DllImport("USER32.DLL")]
    private static extern int GetWindowText(HWND hWnd, StringBuilder lpString, int nMaxCount);

    [DllImport("USER32.DLL")]
    private static extern int GetWindowTextLength(HWND hWnd);

    [DllImport("USER32.DLL")]
    private static extern bool IsWindowVisible(HWND hWnd);

    [DllImport("USER32.DLL")]
    private static extern IntPtr GetShellWindow();

    [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    public static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr childAfter, string lclassName, string windowTitle);

    [DllImport("User32.dll")]
    public static extern Int32 SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
}



namespace ConsoleApp1
{
    class Program
    {
       
        static void Main(string[] args)
        {
            while (true)
            {


                foreach (KeyValuePair<IntPtr, string> window in OpenWindowGetter.GetOpenWindows())
                {
                    IntPtr handle = window.Key;
                    string title = window.Value;

                    //Console.WriteLine("{0}: {1}", handle, title);
                }

            }
        }
    }
}


我一直在使用 FindWindowEx 和 SendMessage 来控制窗口句柄。这对于保存按钮非常有效,但现在我正在尝试访问窗口的工具栏部分并将其文本设置为所需的目录。我什至不确定这种方法是否可行,但它似乎是最简单的。我附上了一个截图供参考(红色圈出的区域是我试图访问并在“按”保存之前更改为给定文件路径的句柄)

图片

如图所示,我一直在使用 Spy++ 来获取有关 Window 及其句柄的信息。例如,如果我尝试这样做以获取指向该句柄的指针:

IntPtr hwndChildToolbar= FindWindowEx((IntPtr)hWnd, IntPtr.Zero, "ToolbarWindow32", "Address: "+"C:\\Windows\\System32");

它不起作用。在 Spy++ 应用程序中看到的“Caption”值会更改为当前目录名称,因此尝试访问它似乎没有意义。

下一行代码确实给了我一个指针作为回报,但它不是正确的地址。值得注意的是,此窗口上有多个句柄属于 ToolbarWindow32 类:

IntPtr hwndChildToolbar= FindWindowEx((IntPtr)hWnd, IntPtr.Zero, "ToolbarWindow32", null);

如果我能找到一种方法来获得正确的句柄,那么从这里我只想在可能的情况下使用 SetWindowText 并将其值设置为我想要的文件路径的字符串。

总结一下,我需要一些方法来轻松设置目录,我不确定这种方法是否可行。我的 C# 知识有限,所以有帮助!

4

1 回答 1

1

这是一个示例 .NET Framework 控制台应用程序,它使用 UI 自动化,打开记事本,键入内容并将其保存为临时文件夹中的文件,使用通用对话框顶部的地址栏。

有关 UI 自动化的介绍和参考,请参见此处.NET UI 自动化概述或此处的Native UI自动化。使用 UI 自动化通常比破解窗口句柄更好。如果你不能用 UI 自动化来做到这一点,那么你可能无论如何也不能用句柄来做到这一点。要发现可以使用和编码的元素,可以使用Windows SDK 中的检查工具

注意我在这里使用的是本机 UI 自动化的互操作版本,因为 Windows 提供的 .NET 原始包装器由于某种原因多年没有被 Microsoft 更新。

// this code needs the "Interop.UIAutomationClient" Nuget package and "using Interop.UIAutomationClient"
class Program
{
    private static readonly CUIAutomation8 _automation = new CUIAutomation8();

    static void Main()
    {
        // track window open event
        var processId = 0;
        _automation.AddAutomationEventHandler(UIA_EventIds.UIA_Window_WindowOpenedEventId, _automation.GetRootElement(), TreeScope.TreeScope_Subtree, null,
            new AutomationEventHandler((window, id) =>
        {
            // check the process id we opened
            if (window.CurrentProcessId != processId)
                return;

            // get editor control
            var editor = window.FindFirst(TreeScope.TreeScope_Children, _automation.CreatePropertyCondition(UIA_PropertyIds.UIA_ControlTypePropertyId, UIA_ControlTypeIds.UIA_EditControlTypeId));
            if (editor == null) // not the window we're looking for
                return;

            // get editor's value pattern & set some text value
            var value = (IUIAutomationValuePattern)editor.GetCurrentPattern(UIA_PatternIds.UIA_ValuePatternId);
            value.SetValue("hello world");

            // get menu bar
            var menuBar = window.FindFirst(TreeScope.TreeScope_Children, _automation.CreatePropertyCondition(UIA_PropertyIds.UIA_ControlTypePropertyId, UIA_ControlTypeIds.UIA_MenuBarControlTypeId));
            if (menuBar == null)
            {
                Console.WriteLine("Can't find menu bar.");
                return;
            }

            // get "File" menu item (beware of localization) & invoke (open)
            var file = menuBar.FindFirst(TreeScope.TreeScope_Children, _automation.CreatePropertyCondition(UIA_PropertyIds.UIA_NamePropertyId, "File"));
            if (file == null)
            {
                Console.WriteLine("Can't find 'File' menu item.");
                return;
            }

            // expand "File" menu
            var expand = (IUIAutomationExpandCollapsePattern)file.GetCurrentPattern(UIA_PatternIds.UIA_ExpandCollapsePatternId);
            expand.Expand();

            do
            {
                // get the "Save" item by name from the window subtree (as the menu that opens is a child of the window)
                // do some retry to handle menu opening time
                var save = window.FindFirst(TreeScope.TreeScope_Subtree, _automation.CreatePropertyConditionEx(UIA_PropertyIds.UIA_NamePropertyId, "Save", PropertyConditionFlags.PropertyConditionFlags_MatchSubstring));
                if (save != null)
                {
                    ((IUIAutomationInvokePattern)save.GetCurrentPattern(UIA_PatternIds.UIA_InvokePatternId)).Invoke();
                    break;
                }

            }
            while (true);

            // get the "Save As" dialog
            // do some retry to handle dialog opening time
            IUIAutomationElement dialog;
            do
            {
                dialog = window.FindFirst(TreeScope.TreeScope_Subtree, _automation.CreatePropertyCondition(UIA_PropertyIds.UIA_LocalizedControlTypePropertyId, "dialog"));
                if (dialog != null)
                    break;
            }
            while (true);

            // get the "Previous locations" to enable the Address edit box
            var previous = dialog.FindFirst(TreeScope.TreeScope_Subtree, _automation.CreateAndCondition(
                _automation.CreatePropertyCondition(UIA_PropertyIds.UIA_ControlTypePropertyId, UIA_ControlTypeIds.UIA_ButtonControlTypeId),
                _automation.CreatePropertyCondition(UIA_PropertyIds.UIA_NamePropertyId, "Previous Locations")));
            if (previous == null)
            {
                Console.WriteLine("Can't find 'Previous Locations' button.");
                return;
            }

            // push "Previous Locations" button
            var previousButton = (IUIAutomationInvokePattern)previous.GetCurrentPattern(UIA_PatternIds.UIA_InvokePatternId);
            previousButton.Invoke();

            // enter the directory path
            var address = dialog.FindFirst(TreeScope.TreeScope_Subtree, _automation.CreateAndCondition(
                _automation.CreatePropertyCondition(UIA_PropertyIds.UIA_ControlTypePropertyId, UIA_ControlTypeIds.UIA_EditControlTypeId),
                _automation.CreatePropertyCondition(UIA_PropertyIds.UIA_NamePropertyId, "Address")));
            if (address == null)
            {
                Console.WriteLine("Can't find 'Address' edit.");
                return;
            }

            // sets the directory (here we use the temp directory)
            var edit = (IUIAutomationValuePattern)address.GetCurrentPattern(UIA_PatternIds.UIA_ValuePatternId);
            edit.SetValue(System.IO.Path.GetTempPath());

            // push "Previous Locations" button again to "commit"
            previousButton.Invoke();

            // get the "File name:" edit
            // do some retry to handle folder refresh
            do
            {
                var fileName = dialog.FindFirst(TreeScope.TreeScope_Subtree, _automation.CreateAndCondition(
                    _automation.CreatePropertyCondition(UIA_PropertyIds.UIA_ControlTypePropertyId, UIA_ControlTypeIds.UIA_EditControlTypeId),
                    _automation.CreatePropertyConditionEx(UIA_PropertyIds.UIA_NamePropertyId, "File name", PropertyConditionFlags.PropertyConditionFlags_MatchSubstring)));
                if (fileName != null)
                {
                    // sets the file name (some "random" name)
                    ((IUIAutomationValuePattern)fileName.GetCurrentPattern(UIA_PatternIds.UIA_ValuePatternId)).SetValue(@"hello" + Environment.TickCount + ".txt");
                    break;
                }
            }
            while (true);

            // get the "Save" button
            var dialogSave = dialog.FindFirst(TreeScope.TreeScope_Subtree, _automation.CreateAndCondition(
                _automation.CreatePropertyCondition(UIA_PropertyIds.UIA_ControlTypePropertyId, UIA_ControlTypeIds.UIA_ButtonControlTypeId),
                _automation.CreatePropertyCondition(UIA_PropertyIds.UIA_NamePropertyId, "Save")));
            if (dialogSave == null)
            {
                Console.WriteLine("Can't find 'Save' button.");
                return;
            }

            // press the 'Save' button
            ((IUIAutomationInvokePattern)dialogSave.GetCurrentPattern(UIA_PatternIds.UIA_InvokePatternId)).Invoke();
        }));

        // start notepad
        var process = Process.Start("notepad");
        processId = process.Id;

        Console.WriteLine("Press any key to quit...");
        Console.ReadKey(false);
        try
        {
            process.CloseMainWindow();
        }
        catch
        {
            // maybe closed by something else, do nothing
        }
    }

    // helper class
    class AutomationEventHandler : IUIAutomationEventHandler
    {
        public AutomationEventHandler(Action<IUIAutomationElement, int> action)
        {
            if (action == null)
                throw new ArgumentNullException(nameof(action));

            Action = action;
        }

        public Action<IUIAutomationElement, int> Action { get; }
        public void HandleAutomationEvent(IUIAutomationElement sender, int eventId) => Action(sender, eventId);
    }
}
于 2021-04-21T15:51:15.987 回答