Basically I've written some code that listens for a "save as" dialog box to pop up inside an application, and when it does it presses "save", all through code. This works great, however I need to be able to set the file path to what I'd like before saving.
Here is my code so far:
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);
}
}
}
}
}
I've been using FindWindowEx and SendMessage for controlling the window handles. This worked perfectly for the Save button, but now I'm trying to access the toolbar part of the window and set its text to the desired directory. I'm not even sure if this approach will work, but it seems the most simple. I've attached a screenshot for reference (the area circled in red is the handle I'm trying to access and change to a given file path before I "press" save)
As seen in the photo I've been using Spy++ to get info about the Window and its handles. If I attempt this for example to get a pointer to that handle:
IntPtr hwndChildToolbar= FindWindowEx((IntPtr)hWnd, IntPtr.Zero, "ToolbarWindow32", "Address: "+"C:\\Windows\\System32");
It doesn't work. The "Caption" value seen in the Spy++ app changes to whatever the current directory name is, so trying to access that doesn't seem to make sense.
This next line of code does give me a pointer in return, but it's not the correct address. Worth noting is there are multiple handles on this window that fall under the ToolbarWindow32 class:
IntPtr hwndChildToolbar= FindWindowEx((IntPtr)hWnd, IntPtr.Zero, "ToolbarWindow32", null);
If I can find a way to get the correct handle, then from here I just want to use SetWindowText if that's possible and set its value to a string that's my intended file path.
To sum it up I need some way to easily set the directory, and I'm not sure if this way is possible. My C# knowledge is limited so anything helps!
Here is a sample .NET Framework Console App that uses UI Automation, opens Notepad, type something and saves it as a file in the temp folder, using the Address bar on top of the Common Dialog.
See here .NET UI Automation Overview or here Native UI Automation for introduction and reference on UI Automation. It's usually better to use UI Automation than hacking windows handles. If you can't do it with UI Automation, there are chances you won't be able to do it with handles anyway. To discover the elements you can use and code against, you can use the Inspect tool from the Windows SDK.
Note I'm using here an interop version of the native UI Automation, as the .NET original wrappers that are provided with Windows have not been updated by Microsoft for many years for some reason.