Prevent Revit window from opening - c#

I'm trying to intercept Revit and keep a window from opening. Specifically, I'm trying to apply a keynote to an object and then let the user create a keynote tag, however any way I do it it lets them place the keynote but then immediately gives them the dialog to select a keynote, but I don't want that dialog to come up because I already know what the selection should be. However every way I can think of isn't able to interrupt the process to apply the keynote before the user gets the dialog. Is it possible to perhaps monitor for the window to appear then close it via Windows API? or even better intercept when it's going to be shown and stop it from showing?

you can always delete warrnings with:failuresAccessor.DeleteWarning(fma);
this is what i use for my code
public class FloorPreProcessor : IFailuresPreprocessor
{
FailureProcessingResult
IFailuresPreprocessor.PreprocessFailures(
FailuresAccessor failuresAccessor)
{
IList<FailureMessageAccessor> fmas
= failuresAccessor.GetFailureMessages();
if (fmas.Count == 0)
{
return FailureProcessingResult.Continue;
}
// We already know the transaction name.
if (fmas.Count != 0)
{
foreach (FailureMessageAccessor fma in fmas)
{
// DeleteWarning mimics clicking 'Ok' button.
failuresAccessor.DeleteWarning(fma);
}
return FailureProcessingResult
.ProceedWithCommit;
}
return FailureProcessingResult.Continue;
}
}
I hope it will help

Try the following, it searches for a window name, button name, then clicks this button:
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll")]
static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SendMessage(IntPtr hWnd, uint msg, int wParam, int lParam);
private const uint BM_CLICK = 0x00F5;
public static bool clickButton (string popUpTitle, string ButtonName)
{
// Get the handle of the window
IntPtr windowHandle = FindWindow((string)null, popUpTitle);
if (windowHandle.ToInt32() == 0)
{
return false;
}
// Get button handle
IntPtr buttonHandle = FindWindowEx(windowHandle, IntPtr.Zero, (string)null, ButtonName);
if (buttonHandle.ToInt32() == 0)
{
return false;
}
// Send click to the button
SendMessage(buttonHandle, BM_CLICK, 0, 0);
return true;
}
You should set the popUpTitle (window name) and the ButtonName to click.
Call this into a timer event that waits for a window to pop-up.
Timer timer = new Timer();
timer.Start();
timer.Tick += new EventHandler(timer_Tick);
//when done call timer.Stop();
private void timer_Tick(object sender, EventArgs e)
{
//set your code to clickButton("","")
}
Try it and let me know.

Ok well since there was a new comment I will make this an official answer. The best I came up with is that you can call OverrideResult() on the dialog even though you can't cancel it. It sill flashes the dialog which isn't ideal but it's better than it was... If anyone has a better way I'd love to hear it :)

Related

C# - trigger key down event for active control

I found command System.Windows.Forms.SendKeys.Send() for sending keypress some key. This function work if open external app like a notepad and set focus and I will be see that my Key printed in this text field. How do same but with key down event, System.Windows.Forms.SendKeys.SendDown("A");, for example?
I tried call in Timer this command System.Windows.Forms.SendKeys.Send() but have runtime error associated with very fast taped.
You can't use the SendKeys class for that, unfortunately. You will need to go to a lower level API.
Poking a window with a keydown message
In Windows, keyboard events are sent to windows and controls via the Windows message pump. A piece of code using PostMessage should do the trick:
[DllImport("user32.dll")]
static extern bool PostMessage(IntPtr hWnd, uint Msg, int wParam, int lParam);
const uint WM_KEYDOWN = 0x0100;
void SendKeyDownToProcess(string processName, System.Windows.Forms.Keys key)
{
Process p = Process.GetProcessesByName(processName).FirstOrDefault();
if (p != null)
{
PostMessage(p.MainWindowHandle, WM_KEYDOWN, (int)key, 0);
}
}
Note that the application receiving these events may not do anything with it until a corresponding WM_KEYUP is received. You can get other message constants from here.
Poking a control other than the main window
The above code will send a keydown to the "MainWindowHandle." If you need to send it to something else (e.g. the active control) you will need to call PostMessage with a handle other than p.MainWindowHandle. The question is... how do you get that handle?
This is actually very involved... you will need to temporarily attach your thread to the window's message input and poke it to figure out what the handle is. This can only work if the current thread exists in a Windows Forms application and has an active message loop.
An explanation can be found here, as well as this example:
using System.Runtime.InteropServices;
public partial class FormMain : Form
{
[DllImport("user32.dll")]
static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
static extern IntPtr GetWindowThreadProcessId(IntPtr hWnd, IntPtr ProcessId);
[DllImport("user32.dll")]
static extern IntPtr AttachThreadInput(IntPtr idAttach,
IntPtr idAttachTo, bool fAttach);
[DllImport("user32.dll")]
static extern IntPtr GetFocus();
public FormMain()
{
InitializeComponent();
}
private void timerUpdate_Tick(object sender, EventArgs e)
{
labelHandle.Text = "hWnd: " +
FocusedControlInActiveWindow().ToString();
}
private IntPtr FocusedControlInActiveWindow()
{
IntPtr activeWindowHandle = GetForegroundWindow();
IntPtr activeWindowThread =
GetWindowThreadProcessId(activeWindowHandle, IntPtr.Zero);
IntPtr thisWindowThread = GetWindowThreadProcessId(this.Handle, IntPtr.Zero);
AttachThreadInput(activeWindowThread, thisWindowThread, true);
IntPtr focusedControlHandle = GetFocus();
AttachThreadInput(activeWindowThread, thisWindowThread, false);
return focusedControlHandle;
}
}
The good news-- if SendKeys worked for you, then you might not need to do all this-- SendKeys also sends messages to the main window handle.

How to put background window/process on top

I want to create a software like a virtualkeyboard, you have a AlwaysTop Window and use this to put some data on another process/windows. In this case I will record all data on clipboard and compare if this data is compatible with a pattern (A### is the patern and A123 is compatible with the patern), if yes the application will put it in a listbox and the user can paste it on another process/windows (already open) clicking on item on list.
My question is about how to put this information on the last application/process used, I already started a prototype of code but the line indicated is wrong, on my code it's the currentprocess and need to be the last used before click on my form.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
[DllImport("user32.dll")]
static extern IntPtr SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll")]
internal static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
[DllImport("User32.dll")]
static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
private void button2_Click(object sender, EventArgs e)
{
Process currentProcess = Process.GetCurrentProcess(); //this line is wrong
IntPtr hWnd = currentProcess.MainWindowHandle; //this line is wrong
if (hWnd != IntPtr.Zero)
{
SetForegroundWindow(hWnd);
ShowWindow(hWnd, 9);
SendKeys.Send("A123");
}
}
}
}
I get on simple solution, instead of get the process I just send the combination ALT+TAB and work for all cases that I need. Below the solution if anyone need in the future:
string old_clipboard = Clipboard.GetText();
Clipboard.SetText("A123");
SendKeys.SendWait("%{Tab}");
SendKeys.SendWait("^V");
Thread.Sleep(100);
Clipboard.SetText(old_clipboard);
Ps.: I put one delay because the SendWait works only on caller windows, as the target of ^V is another process it´s don´t work well.
Best regards. =)

Activate a hidden wpf application when trying to run a second instance

I am working on a wpf application where instead of exiting the app when user closes the button I am minimizing it to the tray(similar to google talk).
void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
e.Cancel = true;
this.Hide();
}
What I need is if user forgets that there is an instance of the app and tries to open a new instance I have to shut down the second instance and set my application as the foreground app. If the app is in minimized state (not hidden) I am able to do this. I am using the following code
protected override void OnStartup(StartupEventArgs e)
{
Process currentProcess = Process.GetCurrentProcess();
var runningProcess = (from process in Process.GetProcesses()
where
process.Id != currentProcess.Id &&
process.ProcessName.Equals(
currentProcess.ProcessName,
StringComparison.Ordinal)
select process).FirstOrDefault();
if (runningProcess != null)
{
Application.Current.Shutdown();
ShowWindow(runningProcess.MainWindowHandle, 5);
ShowWindow(runningProcess.MainWindowHandle, 3);
}
}
[DllImport("user32.dll")]
private static extern Boolean ShowWindow(IntPtr hWnd, Int32 nCmdShow);
When the app is minimized it has some unique value for MainWindowHandle. When I hide the app, the MainWindowHandle of runningProcess is showing as 0. I think this is why my application is not opening when it is in hidden state, but don't know how to fix it.
Tell me if I need to post more code or clarify anything. Thank you in advance.
When I hide the app, the MainWindowHandle of runningProcess is showing as 0
You're right. If a process doesn't have a graphical interface associated with it (hidden/ minimized) then the MainWindowHandle value is zero.
As workaround, you could try getting the HANDLE for the hidden window by enumerating all open windows using EnumDesktopWindows function and compare its process id with the hidden/ minimized windows's process id.
Update
The WPF's WIN32 window has a bit different behavior than the standard WIN32 window. It has the class name composed of the word HwndWrapper, the name of AppDomain it was created in, and a unique random Guid (which changes on every launch), e.g., HwndWrapper[WpfApp.exe;;4d426cdc-31cf-4e4c-88c7-ede846ab6d44].
Update 2
When WPF's window is hidden by using the Hide() method, it internally calls UpdateVisibilityProperty(Visibility.Hidden), which in turns set the internal visibility flag for UIElement to false. When we call the Show() method of WPF Window, UpdateVisibilityProperty(Visibility.Visible) is called, and the internal visibility flag for UIElement is toggled.
When we show the WPF window using the ShowWindow(), the UpdateVisibilityProperty() method is not trigerred, thus the internal visibility flag does not get reversed (which causes the window to be displayed with black backround).
By looking at the WPF Window internal implementation, the only way to toggle the internal visiblity flag, without calling the Show() or Hide() method, is by sending a WM_SHOWWINDOW message.
const int GWL_EXSTYLE = (-20);
const uint WS_EX_APPWINDOW = 0x40000;
const uint WM_SHOWWINDOW = 0x0018;
const int SW_PARENTOPENING = 3;
[DllImport("user32.dll")]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
private static extern bool EnumDesktopWindows(IntPtr hDesktop, EnumWindowsProc ewp, int lParam);
[DllImport("user32.dll")]
static extern uint GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);
[DllImport("user32.dll")]
private static extern uint GetWindowTextLength(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern uint GetWindowText(IntPtr hWnd, StringBuilder lpString, uint nMaxCount);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern bool GetClassName(IntPtr hWnd, StringBuilder lpClassName, int nMaxCount);
[DllImport("user32.dll")]
static extern int GetWindowLong(IntPtr hWnd, int nIndex);
delegate bool EnumWindowsProc(IntPtr hWnd, int lParam);
static bool IsApplicationWindow(IntPtr hWnd) {
return (GetWindowLong(hWnd, GWL_EXSTYLE) & WS_EX_APPWINDOW) != 0;
}
static IntPtr GetWindowHandle(int pid, string title) {
var result = IntPtr.Zero;
EnumWindowsProc enumerateHandle = delegate(IntPtr hWnd, int lParam)
{
int id;
GetWindowThreadProcessId(hWnd, out id);
if (pid == id) {
var clsName = new StringBuilder(256);
var hasClass = GetClassName(hWnd, clsName, 256);
if (hasClass) {
var maxLength = (int)GetWindowTextLength(hWnd);
var builder = new StringBuilder(maxLength + 1);
GetWindowText(hWnd, builder, (uint)builder.Capacity);
var text = builder.ToString();
var className = clsName.ToString();
// There could be multiple handle associated with our pid,
// so we return the first handle that satisfy:
// 1) the handle title/ caption matches our window title,
// 2) the window class name starts with HwndWrapper (WPF specific)
// 3) the window has WS_EX_APPWINDOW style
if (title == text && className.StartsWith("HwndWrapper") && IsApplicationWindow(hWnd))
{
result = hWnd;
return false;
}
}
}
return true;
};
EnumDesktopWindows(IntPtr.Zero, enumerateHandle, 0);
return result;
}
Usage Example
...
if (runningProcess.MainWindowHandle == IntPtr.Zero) {
var handle = GetWindowHandle(runningProcess.Id, runningProcess.MainWindowTitle);
if (handle != IntPtr.Zero) {
// show window
ShowWindow(handle, 5);
// send WM_SHOWWINDOW message to toggle the visibility flag
SendMessage(handle, WM_SHOWWINDOW, IntPtr.Zero, new IntPtr(SW_PARENTOPENING));
}
}
...
Thanks IronGeek, This is great. I'm just learning c# and struggled for a while trying to get this to work. Also I cant 'add comment' as insufficient reputation here so hence this post. I'm using WPF .Net 5.0.
I searched around to implement this, so for other newbies they will also need something like the following in their program to receive the message (sorry, not sure what page I copied this from, so many of them (individuals will need to make their own Mainwindow_Loaded event handler).
private void Mainwindow_Loaded_Event(object sender, RoutedEventArgs e)
{
hwndSource = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
hwndSource.AddHook(new HwndSourceHook(WndProc));
}
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == WM_SHOWWINDOW)
{
MessageBox.Show("I recieved WM_SHOWWINDOW");
handled = true;
}
return IntPtr.Zero;
}
The 'bring to front' tip you mentioned was also needed in my case, here is what is needed:
(from Bring Word to Front )
put this in the declarations section:
[DllImport("user32.dll")]
static extern bool SetForegroundWindow(IntPtr hWnd);
and put the following just after the 'SendMessage(handle, WM_SHOWWINDOW, IntPtr.Zero, new IntPtr(SW_PARENTOPENING));' statement:
SetForegroundWindow(handle);
Without this, the activated window just remains hidden behind other windows and it has to be found by manually fishing around in the taskbar.
So now I've finally got this going for a non-hidden window but now need to look at what is needed for a hidden one as that is my real goal.
Following on from my early post, and to quote an earlier comment from IronGeek the issue is ' If a process doesn't have a graphical interface associated with it (hidden/ minimized) then the MainWindowHandle value is zero'. Therefore any attempt to pass a hidden Window's handle is doomed as it doesn't exist.
So I have found a work-around, although it requires the target process to regularly check for the presence of a new message. Therefore this is still not ideal, but it works for me in 2021 (WPF, .Net 5.0) and doesn't need to import the user32.dll. Rather it carries out a makeshift type of Inter Process Communication (IPC) using the MainWindowTitle as a container to send a message passively.
The MainWindowTitle is settable at runtime and is viewable from other processes, therefore it can be used like an inter-process variable. This is my entire solution below, note it needs to be published to a local folder to see how it runs as the point is to run multiple instances.
<Window x:Class="TitleComsTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:TitleComsTest"
mc:Ignorable="d"
Name="TitleComsTest" Title="TitleComsTest" Height="400" Width="600"
WindowStartupLocation = "CenterScreen" Closing="TitleComsTest_Closing" Visibility="Hidden">
<Grid>
<TextBox Name ="TextBox1" HorizontalAlignment="Center" VerticalAlignment="Center" Height="230" Width="460"/>
<Button Name="QuitButton" Content=" Really Quit " HorizontalAlignment="Center" Margin="0,0,0,30" VerticalAlignment="Bottom" Click="QuitButton_Click"/>
</Grid>
The code behind:
using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Threading;
namespace TitleComsTest
{
public partial class MainWindow : Window
{
//string for MainWindowTitle when first instance and visible:
const string my_Visible_exe_Name = "TitleComstest";
//string for MainWindowTitle when first instance and Hidden:
const string my_Hidden_exe_Name = "TitleComstest...";
//string for MainWindowTitle when 2nd instance :
const string my_exe_Name_Flag = "TitleComstest, Please-Wait";
bool reallyCloseThisProgram = false;
private DispatcherTimer timer1, timer2;
public MainWindow()
{
InitializeComponent();
//Get an array of processes with the chosen name
Process[] TitleComstests = Process.GetProcessesByName(my_Visible_exe_Name);
if (TitleComstests.Length > 1)
{ //Then this is not the first instance
for (int i = 0; i < TitleComstests.Length; i++)
{
if (TitleComstests[i].MainWindowTitle == my_Visible_exe_Name)
{ //The first instance is visible as the MainWindowTitle has been set to the visible name
Close(); //Quit - nothing to do but close the new instance
}
}
//The first instance is hidden, so set MainWindowTitle so the first instance can see it and react
this.Title = my_exe_Name_Flag;
this.WindowState = WindowState.Minimized; //Minimize the window to avoid having two windows shown at once
this.Visibility = Visibility.Visible; //The second instance needs to be visible (minimized is enough) to be seen
StartTimerQuit(4000); //arbitrary time, needs to be longer than 2000ms which is the checking period - see StartTimerLook(2000);
}
else
{
TextBox1.Text = "This is Multi-instance demo using the 'MainWindowTitle' to send messages\r\nto the first (hidden) instance to wake it up.";
TextBox1.Text += "\r\n\r\nThis demo requires the program be published to a local folder and \r\nnot run in the debugger.";
TextBox1.Text += "\r\n\r\nYou can type here to mark this instance: _____________ \r\n\r\nand then hide me by clicking top right close window 'X'";
TextBox1.Text += "\r\n\r\nOnce closed then start the program again to see the 1st instance pop up.";
TextBox1.Text += "\r\n\r\nFinally use the 'Really Quit' button to end this demo.";
this.Visibility = Visibility.Visible;
}
}
private void StartTimerQuit(Int32 interval) //Timer to Quit setup and start
{
timer1 = new DispatcherTimer(); timer1.Tick += timerQuit_Tick;
timer1.Interval = new TimeSpan(0, 0, 0, 0, interval); timer1.Start();
}
private void timerQuit_Tick(object sender, EventArgs e)
{
reallyCloseThisProgram = true; Close();
}
private void TitleComsTest_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
if (!reallyCloseThisProgram)
{
e.Cancel = true;
this.Title = my_Hidden_exe_Name; //Set the Title text to flag a hidden state
this.Visibility = Visibility.Hidden;
//Start checking every 2 secs at the process names - could be faster but this is a constant background process
StartTimerLook(2000);
}
}
private void StartTimerLook(Int32 interval) //Timer to look for new instances setup and start
{
timer2 = new DispatcherTimer(); timer2.Tick += timerLook_Tick;
timer2.Interval = new TimeSpan(0, 0, 0, 0, interval); timer2.Start();
}
private void timerLook_Tick(object sender, EventArgs e)
{ //Every timer interval check to see if a process is present with the Ttile name flag
Process[] myNameFlagProcesses = Process.GetProcessesByName(my_Visible_exe_Name);
for (int i = 0; i < myNameFlagProcesses.Length; i++)
{
if (myNameFlagProcesses[i].MainWindowTitle == my_exe_Name_Flag) //If name flag is seen ...
{ //... then wake up
TextBox1.Text += "\r\n Saw the other window";
this.Visibility = Visibility.Visible;
this.Title = my_Visible_exe_Name; //Set the Title text to flag a visible state
this.Show();
this.Activate();
timer2.Stop();
}
}
}
private void QuitButton_Click(object sender, RoutedEventArgs e)
{
reallyCloseThisProgram = true; Close();
}
}
}

Capture Key press globally but suppress only for current app and child windows regardless of focus

I'm running into a weird problem.
I have a WinForms application that opens another program (billing system emulator), sets it as a child window and then disables it. This works fine, the user cannot send any keys to the that child window, and the winforms application does its thing, sending commands to the child window.
However, it's been discovered that pushing the shift or control, even if the winforms application doesn't have focus, causes an error in the billing system emulator as they aren't valid keys. Users have taken to not using the shift or control keys while the winforms app runs but that's obviously not a practical solution.
My attempted solution was:
Global keyboard hook to capture when those keys are pressed.
Overriding OnKeyDown in the winforms application to stop those keys.
That however still doesn't solve the problem of the shift and alt keys being sent to the child window when the winforms app is not in focus. I can stop shift and alt globally while the winforms app is running but I don't think that is valid. So I need to somehow in the global hook stop the keypress for the winforms app and its children but allow globally. Any ideas/thoughts?
This is my code.
I don't think there's a good answer for your scenario... =\
Here's a hack you can try. It will "release" Control/Shift if they are down, then you send your message afterwards:
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true, CallingConvention = CallingConvention.Winapi)]
public static extern void keybd_event(byte bVk, byte bScan, int dwFlags, int extraInfo);
[DllImport("user32.dll")]
static extern short MapVirtualKey(int wCode, int wMapType);
private void button1_Click(object sender, EventArgs e)
{
if ((Control.ModifierKeys & Keys.Shift) == Keys.Shift)
{
keybd_event((int)Keys.ShiftKey, (byte)MapVirtualKey((int)Keys.ShiftKey, 0), 2, 0); // Shift Up
}
if ((Control.ModifierKeys & Keys.Control) == Keys.Control)
{
keybd_event((int)Keys.ControlKey, (byte)MapVirtualKey((int)Keys.ControlKey, 0), 2, 0); // Control Up
}
// ... now try sending your message ...
}
This obviously isn't foolproof.
I took a look at the only constructor of the globalKeyboardHook and looks like it is designed only for global hook. You can add another overload to hook into the current running module only like this:
class globalKeyboardHook {
[DllImport("kernel32")]
private static extern int GetCurrentThreadId();
[DllImport("user32")]
private static extern IntPtr SetWindowsHookEx(int hookType, KeyBoardProc proc, IntPtr moduleHandle, int threadId);
[DllImport("user32")]
private static extern int CallNextHookEx(IntPtr hHook, int nCode, IntPtr wParam, IntPtr lParam);
public globalKeyboardHook(bool currentModuleOnly){
if(currentModuleOnly){
proc = KeyBoardCallback;
//WH_KEYBOARD = 0x2
hhook = SetWindowsHookEx(2, proc, IntPtr.Zero, GetCurrentThreadId());
} else hook();
}
public delegate int KeyBoardProc(int nCode, IntPtr wParam, IntPtr lParam);
public int KeyBoardCallback(int nCode, IntPtr wParam, IntPtr lParam) {
if (nCode >= 0) {
Keys key = (Keys)wParam;
var lp = lParam.ToInt64();
//your own handling with the key
if ((lp >> 31) == 0)//Key down
{
//your own code ...
} else { //Key up
//your own code ...
}
}
return CallNextHookEx(hHook, nCode, wParam, lParam);
}
KeyBoardProc proc;
//other code ...
}
//then use the new overload constructor instead of the parameterless constructor:
globalHook = new globalKeyboardHook(true);
NOTE: You can implement your own KeyDown and KeyUp event based on what I posted above (the comment your own code ...). After some searching I understand that the WH_KEYBOARD_LL supports global hook only while the WH_KEYBOARD is for thread hook only. That should be what you want instead of the WH_KEYBOARD_LL.
BTW, I doubt that the IMessageFilter which can be registered/added by your application can be used in this case. It also supports a PreFilterMessage method helping you to intercept any key and mouse messages at the application-level. You should try searching on that, it's easy to follow.

C# WebBrowser Control FileUpload Dialog Not Closing all the Time

I am using a WebBrowser control for some automated testing. The problem is that occasionally - not all the time - when I am testing uploading images, the file upload dialog box does not close and the the program just "hangs" and waits for manual input, which defeats the purpose of the whole automated process. What I want to do is to "force" a close of the dialog box, but have been unable to figure this out. Any help or direction would be much appreciated.
The thing to realize is that this code works some of the time, but NOT all of the time. I need help figuring out how to make this code work ALL of the time.
Here is the code:
async Task PopulateInputFile(System.Windows.Forms.HtmlElement file, string fname)
{
file.Focus();
// delay the execution of SendKey 500ms to let the Choose File dialog show up
var sendKeyTask = Task.Delay(5000).ContinueWith((_) =>
{
// this gets executed when the dialog is visible
//SendKeys.Send(fname + "{ENTER}");
//PressKey(Keys.Space, false);
SendKeys.SendWait(fname);
PressKey(Keys.Enter, false);
}, TaskScheduler.FromCurrentSynchronizationContext());
file.InvokeMember("Click"); // this shows up the dialog
await sendKeyTask;
// delay continuation 500ms to let the Choose File dialog hide
await Task.Delay(5000);
}
async Task Populate(string fname)
{
var elements = webBrowser.Document.GetElementsByTagName("input");
foreach (System.Windows.Forms.HtmlElement file in elements)
{
if (file.GetAttribute("name") == "file")
{
this.Activate();
this.BringToFront();
file.Focus();
await PopulateInputFile(file, fname);
file.RemoveFocus();
}
}
}
Ok, so here is the solution. You have to use the WIN API to close the window. I found the class name of the "Choose File to Upload" dialog by using SPY++, which turns out to be: #32770.
[DllImport("user32.dll")]
public static extern int FindWindow(string lpClassName,string lpWindowName);
[DllImport("user32.dll")]
public static extern int SendMessage(int hWnd, uint Msg, int wParam, int lParam);
public const int WM_SYSCOMMAND = 0x0112;
public const int SC_CLOSE = 0xF060;
int iHandle = FindWindow("#32770", "Choose File to Upload");
if (iHandle > 0)
{
// close the window using API
SendMessage(iHandle, WM_SYSCOMMAND, SC_CLOSE, 0);
}
Not really an answer, but it may turn into an answer later. Are use sure the focus is inside the IE "Choose File to Upload" dialog, when you do SendKeys? Use the following to verify that, put the code from below Task.Delay(4000) into your ContinueWith and check the output from Debug.Print.
static class Win32
{
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int GetWindowText(IntPtr hWnd, System.Text.StringBuilder lpString, int nMaxCount);
[DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();
}
private async void Form1_Load(object sender, EventArgs ev)
{
await Task.Delay(4000);
var currentWindow = new System.Text.StringBuilder(1024);
Win32.GetWindowText(Win32.GetForegroundWindow(), currentWindow, currentWindow.Capacity);
Debug.Print("Currently focused window: \"{0}\"", currentWindow);
}

Categories