C# Capture Microsoft Print to PDF dialog - c#

I would like to capture and suppress the Savefiledialog that is shown when using the Microsoft Print to PDF driver from an application that is not office. Then programmatically enter the file path, possibly with System.Windows.Automation.
I can’t seem to find a handle to the SaveFileDialog when its displayed. I believe I could handle the Windows.Automation part.
I would like to use the Microsoft driver, since it is shipped with all windows 10.
Are there other ways to capture/suppress this dialog? I currently do this with another pdf driver on my local machine through the registry. But I would like to move to the Microsoft PDF since it is standard.
The Thread:
How to programmatically print to PDF file without prompting for filename in C# using the Microsoft Print To PDF printer that comes with Windows 10 -Does not solve my problem, and prints a blank page when run from the Revit API. Autodesk Revit has to initiate the printing (and is done through its api).
Code for trying to find the dialog from user32.dll
public static List<IntPtr>GetChildWindows( IntPtr parent) {
List<IntPtr>result=new List<IntPtr>();
GCHandle listHandle=GCHandle.Alloc(result);
try {
EnumWindowProc childProc=new EnumWindowProc(EnumWindow);
EnumChildWindows(parent, childProc, GCHandle.ToIntPtr(listHandle));
}
finally {
if (listHandle.IsAllocated) listHandle.Free();
}
return result;
}
public static string GetText(IntPtr hWnd) {
int length=GetWindowTextLength(hWnd);
StringBuilder sb=new StringBuilder(length + 1);
GetWindowText(hWnd, sb, sb.Capacity);
return sb.ToString();
}
private void Test() {
Process[] revits=Process.GetProcessesByName( "Revit");
List<IntPtr>children=GetChildWindows( revits[0].MainWindowHandle);
var names=new List<string>();
foreach (var child in children) {
names.Add(GetText(child));
}
}

I ran a few tests on my own system and it appears that enumerating the top level windows will find the Save file dialog. I tried with printing to the MS PDF printer from multiple programs and the results were all the same. Below is some code adapted from the MS Docs window enumeration example. I added in the code to get the process ID so you can check to be sure it is your window.
// P/Invoke declarations
protected delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
protected static extern int GetWindowText(IntPtr hWnd, StringBuilder strText, int maxCount);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
protected static extern int GetWindowTextLength(IntPtr hWnd);
[DllImport("user32.dll")]
protected static extern bool EnumWindows(EnumWindowsProc enumProc, IntPtr lParam);
[DllImport("user32.dll")]
protected static extern bool IsWindowVisible(IntPtr hWnd);
[DllImport("user32.dll", SetLastError = true)]
static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId);
// Callback for examining the window
protected static bool EnumTheWindows(IntPtr hWnd, IntPtr lParam)
{
int size = GetWindowTextLength(hWnd);
if (size++ > 0 && IsWindowVisible(hWnd))
{
StringBuilder sb = new StringBuilder(size);
GetWindowText(hWnd, sb, size);
if (sb.ToString().Equals("Save Print Output As", StringComparison.Ordinal))
{
uint procId = 0;
GetWindowThreadProcessId(hWnd, out procId);
Console.WriteLine($"Found it! ProcID: {procId}");
}
}
return true;
}
void Main()
{
EnumWindows(new EnumWindowsProc(EnumTheWindows), IntPtr.Zero);
}

Related

Capture events from other windows applications

I have a third party application which I did not create myself. I need to create an application which is able to listen for button clicks and read data from tables in that application. I believe that the third party application is made in C# but I do not know for sure. Is there a way to know when UI buttons are pressed and to harvest data from the application? I do not mind which programming language the solution must be written in, as long as it fulfils the above tasks.
You can use few dlls like user32.dll, to get data from other apps.
Find parent handle of a window:
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
public static IntPtr FindWindow(string windowName)
{
var hWnd = FindWindow(windowName, null);
return hWnd;
}
After that, find handle of a child window
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr childAfter, string className, string windowTitle);
private IntPtr FindSomeElement(IntPtr parent)
{
IntPtr childHandle;
childHandle = FindWindowEx(
parent,
IntPtr.Zero,
"WindowsForms10.EDIT.app21",
IntPtr.Zero);
return childHandle;}
and get text from it:
private static string GetText(IntPtr childHandle)
{
const uint WM_GETTEXTLENGTH = 0x000E;
const uint WM_GETTEXT = 0x000D;
var length = (int)SendMessage(handle, WM_GETTEXTLENGTH, IntPtr.Zero, null);
var sb = new StringBuilder(length + 1);
SendMessage(handle, WM_GETTEXT, (IntPtr)sb.Capacity, sb);
return sb.ToString();
}
//I didnt test this code, just gave an idea.
Visit www.pinvoke.net/default.aspx/ for more information. You can find alot about user32.dll

Send text to "save as" dialog

I have winapi c# to save webpage as pdf. Application uses control + P on webpage and hit enter. My default printer is "nuance pdf" and I want to save the file as pdf. My code looks as below:
static class Program
{
[DllImport("user32.dll")]
public static extern int SetForegroundWindow(IntPtr hWnd);
[DllImport("User32.dll")]
public static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, string lParam);
[DllImport("user32.dll")]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll")]
public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
[STAThread]
static void Main()
{
Boolean Check = true;
string text = "MyFileName";
while (Check)
{
Process[] processes = Process.GetProcessesByName("iexplore");
foreach (Process proc in processes)
{
SetForegroundWindow(proc.MainWindowHandle);
SendKeys.SendWait("^(p)");
SendKeys.SendWait("{ENTER}");
var handle = FindWindow(null, "Save As");
Console.WriteLine("handle {0}", handle);
//SendMessage(handle, 0x000C, IntPtr.Zero, text);
SendKeys.SendWait("{ENTER}");
}
Check = false;
}
}
}
This code saves the file name as "https___www.google.pdf". I would like to change the file name (highlighted).
How do I do that? Any pointers?
Thanks for helping out.
Given your current approach, and given that the text is highlighted by default, why not just send keys with the file name you want?
var handle = FindWindow(null, "Save As");
Console.WriteLine("handle {0}", handle);
SendKeys.SendWait(text);
SendKeys.SendWait("{ENTER}");
You may need to highlight the text first. Which could be a double click, or access the control directly, e.g. var filename = FindEdit(handle, "File Name:") to access it in case it's not highlighted by default.

C#: How to tell if an EXE has an icon?

I'm looking for a way to tell whether or not an EXE file contains an application icon. From the answer here, I tried this:
bool hasIcon = Icon.ExtractAssociatedIcon(exe) != null;
But this seems to work even if the EXE has no icon. Is there a way to detect this in .NET?
edit: I'm OK with solutions involving P/Invoke.
You can get the IDI_APPLICATION icon through SystemIcons.Application property from SystemIcons class
if (Icon.ExtractAssociatedIcon(exe).Equals(SystemIcons.Application))
{
...
}
See MSDN for more details.
Try this. Define your pinvoke like this:
[DllImport("user32.dll")]
internal static extern IntPtr LoadImage(IntPtr hInst, IntPtr name, uint type, int cxDesired, int cyDesired, uint fuLoad);
[DllImport("kernel32.dll")]
static extern bool EnumResourceNames(IntPtr hModule, int dwID, EnumResNameProcDelegate lpEnumFunc, IntPtr lParam);
delegate bool EnumResNameProcDelegate(IntPtr hModule, IntPtr lpszType, IntPtr lpszName, IntPtr lParam);
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern IntPtr LoadLibraryEx(string name, IntPtr handle, uint dwFlags);
private const int LOAD_LIBRARY_AS_DATAFILE = 0x00000002;
private const int LOAD_LIBRARY_AS_IMAGE_RESOURCE = 0x00000020;
private const int IMAGE_ICON = 1;
private const int RT_GROUP_ICON = 14;
Then you can write a function like this:
static bool HasIcon(string path)
{
// This loads the exe into the process address space, which is necessary
// for LoadImage / LoadIcon to work note, that LOAD_LIBRARY_AS_DATAFILE
// allows loading a 32-bit image into 64-bit process which is otherwise impossible
IntPtr moduleHandle = LoadLibraryEx(path, IntPtr.Zero, LOAD_LIBRARY_AS_DATAFILE | LOAD_LIBRARY_AS_IMAGE_RESOURCE);
if (moduleHandle == IntPtr.Zero)
{
throw new ApplicationException("Cannot load executable");
}
IntPtr index = IntPtr.Zero;
bool hasIndex = false;
bool enumerated = EnumResourceNames(moduleHandle, RT_GROUP_ICON, (module, type, name, param) =>
{
index = name;
hasIndex = true;
// Only load first icon and bail out
return false;
}, IntPtr.Zero);
if (!enumerated || !hasIndex)
{
return false;
}
// Strictly speaking you do not need this you can return true now
// This is to demonstrate how to access the icon that was found on
// the previous step
IntPtr result = LoadImage(moduleHandle, index, IMAGE_ICON, 0, 0, 0);
if (result == IntPtr.Zero)
{
return false;
}
return true;
}
It has added bonus that if you want to, after LoadImage you can load the icon with
Icon icon = Icon.FromHandle(result);
and do whatever you want with that.
Important note: I have not done any clean up in the function, so you cannot use it as is, you'll leak handles/memory. Proper clean up is left as an exercise for the reader. Read the description of every of the winapi function used in MSDN and call corresponding clean up functions as needed.
An alternate way using shell32 api can be found here, although I don't know if it has the same problem you encountered.
Also, old, but still very relevant article: https://msdn.microsoft.com/en-us/library/ms997538.aspx

Find handle of a ActiveX user control inside IE

How can I programatically find the handle of a user control in a webpage running on IE?
I'm able to find it using Spy++ but since the handle keeps changing I'm stuck.
I've been trying using FindWindow() but no luck :( I also wonder if I am doing something wrong or it simply only work for Windows...
Thanks in advance,
Zubrowka
I had a similar problem finding a PDF ActiveX Control inside a IE control in WPF.
To overcome the problem I used the EnumChildWindows API to find the correct child window and thus get its handle.
I'll include as much code as I can.
private static IntPtr FindPdfControlWindow(IntPtr parentHandle)
{
IntPtr result = IntPtr.Zero;
IntPtr matchPointer = IntPtr.Zero;
try
{
//allocate unmanaged memory for the result of the callback delegate
matchPointer = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(IntPtr)));
Marshal.WriteIntPtr(matchPointer, IntPtr.Zero)
//instantiate the delegate and pass to the API
NativeMethods.EnumWindowProc windowChecker = CheckForPdfControlWindow;
if (!NativeMethods.EnumChildWindows(parentHandle,
windowChecker,
matchPointer))
}
finally
{
if (matchPointer != IntPtr.Zero) Marshal.FreeHGlobal(matchPointer);
}
return result;
}
private static bool CheckForPdfControlWindow(IntPtr handle,
IntPtr matchPointer)
{
int captionLength = NativeMehtods.GetWindowTextLength(handle);
if (captionLength > 0)
{
StringBuilder buffer = new StringBuilder(captionLength + 1);
NativeMethods.GetWindowText(handle, buffer, buffer.Capacity);
if (buffer.ToString().Contains("Adobe"))
{
Marhsal.WriteIntPtr(matchPointer, handle)
return false;
}
}
return true;
}
private static class NativeMethods
{
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool EnumChildWindows(IntPtr window,
EnumWindowProc callback,
IntPtr i);
internal delegate bool EnumWindowProc(IntPtr hWnd, IntPtr parameter);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSer.Auto)]
internal static extern int GetWindowTextLength(IntPtr hWnd);
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
internal static extern int GetWindowText(IntPtr hWnd,
StringBuilder lpString,
int nMaxCount);
}
transcribed in a rush so I hope it is both helpful and accurate.
If the ActiveX control is windowed, then you can query its IOleWindow interface to get the window handle.
Before you query interfaces from the ActiveX, you need to review the page's HTML to find a way to identify the activex in the document, such as element id.

How do I get the classname of the active window?

By using this code I can get the title of the active window..
[DllImport("user32.dll")]
static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int count);
private string GetActiveWindowTitle()
{
const int nChars = 256;
IntPtr handle = IntPtr.Zero;
StringBuilder Buff = new StringBuilder(nChars);
handle = GetForegroundWindow();
if (GetWindowText(handle, Buff, nChars) > 0)
{
return Buff.ToString();
}
return null;
But how should I do to get the classname of the active window?
Simply pinvoke GetClassName(). This returns the Windows class name for a window, it doesn't have anything to do with a C# class. Getting the C# class name for a window in another process is not possible. Take a look at the Managed Spy++ tool for possible hacks if this is a Winforms app.
I expanded Hans Passant's answer into working code:
Usage:
string className = Spy.GetForegroundWindowClassName();
Class:
using System.Runtime.InteropServices;
using System.Text;
public static class Spy
{
[DllImport("user32.dll")]
private static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
public static string GetForegroundWindowClassName()
{
IntPtr hWnd = GetForegroundWindow();
var className = new StringBuilder(256);
GetClassName(hWnd, className, className.Capacity);
return className.ToString();
}
}
Side Note: in my case, I just needed a basic utility to tell me the class name of a window so I could reference that in my C# code. After writing the code above, I realized I could achieve the same thing using pre-existing utilities. One such utility I see mentioned often in the C# community is Visual Studio's Spy++ tool. I didn't bother trying that since it requires downloading 2.5 GB of C++ components. Instead, I used the "Window Spy" tool that comes with Autohotkey. Autohotkey is a tiny download compared to what's needed for Spy++, so I think it's a good option if it suits your needs.

Categories