helper method checking if window is open on another thread - c#

I have found this solution for checking if a window is open:
How do I know if a WPF window is opened
It's throwing an error back at me since my wpf window is on another thread. Is there a way to still use it?
Solution:
public static bool IsWindowOpen<T>(string name = "") where T : Window
{
return string.IsNullOrEmpty(name)
? Application.Current.Windows.OfType<T>().Any()
: Application.Current.Windows.OfType<T>().Any(w => w.Name.Equals(name));
}
if (Helpers.IsWindowOpen<Window>("MyWindowName"))
{
// MyWindowName is open
}
if (Helpers.IsWindowOpen<MyCustomWindowType>())
{
// There is a MyCustomWindowType window open
}
if (Helpers.IsWindowOpen<MyCustomWindowType>("CustomWindowName"))
{
// There is a MyCustomWindowType window named CustomWindowName open
}

I have created a sample application solving your problem after spending entire day.
Solution can be downloaded here
What it does :
Click button to create window on new thread. A new window is created for you on new thread. The moment this new window is created, this button in your mainwindow is disabled. When you close your new window, creation button in your mainwindow is enabled again.
If it doesn't fit your needs, tell your requirements, I will improve it. Same can be done using pure Win32 functions too without using our event bridge class. I am working on it. And I will post win32 version soon.
I am creating NewWindow on a separate thread. If you close MainWindow, NewWindow still runs as it is on new thread.
I am keeping it completely separate as no instance is used in MainWindow to point to NewWindow. To solve this issue I am using a Win32 handle.
For NewWindow to send notifications to MainWindow, I am using a static class WindowNotifier with static events. This class acts as the bridge between the two. In NewWindow Closing/Closed/Loaded events are used to fire events.
MainWindow handle various events of this static class to remain updated about NewWindow.
Win32 functions used :
[DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
public static extern bool IsWindowVisible(IntPtr hWnd);
ThreadCreator.cs
public static class ThreadCreator
{
private static NewWindow W;
public static void CreateWindow()
{
Thread t = new Thread(ThreadProc);
t.SetApartmentState(ApartmentState.STA);
t.Start();
}
private static void ThreadProc(object obj)
{
W = new NewWindow();
W.ShowDialog();
}
}
WindowNotifier.cs
public static class WindowNotifier
{
public static event CreatedDelegateCallback IamCreatedEvent;
public delegate void CreatedDelegateCallback(IntPtr handle);
public static event ClosingDelegateCallback IamClosingEvent;
public delegate void ClosingDelegateCallback (IntPtr handle);
public static event ClosedDelegateCallback IamClosedEvent;
public delegate void ClosedDelegateCallback(IntPtr handle);
public static void OnIamCreated(IntPtr handle)
{
if (IamCreatedEvent != null)
IamCreatedEvent(handle);
}
public static void OnIamClosing(IntPtr handle)
{
if (IamClosingEvent != null)
IamClosingEvent(handle);
}
public static void OnIamClosed(IntPtr handle)
{
if (IamClosedEvent != null)
IamClosedEvent(handle);
}
}
MainWindow.xaml.cs
...
void WindowNotifier_IamCreatedEvent(IntPtr handle)
{
HandleOfWindowOnNewThread = handle;
Debug.WriteLine(string.Format("I am created : {0}", handle));
btnCreateNewWindow.Dispatcher.Invoke(() => btnCreateNewWindow.IsEnabled = false);
}
void WindowNotifier_IamClosedEvent(IntPtr handle)
{
if (HandleOfWindowOnNewThread == handle)
HandleOfWindowOnNewThread = IntPtr.Zero;
Debug.WriteLine(string.Format("I am closed : {0}", handle));
btnCreateNewWindow.Dispatcher.Invoke(() => btnCreateNewWindow.IsEnabled = true);
}
...
NewWindow.xaml.cs
...
private void Window_Closed(object sender, EventArgs e)
{
WindowNotifier.OnIamClosed(Handle);
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
WindowNotifier.OnIamClosing(Handle);
}
// To get correct handle we need to ensure Window is fully created and active
private void Window_Loaded(object sender, RoutedEventArgs e)
{
_handle = GetForegroundWindow();
WindowNotifier.OnIamCreated(Handle);
}
...

Dispatcher does not help here because when window is created on a different thread, it's not contained in the Application.Windows collection, but in a collection which for some reason is not exposed (called NonAppWindowsInternal). Shortly, there is no official way to do that. Of course you can use reflection on your own risk.
But if your window is on UI thread and you just want to call the function from another thread, then you can use something like this
Application.Current.Dispatcher.Invoke(() => IsWindowOpen<...>(...))
or better change the helper method to be
public static bool IsWindowOpen<T>(string name = "") where T : Window
{
return Application.Current.Dispatcher.Invoke(() => string.IsNullOrEmpty(name)
? Application.Current.Windows.OfType<T>().Any()
: Application.Current.Windows.OfType<T>().Any(w => w.Name.Equals(name)));
}
EDIT Here is something that works currently, but may change in the future, so as mentioned above, use it on your own risk
public static class WindowUtils
{
public static bool IsWindowOpen<T>(string name = "") where T : Window
{
return FindWindow<T>(name) != null;
}
public static T FindWindow<T>(string name = "") where T : Window
{
return FindWindow<T>(WindowsInternal, name) ?? FindWindow<T>(NonAppWindowsInternal, name);
}
private static T FindWindow<T>(Func<Application, WindowCollection> windowListAccessor, string name = "") where T : Window
{
bool matchName = !string.IsNullOrEmpty(name);
var windowList = windowListAccessor(Application.Current);
for (int i = windowList.Count - 1; i >= 0; i--)
{
var window = windowList[i] as T;
if (window != null && (!matchName || window.Name == name)) return window;
}
return null;
}
private static readonly Func<Application, WindowCollection> WindowsInternal = GetWindowCollectionAccessor("WindowsInternal");
private static readonly Func<Application, WindowCollection> NonAppWindowsInternal = GetWindowCollectionAccessor("NonAppWindowsInternal");
private static Func<Application, WindowCollection> GetWindowCollectionAccessor(string propertyName)
{
var property = typeof(Application).GetProperty(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
return (Func<Application, WindowCollection>)Delegate.CreateDelegate(
typeof(Func<Application, WindowCollection>), property.GetMethod);
}
}

If you return the window from your IsWindowOpen method. U can use the Invoke or BeginInvoke on the window, to dispatch the work on the thread where the window was created on.

Related

How to get the current Title of a Window parented to my Form?

I've got a WinForm app that parents Windows of other processes (ex. Google Chrome). I'm using the following code to parent a Windows to my Form, using the Handle returned by [Process].MainWindowHandle.
I'm trying to find the MainWindowTitle of all the Windows that are parented to my Form, so I can display their name on a Label.
When the Window of a WebBrowser is embedded, the Title will change when a different Web Page is selected, switching Tabs.
The code I have for starting the program does work as it should:
ProcessStartInfo ps1 = new ProcessStartInfo(#"C:/Users/Jacob/AppData/Roaming/Spotify/Spotify.exe");
ps1.WindowStyle = ProcessWindowStyle.Minimized;
Process p1 = Process.Start(ps1);
// Allow the process to open it's window
Thread.Sleep(1000);
appWin1 = p1.MainWindowHandle;
spotify = p1;
// Put it into this form
SetParent(appWin1, this.Handle);
// Move the window to overlay it on this window
MoveWindow(appWin1, 0, 70, this.Width / 2, this.Height/2, true);
Since you're willing to use UIAutomation to handle this parenting affair, I propose to handle this using Automation methods entirely. Almost, SetParent still required :).
The class shown here uses the WindowPatter.WindowOpenedEvent to detect and notify when a new Window is opened in the System.
It can be any Window, Console included (still a Window).
This method allows to identify a Window when it's handle is already created, so you don't need an arbitrary time-out or try to use Process.WaitForInputIdle(), which may not have the desired result.
You can pass a list of names of processes to the ProcessNames Property of the class: when any Window that belongs to one of these Processes is opened, UIAutomation detects it and a public event is raised. It notifies the subscribers that one of the Processes in the list has opened a Window, which is the ProcessId of the Owner and the handle of the Windows.
These values are passed in a custom EventArgs class, ProcessStartedArgs when the ProcessStarted event is raised.
Since the Automation Event is raised in a Thread other than the UI Thread, the class captures the SynchronizationContext where the class is created (the UI Thread, since you're probably creating this class in a Form) and marshals the event to that Thread, calling its Post() method passing a SendOrPostCallback delegate.
This way, you can safely pass the Handle of your Form and the Handle of the Window to SetParent().
To retrieve the current Title (Caption) of the parented Window, pass the Handle previously returned in the event argument to the GetCurrentWindowTitle() method. If the Window contains tabbed child Windows, as a Web Browser, this method will return the Title related to the Tab currently selected.
▶ The class is disposable and you need to call its public Dispose() method. This removes the Automation event handler and also all the events in the Invocation List of the public event you have subscribed to. This way, you can use a Lambda to subscribe to the event.
Use a Field to store an instance of this class. Create the instance when needed, passing a List of Process Names you're interested in.
Subscribe to the ProcessStarted event.
When on of these Processes opens a new Window, you'll get a notification and the parenting thing can be performed:
public partial class SomeForm : Form
{
private WindowWatcher watcher = null;
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
watcher = new WindowWatcher();
watcher.ProcessNames.AddRange(new[] { "msedge", "firefox", "chrome", "notepad" });
watcher.ProcessStarted += (o, ev) => {
SetParent(ev.WindowHandle, this.Handle);
MoveWindow(ev.WindowHandle, 0, 70, this.Width / 2, this.Height / 2, true);
string windowTitle = WindowWatcher.GetCurrentWindowTitle(ev.WindowHandle);
};
}
protected override void OnFormClosed(FormClosedEventArgs e)
{
watcher.Dispose();
base.OnFormClosed(e);
}
}
WindowWatcher class:
NOTE: UI Automation assemblies are part of Windows Presentation Framework.
When one of these assemblies is referenced in a WinForms application, the WinForms application will become DpiAware (SystemAware), if it's not already DpiAware.
This can have an impact on the Layout of one or more Forms that is not designed to handle Dpi Awareness changes and notifications.
Requires a Project Reference to:
UIAutomationClient
UIAutomationTypes
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Automation;
public class WindowWatcher : IDisposable
{
private SynchronizationContext context = null;
private readonly SendOrPostCallback eventCallback;
public event EventHandler<ProcessStartedArgs> ProcessStarted;
private AutomationElement uiaWindow;
private AutomationEventHandler WindowOpenedHandler;
public WindowWatcher() {
context = SynchronizationContext.Current;
eventCallback = new SendOrPostCallback(EventHandlersInvoker);
InitializeWatcher();
}
public List<string> ProcessNames { get; set; } = new List<string>();
private void InitializeWatcher()
{
Automation.AddAutomationEventHandler(
WindowPattern.WindowOpenedEvent, AutomationElement.RootElement,
TreeScope.Children, WindowOpenedHandler = new AutomationEventHandler(OnWindowOpenedEvent));
}
public static string GetCurrentWindowTitle(IntPtr handle)
{
if (handle == IntPtr.Zero) return string.Empty;
var element = AutomationElement.FromHandle(handle);
if (element != null) {
return element.Current.Name;
}
return string.Empty;
}
private void OnWindowOpenedEvent(object uiaElement, AutomationEventArgs e)
{
uiaWindow = uiaElement as AutomationElement;
if (uiaWindow == null || uiaWindow.Current.ProcessId == Process.GetCurrentProcess().Id) return;
var window = uiaWindow.Current;
var procName = string.Empty;
using (var proc = Process.GetProcessById(window.ProcessId)) {
if (proc == null) throw new InvalidOperationException("Invalid Process");
procName = proc.ProcessName;
}
if (ProcessNames.IndexOf(procName) >= 0) {
var args = new ProcessStartedArgs(procName, window.ProcessId, (IntPtr)window.NativeWindowHandle);
context.Post(eventCallback, args);
}
}
public class ProcessStartedArgs : EventArgs
{
public ProcessStartedArgs(string procName, int procId, IntPtr windowHandle)
{
ProcessName = procName;
ProcessId = procId;
WindowHandle = windowHandle;
}
public string ProcessName { get; }
public int ProcessId { get; }
public IntPtr WindowHandle { get; }
}
private void EventHandlersInvoker(object state)
{
if (!(state is ProcessStartedArgs args)) return;
ProcessStarted?.Invoke(this, args);
}
~WindowWatcher() { Dispose(false); }
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected void Dispose(bool disposing)
{
if (uiaWindow != null && WindowOpenedHandler != null) {
Automation.RemoveAutomationEventHandler(
WindowPattern.WindowOpenedEvent, uiaWindow, WindowOpenedHandler);
}
if (ProcessStarted != null) {
var invList = ProcessStarted.GetInvocationList();
if (invList != null && invList.Length > 0) {
for (int i = invList.Length - 1; i >= 0; i--) {
ProcessStarted -= (EventHandler<ProcessStartedArgs>)invList[i];
}
}
}
}
}

SendKeys not working with VisualBoy Advance

I am attempting to simulate keyboard input to programmatically play a game in VisualBoy Advance. There is no response from VisualBoy Advance when SendKeys.SendWait() is used.
private const string VBA_PROCESS_NAME = "VBA-rr-svn480";
public void Up()
{
PressButton("{UP}");
}
public void Down()
{
PressButton("{DOWN}");
}
public void Left()
{
PressButton("{LEFT}");
}
public void Right()
{
PressButton("{RIGHT}");
}
public void A()
{
PressButton("z");
}
public void B()
{
PressButton("x");
}
public void LShoulder()
{
PressButton("a");
}
public void RShoulder()
{
PressButton("s");
}
public void Start()
{
PressButton("~");
}
public void Select()
{
PressButton("+");
}
private void PressButton(string Button)
{
var VBAProcess = GetVBAProcess();
// Verify that VBA is a running process.
if (VBAProcess == null)
throw new Exception("Visual Boy Advance could not be found.");
IntPtr VBAHandle = VBAProcess.MainWindowHandle;
// Make sure that VBA is running and that we have a valid handle.
if (VBAHandle == IntPtr.Zero)
throw new Exception("Visual Boy Advance is not running.");
// Make VBA the foreground application and send it the button press.
SetForegroundWindow(VBAHandle);
SendKeys.SendWait(Button);
}
// Activate an application window.
[DllImport("USER32.DLL")]
public static extern bool SetForegroundWindow(IntPtr hWnd);
private Process GetVBAProcess()
{
return Process.GetProcessesByName(VBA_PROCESS_NAME).FirstOrDefault();
}
If I swap out the process name for a different process (such as notepad++) the key presses work perfectly. This leads me to believe that I must have the wrong process or window for VisualBoy Advance, but I haven't found one that looks correct when I grab all processes and look through them.

returning an object from a class in C# (wpf)

I am trying to call a class called ThreadCreator() and then one of its methods called CreateWindow() and have it return a new window it created so that I can use that object somewhere else to invoke a method on it.
Here's the breakdown. My thread creator class that pops a new wpf window on a new thread:
using System;
using System.Threading;
using System.Diagnostics;
using System.IO;
using System.ComponentModel;
using System.Windows;
namespace Windamow
{
public static class ThreadCreator
{
private static NewWindow W;
public static NewWindow CreateWindow()
{
string appName = "";
try
{
appName = Path.GetFileName(System.Reflection.Assembly.GetEntryAssembly().Location);
const string IE_EMULATION = #"Software\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_BROWSER_EMULATION";
using (var fbeKey = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(IE_EMULATION, true))
{
fbeKey.SetValue(appName, 9000, Microsoft.Win32.RegistryValueKind.DWord);
}
}
catch (Exception ex)
{
MessageBox.Show(appName + "\n" + ex.ToString(), "Unexpected error setting browser mode!");
}
Thread t = new Thread(ThreadProc);
t.SetApartmentState(ApartmentState.STA);
t.Start();
return W;
}
private static void ThreadProc(object obj)
{
W = new NewWindow();
W.ShowDialog();
}
}
}
New Window.xaml.cs looks like this:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Runtime.InteropServices;
using System.Diagnostics;
namespace Windamow
{
/// <summary>
/// Interaction logic for NewWindow.xaml
/// </summary>
public partial class NewWindow : Window
{
string _message;
public string Message
{
get { return _message; }
set
{
_message = value;
this.Dispatcher.Invoke(() => { this.webBrowser.NavigateToString(_message); });
}
}
[DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();
IntPtr _handle;
public IntPtr Handle { get { return _handle; } }
int _pid;
public int PID { get { return _pid; } }
public NewWindow()
{
InitializeComponent();
}
private void Window_Closed(object sender, EventArgs e)
{
WindowNotifier.OnIamClosed(Handle);
}
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
WindowNotifier.OnIamClosing(Handle);
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
_pid = Process.GetCurrentProcess().Id;
_handle = GetForegroundWindow();
WindowNotifier.OnIamCreated(Handle);
}
}
}
What I am trying to do with the window is invoke a NavigateToString() method every time a method called CheckIfWindowOpen() is run. It will basically check if window is open and either open it and execute NavigateToString() or it will just execute NavigateToString() with possibly new inputs (refresh).
Here's how I am calling it:
namespace Windamow
{
public class Win32F
{
[DllImport("user32.dll")]
public static extern bool IsWindowVisible(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();
}
public class DynamoDataVizNodes
{
[DllImport("user32.dll", SetLastError = true)]
public static extern int GetLastError();
// test html string
public static string HTMLString = "<html></html>";
IntPtr HandleOfWindowOnNewThread;
void WindowNotifier_IamCreatedEvent(IntPtr handle)
{
HandleOfWindowOnNewThread = handle;
Debug.WriteLine(string.Format("I am created : {0}", handle));
}
void WindowNotifier_IamClosedEvent(IntPtr handle)
{
if (HandleOfWindowOnNewThread == handle)
HandleOfWindowOnNewThread = IntPtr.Zero;
Debug.WriteLine(string.Format("I am closed : {0}", handle));
}
void WindowNotifier_IamClosingEvent(IntPtr handle)
{
Debug.WriteLine(string.Format("I am closing : {0}", handle));
}
public static NewWindow window;
public void CheckIfWindowOpen(string SourceString)
{
WindowNotifier.IamClosingEvent += WindowNotifier_IamClosingEvent;
WindowNotifier.IamClosedEvent += WindowNotifier_IamClosedEvent;
WindowNotifier.IamCreatedEvent += WindowNotifier_IamCreatedEvent;
if (HandleOfWindowOnNewThread == IntPtr.Zero)
{
// create new window and set browser
window = ThreadCreator.CreateWindow();
window.Dispatcher.Invoke(() => { window.webBrowser.NavigateToString(SourceString); });
}
else
{
// only set browser
window.Dispatcher.Invoke(() => { window.webBrowser.NavigateToString(SourceString); });
}
}
}
}
The error happens in the CheckIfWindowOpen() method when I am trying to return a NewWindow object to then invoke a NavigateToString() method on it. It basically returns null. Is there a way to make ThreadCreator.CreateWindow() return the created window?
Thanks!
Solution1 : Without storing reference to NewWindow in MainWindow. Instead raising RefreshBrowser event from MainWindow.
Dropbox link
Solution2 : Storing reference to NewWindow in MainWindow :
New solution changed to your requirements :
Dropbox link
Thread creation routine doesn't wait for ThreadProcedure to run, so return value will be null always. But you can use this value in say some other event handler. Because once this routine finishes, variable window will contain valid value.
However this is not proper way to do what you are trying to do.
There is a cleaner way in today's event driven programming world. Remember, controls/windows fire events and we access the sender and get additional info using corresponding eventargs. Eg;
private void Window_Loaded(object sender, RoutedEventArgs e)
Following same pattern as above, we modify our delegates like below :
public static class WindowNotifier2
{
public static event CreatedDelegateCallback IamCreatedEvent;
public delegate void CreatedDelegateCallback(object sender, WindowNotifierEventArgs args);
public static event ClosingDelegateCallback IamClosingEvent;
public delegate void ClosingDelegateCallback(object sender, WindowNotifierEventArgs args);
public static event ClosedDelegateCallback IamClosedEvent;
public delegate void ClosedDelegateCallback(object sender, WindowNotifierEventArgs args);
public static void OnIamCreated(object sender, WindowNotifierEventArgs args)
{
if (IamCreatedEvent != null)
IamCreatedEvent(sender, args);
}
public static void OnIamClosing(object sender, WindowNotifierEventArgs args)
{
if (IamClosingEvent != null)
IamClosingEvent(sender, args);
}
public static void OnIamClosed(object sender, WindowNotifierEventArgs args)
{
if (IamClosedEvent != null)
IamClosedEvent(sender, args);
}
}
public class WindowNotifierEventArgs : EventArgs
{
public IntPtr WindowHandle { get; set; }
}
...
NewWindow.xaml.cs
private void Window_Loaded(object sender, RoutedEventArgs e)
{
_pid = Process.GetCurrentProcess().Id;
_handle = GetForegroundWindow();
WindowNotifier2.OnIamCreated(this, new WindowNotifierEventArgs() { WindowHandle = _handle });
}
MainWindow.xaml.cs
private void btnCheckNewWindow_Click(object sender, RoutedEventArgs e)
{
if (HandleOfWindowOnNewThread == IntPtr.Zero)
{
WindowNotifier2.IamCreatedEvent += (object source, WindowNotifierEventArgs args) =>
{
_newWindow = (NewWindow)source;
HandleOfWindowOnNewThread = args.WindowHandle;
Debug.WriteLine(string.Format("I am created : {0}", args.WindowHandle));
_newWindow.tbMsgFromMainWindow.Dispatcher.InvokeAsync(() => {
_newWindow.tbMsgFromMainWindow.Text = "I created you ! I am your creator.";
});
};
ThreadCreator.CreateWindow();
}
else
{
_newWindow.tbMsgFromMainWindow.Dispatcher.InvokeAsync(() => {
_newWindow.tbMsgFromMainWindow.Text = "I can see you are already running !";
});
}
}
W is only set on the another thread and when you return W it most like is still null. Besides that why do you try to run a new thread as dispatcher; have a look at the dispatcher run method.

In WPF, how to set a Window Owner of a Window build on another thread (another Dispatcher)

I got the following exception:
InvalidOperationException : The calling thread cannot access this object because a different thread owns it.
when I try to set the Owner of a window that is build on another thread than the Owner.
I know that I can only update UI object from the proper thread, but why I can't just set the owner if it come from another thread? Can I do it on another way ? I want to make the progress window the only one which can have input entries.
This is the portion of code where bug occurs:
public partial class DlgProgress : Window
{
// ******************************************************************
private readonly DlgProgressModel _dlgProgressModel;
// ******************************************************************
public static DlgProgress CreateProgressBar(Window owner, DlgProgressModel dlgProgressModel)
{
DlgProgress dlgProgressWithProgressStatus = null;
var listDlgProgressWithProgressStatus = new List<DlgProgress>();
var manualResetEvent = new ManualResetEvent(false);
var workerThread = new ThreadEx(() => StartDlgProgress(owner, dlgProgressModel, manualResetEvent, listDlgProgressWithProgressStatus));
workerThread.Thread.SetApartmentState(ApartmentState.STA);
workerThread.Start();
manualResetEvent.WaitOne(10000);
if (listDlgProgressWithProgressStatus.Count > 0)
{
dlgProgressWithProgressStatus = listDlgProgressWithProgressStatus[0];
}
return dlgProgressWithProgressStatus;
}
// ******************************************************************
private static void StartDlgProgress(Window owner, DlgProgressModel progressModel, ManualResetEvent manualResetEvent, List<DlgProgress> listDlgProgressWithProgressStatus)
{
DlgProgress dlgProgress = new DlgProgress(owner, progressModel);
listDlgProgressWithProgressStatus.Add(dlgProgress);
dlgProgress.ShowDialog();
manualResetEvent.Set();
}
// ******************************************************************
private DlgProgress(Window owner, DlgProgressModel dlgProgressModel)
{
if (owner == null)
{
throw new ArgumentNullException("Owner cannot be null");
}
InitializeComponent();
this.Owner = owner; // Can't another threads owns it exception
Answer above was correct. But I will try to summarize:
[DllImport("user32.dll")]
static extern int SetWindowLong(IntPtr hwnd, int index, int newStyle);
public static void SetOwnerWindowMultithread(IntPtr windowHandleOwned, IntPtr intPtrOwner)
{
if (windowHandleOwned != IntPtr.Zero && intPtrOwner != IntPtr.Zero)
{
SetWindowLong(windowHandleOwned, GWL_HWNDPARENT, intPtrOwner.ToInt32());
}
}
Code to get WPF handler:
public static IntPtr GetHandler(Window window)
{
var interop = new WindowInteropHelper(window);
return interop.Handle;
}
Note that window should be initialized before set owner call! (can be set in window.Loaded or window.SourceInitialized event)
var handler = User32.GetHandler(ownerForm);
var thread = new Thread(() =>
{
var window = new DialogHost();
popupKeyboardForm.Show();
SetOwnerWindowMultithread(GetHandler(popupKeyboardForm), handler);
Dispatcher.Run();
});
thread.IsBackground = true;
thread.Start();
Also SetParent can be used. Than you dont need to convert handlers:
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
Note that parent and owner have different meanings.
Win32 window Owner vs window Parent?
I made it according based mainly on Hans Passant suggestion.
Important, I suspect that this code should only work on 32 bits because I use "ToInt32" on IntPtr.
This is the code:
WindowHelper function:
// ******************************************************************
private const int GWL_HWNDPARENT = -8; // Owner --> not the parent
[DllImport("user32.dll")]
static extern int SetWindowLong(IntPtr hwnd, int index, int newStyle);
// ******************************************************************
public static void SetOwnerWindow(Window owned, IntPtr intPtrOwner)
{
try
{
IntPtr windowHandleOwned = new WindowInteropHelper(owned).Handle;
if (windowHandleOwned != IntPtr.Zero && intPtrOwner != IntPtr.Zero)
{
SetWindowLong(windowHandleOwned, GWL_HWNDPARENT, intPtrOwner.ToInt32());
}
}
catch (Exception ex)
{
Debug.Print(ex.Message);
}
}
// ******************************************************************
Calling function:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Threading;
using HQ.Util.General.Threading;
using HQ.Util.Unmanaged;
namespace HQ.Wpf.Util.Dialog
{
/// <summary>
/// Interaction logic for DlgProgressWithProgressStatus.xaml
/// </summary>
public partial class DlgProgress : Window
{
// ******************************************************************
private readonly DlgProgressModel _dlgProgressModel;
// ******************************************************************
public static DlgProgress CreateProgressBar(Window owner, DlgProgressModel dlgProgressModel)
{
DlgProgress dlgProgressWithProgressStatus = null;
var listDlgProgressWithProgressStatus = new List<DlgProgress>();
var resetEvent = new ManualResetEvent(false);
IntPtr windowHandleOwner = new WindowInteropHelper(owner).Handle;
dlgProgressModel.Owner = owner;
dlgProgressModel.IntPtrOwner = windowHandleOwner;
var workerThread = new ThreadEx(() => StartDlgProgress(dlgProgressModel, resetEvent, listDlgProgressWithProgressStatus));
workerThread.Thread.SetApartmentState(ApartmentState.STA);
workerThread.Start();
resetEvent.WaitOne(10000);
if (listDlgProgressWithProgressStatus.Count > 0)
{
dlgProgressWithProgressStatus = listDlgProgressWithProgressStatus[0];
}
return dlgProgressWithProgressStatus;
}
// ******************************************************************
private static void StartDlgProgress(DlgProgressModel progressModel, ManualResetEvent resetEvent, List<DlgProgress> listDlgProgressWithProgressStatus)
{
DlgProgress dlgProgress = new DlgProgress(progressModel);
listDlgProgressWithProgressStatus.Add(dlgProgress);
resetEvent.Set();
dlgProgress.ShowDialog();
}
// ******************************************************************
private DlgProgress(DlgProgressModel dlgProgressModel)
{
if (dlgProgressModel.Owner == null)
{
throw new ArgumentNullException("Owner cannot be null");
}
InitializeComponent();
// this.Owner = owner; // Can't another threads owns it exception
if (dlgProgressModel == null)
{
throw new ArgumentNullException("dlgProgressModel");
}
_dlgProgressModel = dlgProgressModel;
_dlgProgressModel.Dispatcher = this.Dispatcher;
_dlgProgressModel.PropertyChanged += _dlgProgressModel_PropertyChanged;
DataContext = _dlgProgressModel;
}
// ******************************************************************
// Should be call as a modal dialog
private new void Show()
{
throw new Exception("Should only be used as modal dialog");
}
// ******************************************************************
void _dlgProgressModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
// if (e.PropertyName == "IsJobCanceled" || e.PropertyName == "IsJobCompleted" || e.PropertyName == "IsProgressCompleted")
// Faster if we don't check strings and check condition directly
{
if (_dlgProgressModel.HaveConditionToClose())
{
if (_dlgProgressModel.IsJobCanceled == true)
{
SetDialogResult(false);
}
else
{
SetDialogResult(true);
}
}
}
}
// ******************************************************************
private void SetDialogResult(bool result)
{
this._dlgProgressModel.Dispatcher.BeginInvoke(new Action(() =>
{
this.DialogResult = result;
}), DispatcherPriority.Background);
}
// ******************************************************************
private bool _isFirstTimeLoaded = true;
private Timer _timer = null;
// ******************************************************************
private void WindowLoaded(object sender, RoutedEventArgs e)
{
if (_isFirstTimeLoaded)
{
WindowHelper.SetOwnerWindow(this, _dlgProgressModel.IntPtrOwner);
Dispatcher.BeginInvoke(new Action(ExecuteDelayedAfterWindowDisplayed), DispatcherPriority.Background);
_isFirstTimeLoaded = false;
if (_dlgProgressModel.FuncGetProgressPercentageValue != null)
{
TimerCallback(null);
_timer = new Timer(TimerCallback, null, _dlgProgressModel.MilliSecDelayBetweenCall, _dlgProgressModel.MilliSecDelayBetweenCall);
}
}
}
// ******************************************************************
private void TimerCallback(Object state)
{
Dispatcher.BeginInvoke(new Action(() =>
{
_dlgProgressModel.ValueCurrent = _dlgProgressModel.FuncGetProgressPercentageValue();
}));
}
// ******************************************************************
private void ExecuteDelayedAfterWindowDisplayed()
{
if (_dlgProgressModel._actionStarted == false)
{
_dlgProgressModel._actionStarted = true;
Task.Factory.StartNew(ExecuteAction);
}
}
// ******************************************************************
private void ExecuteAction()
{
_dlgProgressModel.ExecuteAction();
_dlgProgressModel._actionTerminated = true;
_dlgProgressModel.IsJobCompleted = true;
}
// ******************************************************************
private void CmdCancel_Click(object sender, RoutedEventArgs e)
{
this._dlgProgressModel.IsJobCanceled = true;
}
// ******************************************************************
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
if (! _dlgProgressModel.HaveConditionToClose())
{
e.Cancel = true;
return;
}
WindowHelper.SetOwnerWindow(this, 0);
this.CmdCancel.IsEnabled = false;
this.CmdCancel.Content = "Canceling...";
this._dlgProgressModel.Dispose();
}
// ******************************************************************
}
}
internal class WindowHelp
{
private const int GWL_HWNDPARENT = -8;
[DllImport("user32.dll")]
private static extern IntPtr SetWindowLong(IntPtr hwnd, int index, int newStyle);
public static void SetOwnerWindow(IntPtr hwndOwned, IntPtr intPtrOwner)
{
try
{
if (hwndOwned != IntPtr.Zero && intPtrOwner != IntPtr.Zero)
{
SetWindowLong(hwndOwned, GWL_HWNDPARENT, intPtrOwner.ToInt32());
}
}
catch { }
}
}
WindowInteropHelper helper = new WindowInteropHelper(owner);
_messageBox.Loaded += (sender, e) =>
{
IntPtr windowHandleOwned = new WindowInteropHelper(_messageBox).Handle;
owner.Dispatcher.Invoke(new Action(() =>
{
WindowHelp.SetOwnerWindow(windowHandleOwned, helper.Handle);
}));
};
A problem this has is that when the application is shutting down and the owned window is still open it will try to execute something that can fail, my guess is that it's trying to close all owned windows.
System.ComponentModel.Win32Exception
HResult=0x80004005
Message=Invalid window handle
Source=WindowsBase
StackTrace:
at MS.Win32.ManagedWndProcTracker.HookUpDefWindowProc(IntPtr hwnd)
at MS.Win32.ManagedWndProcTracker.OnAppDomainProcessExit()
at MS.Win32.ManagedWndProcTracker.ManagedWndProcTrackerShutDownListener.OnShutDown(Object target, Object sender, EventArgs e)
at MS.Internal.ShutDownListener.HandleShutDown(Object sender, EventArgs e)
A disadvantage of giving it its own thread is that you have to keep track of the child window and close it when the main window is closing before the application reaches the later stages of shut down:
private void View_Closing(object sender, CancelEventArgs e)
{
UIGlobal.SelfThreadedDialogs.ForEach(k =>
{
try
{
if (k != null && !k.Dispatcher.HasShutdownStarted)
{
k.Dispatcher.InvokeShutdown();
//k.Dispatcher.Invoke(new Action(() => { k.Close(); }));
}
}
catch { }
});
}
The price of having this kind of "multi-threaded and related" behavior.
Sometimes the tracking and/or the owner's View_Closing code is not needed. Sometimes you only need tracking to keep a reference to the owned windows so that they aren't garbage collected before application shutdown occurs. It depends. See what works for your situation.
It's not about setting the owner. If you want to manipulate a control in WPF from another thread, you'll need to create a delegate and pass this to the dispatcher of the control.
if(Control.Dispatcher.CheckAccess())
{
//The control can be accessed without using the dispatcher.
Control.DoSomething();
}
else{
//The dispatcher of the control needs to be informed
MyDelegate md = new MyDelegate( delegate() { Control.DoSomething(); });
Control.Dispatcher.Invoke(md, null);
}
See this post.

WPF: Close a secondary window when application shutdown without "programmer" intervention

It's quite hard to explain this in the title, if someone would like to change it it's ok.
I have a situation where, in WPF, I create an "hidden" window which is transparent to the programmer.
What I mean is that this window is created in static constructor, hidden and moved outside of the screen and it's width and height are 0. This because I'm using this window to do some interop operations and to allow a sort of handlers for all WndProcs override that someone could require (there is a list of delegates which handles methods that should override WndProc).
In hope that you understand what I've said (it's not easy), my problem is that when I create a WPF project and start it, if I close the Main Window (which is not the one created transparently to the programmer), I want that my application shutdown. However with the code I created this doesn't happen except if I use Application.Current.Shutdown();
Are there any way to fix this without calling that method? I want a transparent way, that other programmers shouldn't even notice (it's a lib, shouldn't change the behaviour of working programs in this way).
Thanks for any suggestion, here you can see some code snippets:
The window created by the lib
public class InteropWindow : Window
{
public HwndSource Source { get; protected set; }
private static InteropWindow _Instance;
static InteropWindow()
{
_WndProcs = new LinkedList<WndProcHandler>();
_Instance = new InteropWindow();
}
private static WindowInteropHelper _InteropHelper;
public static WindowInteropHelper InteropHelper
{
get
{
if (_InteropHelper == null)
{
_InteropHelper = new WindowInteropHelper(_Instance);
_InteropHelper.EnsureHandle();
}
return _InteropHelper;
}
}
public static IntPtr Handle { get { return InteropHelper.Handle; } }
private InteropWindow()
{
Opacity = 0.0;
//We have to "show" the window in order to obtain hwnd to process WndProc messages in WPF
Top = -10;
Left = -10;
Width = 0;
Height = 0;
WindowStyle = WindowStyle.None;
ShowInTaskbar = false;
ShowActivated = false;
Show();
Hide();
}
private static LinkedList<WndProcHandler> _WndProcs;
public static void AddWndProcHandler(WndProcHandler handler)
{
_WndProcs.AddLast(handler);
}
public static void RemoveWndProcHandler(WndProcHandler handler)
{
_WndProcs.Remove(handler);
}
private static IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
IntPtr result = IntPtr.Zero;
foreach (WndProcHandler handler in _WndProcs)
{
IntPtr tmp = handler(hwnd, msg, wParam, lParam, ref handled);
if (tmp != IntPtr.Zero)
{
if (result != IntPtr.Zero)
throw new InvalidOperationException(string.Format("result should be zero if tmp is non-zero:\nresult: {0}\ntmp: {1}", result.ToInt64().ToString(), tmp.ToInt64().ToString()));
result = tmp;
}
}
return result;
}
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
Source = PresentationSource.FromVisual(this) as HwndSource;
Source.AddHook(WndProc);
OnWindowInitialized(null, e);
}
protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
if (Source != null)
Source.RemoveHook(WndProc);
OnWindowClosed(null, e);
}
private static void OnWindowInitialized(object sender, EventArgs e)
{
if (WindowInitialized != null) WindowInitialized(sender, e);
}
private static void OnWindowClosed(object sender, EventArgs e)
{
if (WindowClosed != null) WindowClosed(sender, e);
}
public static event EventHandler WindowInitialized;
public static event EventHandler WindowClosed;
}
A normal window created with wpf (base window created from project)
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ExClipboard.ClipboardUpdate += new RoutedEventHandler(ExClipboard_ClipboardUpdate);
Closed += new EventHandler(MainWindow_Closed);
}
private void MainWindow_Closed(object sender, EventArgs e)
{
//InteropWindow.Dispose();
App.Current.Shutdown(0);
}
}
Update 1:
To answer to your answers, No I would like to avoid any intervetion by programmer using my library, so the ideal solution is that in my lib I subscribe to some Application.Exit event and close my window, obviusly I can't use Application.Exit because the application doesn't close due of my window not closing
Maybe there is a way to calculate all windows that belongs to an application? I can do something with that too
If you have a main window, can't you set Application.ShutdownMode to OnMainWindowClose ?
The default value is OnLastWindowClose, which is most likely why you're seeing this behaviour.
It's a cheap hack, but I think this may achieve what you're after..
In your lib you will need to reference the xaml dependencies (PresentationCore, PresentationFramework, System.Xaml and WindowsBase)
In the static constructor for your lib, you can then add something like
Application.Current.MainWindow.Closed += new EventHandler(MainWindow_Closed);
static void MainWindow_Closed(object sender, EventArgs e)
{
Dispose();
}
Where dispose closes your window (_Instance.Close()) and handles any other clean-up calls
Conceptually speaking, the only thing that comes to mind is an event message notification service, in which, the second window is listening or awaiting a message to close and the first window sends a message for it to be closed. This also requires using the MVVM pattern. I'm not sure about this entirely, and I am also not sure if this falls into your idea of not letting other programmers know.
Here is a blog article on it:
Sending notifications in WPF MVVM applications

Categories

Resources