问题描述
基本上我已经编写了一些代码来监听“另存为”对话框在应用程序中弹出,当它弹出时,它按下“保存”,所有这些都是通过代码完成的。这很好用,但是我需要能够在保存之前将文件路径设置为我想要的。
这是我目前的代码:
False
我一直在使用 findwindowex 和 SendMessage 来控制窗口句柄。这对于 Save 按钮非常有效,但现在我正在尝试访问窗口的工具栏部分并将其文本设置为所需的目录。我什至不确定这种方法是否有效,但它似乎是最简单的。我附上了一个截图以供参考(红色圈出的区域是我在“按”保存之前尝试访问并更改为给定文件路径的句柄)
如照片所示,我一直在使用 Spy++ 来获取有关 Window 及其句柄的信息。例如,如果我尝试这样做以获取指向该句柄的指针:
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);
}
}
}
}
}
它不起作用。在 Spy++ 应用程序中看到的“Caption”值更改为当前目录名称的任何内容,因此尝试访问它似乎没有意义。
下一行代码确实给了我一个返回指针,但它不是正确的地址。值得注意的是,此窗口上有多个句柄属于 ToolbarWindow32 类:
IntPtr hwndChildToolbar= findwindowex((IntPtr)hWnd,"ToolbarWindow32","Address: "+"C:\\Windows\\System32");
如果我能找到获得正确句柄的方法,那么从这里开始,如果可能的话,我只想使用 SetwindowText 并将其值设置为我想要的文件路径的字符串。
总而言之,我需要某种方式来轻松设置目录,我不确定这种方式是否可行。我的 C# 知识有限,所以任何事情都有帮助!
解决方法
这是一个示例 .NET Framework 控制台应用程序,它使用 UI 自动化、打开记事本、键入内容并将其保存为临时文件夹中的文件,使用通用对话框顶部的地址栏。
有关 UI 自动化的介绍和参考,请参阅此处 .NET UI Automation Overview 或此处 Native UI Automation。使用 UI 自动化通常比破解 Windows 句柄更好。如果您不能使用 UI 自动化来做到这一点,那么您可能无论如何也无法使用句柄来做到这一点。要发现您可以使用和编码的元素,您可以使用 Inspect tool from the Windows SDK。
请注意,我在这里使用了本机 UI 自动化的 interop version,因为 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,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),"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,UIA_ControlTypeIds.UIA_EditControlTypeId),"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,"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,"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);
}
}