In Windows Forms, I'd just override WndProc, and start handling messages as they came in.
Can someone show me an example of how to achieve the same thing in WPF?
You can do this via the System.Windows.Interop namespace which contains a class named HwndSource.
Example of using this
using System;
using System.Windows;
using System.Windows.Interop;
namespace WpfApplication1
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
HwndSource source = PresentationSource.FromVisual(this) as HwndSource;
source.AddHook(WndProc);
}
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
// Handle messages...
return IntPtr.Zero;
}
}
}
Completely taken from the excellent blog post: Using a custom WndProc in WPF apps by Steve Rands
Actually, as far as I understand such a thing is indeed possible in WPF using HwndSource and HwndSourceHook. See this thread on MSDN as an example. (Relevant code included below)
// 'this' is a Window
HwndSource source = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
source.AddHook(new HwndSourceHook(WndProc));
private static IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
// do stuff
return IntPtr.Zero;
}
Now, I'm not quite sure why you'd want to handle Windows Messaging messages in a WPF application (unless it's the most obvious form of interop for working with another WinForms app). The design ideology and the nature of the API is very different in WPF from WinForms, so I would suggest you just familiarise yourself with WPF more to see exactly why there is no equivalent of WndProc.
HwndSource src = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
src.AddHook(new HwndSourceHook(WndProc));
.......
public IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if(msg == THEMESSAGEIMLOOKINGFOR)
{
//Do something here
}
return IntPtr.Zero;
}
If you don't mind referencing WinForms, you can use a more MVVM-oriented solution that doesn't couple service with the view. You need to create and initialize a System.Windows.Forms.NativeWindow which is a lightweight window that can receive messages.
public abstract class WinApiServiceBase : IDisposable
{
/// <summary>
/// Sponge window absorbs messages and lets other services use them
/// </summary>
private sealed class SpongeWindow : NativeWindow
{
public event EventHandler<Message> WndProced;
public SpongeWindow()
{
CreateHandle(new CreateParams());
}
protected override void WndProc(ref Message m)
{
WndProced?.Invoke(this, m);
base.WndProc(ref m);
}
}
private static readonly SpongeWindow Sponge;
protected static readonly IntPtr SpongeHandle;
static WinApiServiceBase()
{
Sponge = new SpongeWindow();
SpongeHandle = Sponge.Handle;
}
protected WinApiServiceBase()
{
Sponge.WndProced += LocalWndProced;
}
private void LocalWndProced(object sender, Message message)
{
WndProc(message);
}
/// <summary>
/// Override to process windows messages
/// </summary>
protected virtual void WndProc(Message message)
{ }
public virtual void Dispose()
{
Sponge.WndProced -= LocalWndProced;
}
}
Use SpongeHandle to register for messages you're interested in and then override WndProc to process them:
public class WindowsMessageListenerService : WinApiServiceBase
{
protected override void WndProc(Message message)
{
Debug.WriteLine(message.msg);
}
}
The only downside is that you have to include System.Windows.Forms reference, but otherwise this is a very encapsulated solution.
Here is a link on overriding WindProc using Behaviors:
http://10rem.net/blog/2010/01/09/a-wpf-behavior-for-window-resize-events-in-net-35
[Edit: better late than never] Below is my implementation based on the above link. Although revisiting this I like the AddHook implementations better. I might switch to that.
In my case I wanted to know when the window was being resized and a couple other things. This implementation hooks up to the Window xaml and sends events.
using System;
using System.Windows.Interactivity;
using System.Windows; // For Window in behavior
using System.Windows.Interop; // For Hwnd
public class WindowResizeEvents : Behavior<Window>
{
public event EventHandler Resized;
public event EventHandler Resizing;
public event EventHandler Maximized;
public event EventHandler Minimized;
public event EventHandler Restored;
public static DependencyProperty IsAppAskCloseProperty = DependencyProperty.RegisterAttached("IsAppAskClose", typeof(bool), typeof(WindowResizeEvents));
public Boolean IsAppAskClose
{
get { return (Boolean)this.GetValue(IsAppAskCloseProperty); }
set { this.SetValue(IsAppAskCloseProperty, value); }
}
// called when the behavior is attached
// hook the wndproc
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Loaded += (s, e) =>
{
WireUpWndProc();
};
}
// call when the behavior is detached
// clean up our winproc hook
protected override void OnDetaching()
{
RemoveWndProc();
base.OnDetaching();
}
private HwndSourceHook _hook;
private void WireUpWndProc()
{
HwndSource source = HwndSource.FromVisual(AssociatedObject) as HwndSource;
if (source != null)
{
_hook = new HwndSourceHook(WndProc);
source.AddHook(_hook);
}
}
private void RemoveWndProc()
{
HwndSource source = HwndSource.FromVisual(AssociatedObject) as HwndSource;
if (source != null)
{
source.RemoveHook(_hook);
}
}
private const Int32 WM_EXITSIZEMOVE = 0x0232;
private const Int32 WM_SIZING = 0x0214;
private const Int32 WM_SIZE = 0x0005;
private const Int32 SIZE_RESTORED = 0x0000;
private const Int32 SIZE_MINIMIZED = 0x0001;
private const Int32 SIZE_MAXIMIZED = 0x0002;
private const Int32 SIZE_MAXSHOW = 0x0003;
private const Int32 SIZE_MAXHIDE = 0x0004;
private const Int32 WM_QUERYENDSESSION = 0x0011;
private const Int32 ENDSESSION_CLOSEAPP = 0x1;
private const Int32 WM_ENDSESSION = 0x0016;
private IntPtr WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, ref Boolean handled)
{
IntPtr result = IntPtr.Zero;
switch (msg)
{
case WM_SIZING: // sizing gets interactive resize
OnResizing();
break;
case WM_SIZE: // size gets minimize/maximize as well as final size
{
int param = wParam.ToInt32();
switch (param)
{
case SIZE_RESTORED:
OnRestored();
break;
case SIZE_MINIMIZED:
OnMinimized();
break;
case SIZE_MAXIMIZED:
OnMaximized();
break;
case SIZE_MAXSHOW:
break;
case SIZE_MAXHIDE:
break;
}
}
break;
case WM_EXITSIZEMOVE:
OnResized();
break;
// Windows is requesting app to close.
// See http://msdn.microsoft.com/en-us/library/windows/desktop/aa376890%28v=vs.85%29.aspx.
// Use the default response (yes).
case WM_QUERYENDSESSION:
IsAppAskClose = true;
break;
}
return result;
}
private void OnResizing()
{
if (Resizing != null)
Resizing(AssociatedObject, EventArgs.Empty);
}
private void OnResized()
{
if (Resized != null)
Resized(AssociatedObject, EventArgs.Empty);
}
private void OnRestored()
{
if (Restored != null)
Restored(AssociatedObject, EventArgs.Empty);
}
private void OnMinimized()
{
if (Minimized != null)
Minimized(AssociatedObject, EventArgs.Empty);
}
private void OnMaximized()
{
if (Maximized != null)
Maximized(AssociatedObject, EventArgs.Empty);
}
}
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:behaviors="clr-namespace:RapidCoreConfigurator._Behaviors"
Title="name" Height="500" Width="750" BorderBrush="Transparent">
<i:Interaction.Behaviors>
<behaviors:WindowResizeEvents IsAppAskClose="{Binding IsRequestClose, Mode=OneWayToSource}"
Resized="Window_Resized"
Resizing="Window_Resizing" />
</i:Interaction.Behaviors>
...
</Window>
You can attach to the 'SystemEvents' class of the built-in Win32 class:
using Microsoft.Win32;
in a WPF window class:
SystemEvents.PowerModeChanged += SystemEvents_PowerModeChanged;
SystemEvents.SessionSwitch += SystemEvents_SessionSwitch;
SystemEvents.SessionEnding += SystemEvents_SessionEnding;
SystemEvents.SessionEnded += SystemEvents_SessionEnded;
private async void SystemEvents_PowerModeChanged(object sender, PowerModeChangedEventArgs e)
{
await vm.PowerModeChanged(e.Mode);
}
private async void SystemEvents_PowerModeChanged(object sender, PowerModeChangedEventArgs e)
{
await vm.PowerModeChanged(e.Mode);
}
private async void SystemEvents_SessionSwitch(object sender, SessionSwitchEventArgs e)
{
await vm.SessionSwitch(e.Reason);
}
private async void SystemEvents_SessionEnding(object sender, SessionEndingEventArgs e)
{
if (e.Reason == SessionEndReasons.Logoff)
{
await vm.UserLogoff();
}
}
private async void SystemEvents_SessionEnded(object sender, SessionEndedEventArgs e)
{
if (e.Reason == SessionEndReasons.Logoff)
{
await vm.UserLogoff();
}
}
There are ways to handle messages with a WndProc in WPF (e.g. using a HwndSource, etc.), but generally those techniques are reserved for interop with messages that can't directly be handled through WPF. Most WPF controls aren't even windows in the Win32 (and by extension Windows.Forms) sense, so they won't have WndProcs.
WPF doesn't operate on WinForms type wndprocs
You can host an HWndHost in an appropriate WPF element then override the Hwndhost's wndproc, but AFAIK that's as close as you're going to get.
http://msdn.microsoft.com/en-us/library/ms742522.aspx
http://blogs.msdn.com/nickkramer/archive/2006/03/18/554235.aspx
The short answer is you can't. WndProc works by passing messages to a HWND on a Win32 level. WPF windows have no HWND and hence can't participate in WndProc messages. The base WPF message loop does sit on top of WndProc but it abstracts them away from core WPF logic.
You can use a HWndHost and get at a WndProc for it. However this is almost certainly not what you want to do. For the majority of purposes, WPF does not operate on HWND and WndProc. Your solution almost certainly relies on making a change in WPF not in WndProc.
Related
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.
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.
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.
I'm having problems to minimize/restore my wpf app by clicking on windows taskbar. Sometimes it works, sometimes it does not. So, I added a hook to app main window to try to see if events were coming or not. Sometimes they do, sometimes they do not. Then I use Spy++ to see if events were at least launched... Yes! They are. So why am I receiving just some of them?
public MyMainWindow()
{
InitializeComponent();
this.SourceInitialized += new EventHandler(OnSourceInitialized);
}
void OnSourceInitialized(object sender, EventArgs e)
{
HwndSource source = (HwndSource)PresentationSource.FromVisual(this);
source.AddHook(new HwndSourceHook(HandleMessages));
}
private IntPtr HandleMessages(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
switch (msg) {
case 0x0112://WM_SYSCOMMAND:
switch ((int)wParam & 0xFFF0) {
case 0xF020://SC_MINIMIZE:
break;
case 0xF120://SC_RESTORE:
break;
}
break;
}
}
I have a custom main and I'm using Caliburn Micro with customized Bootstrapper.
I think I made a simplified case where this problem can be seen (not sure about this been same source of my problem). I used a Timer to emulate been waiting for async socket/activeX response. As is possible to see, clicking over taskbar, sometimes window will not be maximized/minimized.
Still don't know how to solve it.
using System;
using System.Threading;
using System.Windows;
using System.Windows.Threading;
using Timer = System.Timers.Timer;
namespace Garbage
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var dispatcherTimer = new DispatcherTimer { Interval = new TimeSpan(0,0,0,0,10) };
dispatcherTimer.Tick += (o, e) =>
{
var waintingForSocketAsyncRenponseEmulation = new Timer(10);
waintingForSocketAsyncRenponseEmulation.Elapsed += delegate
{
lock (this)
{
Monitor.Pulse(this);
}
};
waintingForSocketAsyncRenponseEmulation.Start();
lock (this)
{
Monitor.Wait(this);
}
};
dispatcherTimer.Start();
}
}
}
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