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

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

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];
}
}
}
}
}

helper method checking if window is open on another thread

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.

Missing WM_Messages on WPF

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();
}
}
}

Capturing mouse events from every component

I have a problem with MouseEvents on my WinForm C# application.
I want to get all mouse clicks on my application, but I don't want to put a listener in every child component neither use Windows mouse hook.
On Flash I could put a listener on Stage to get all the MouseEvents on the movie.
Is there such thing on C#? A global MouseListener?
Edit:
I create this class from IMessageFilter ans used Application.AddMessageFilter.
public class GlobalMouseHandler : IMessageFilter{
private const int WM_LBUTTONDOWN = 0x201;
public bool PreFilterMessage(ref Message m){
if (m.Msg == WM_LBUTTONDOWN) {
// Do stuffs
}
return false;
}
}
And put this code on the Controls that need listen global clicks:
GlobalMouseHandler globalClick = new GlobalMouseHandler();
Application.AddMessageFilter(globalClick);
One straightforward way to do this is to add a message loop filter by calling Application.AddMessageFilter and writing a class that implements the IMessageFilter interface.
Via IMessageFilter.PreFilterMessage, your class gets to see any inputs messages that pass through your application's message loop. PreFilterMessage also gets to decide whether to pass these messages on to the specific control to which they're destined.
One piece of complexity that this approach introduces is having to deal with Windows messages, via the Message struct passed to your PreFilterMessage method. This means referring to the Win32 documention on WM\_LBUTTONDOWN, WM\_MOUSEMOVE, WM\_LBUTTONUP etc, instead of the conventional MouseDown, MouseMove and MouseUp events.
Sample Class
class CaptureEvents : IMessageFilter
{
#region IMessageFilter Members
public delegate void Callback(int message);
public event Callback MessageReceived;
IntPtr ownerWindow;
Hashtable interestedMessages = null;
CaptureEvents(IntPtr handle, int[] messages)
{
ownerWindow = handle;
for(int c = 0; c < messages.Length ; c++)
{
interestedMessages[messages[c]] = 0;
}
}
public bool PreFilterMessage(ref Message m)
{
if (m.HWnd == ownerWindow && interestedMessages.ContainsKey(m.Msg))
{
MessageReceived(m.Msg);
}
return true;
}
#endregion
}
Take a look at this article. It recursively hoooks all the control events and broadcasts them. You could also override WndProc in your form.
If you don't want to handle the messages by overriding Form.PreProcessMessage or Form.WndProc then you could subclass Form to hook an event handler to all the MouseClick events from the various controls on the form.
EDIT: forgot to recurse through child controls of controls on the form.
public class MousePreviewForm : Form
{
protected override void OnClosed(EventArgs e)
{
UnhookControl(this as Control);
base.OnClosed(e);
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
HookControl(this as Control);
}
private void HookControl(Control controlToHook)
{
controlToHook.MouseClick += AllControlsMouseClick;
foreach (Control ctl in controlToHook.Controls)
{
HookControl(ctl);
}
}
private void UnhookControl(Control controlToUnhook)
{
controlToUnhook.MouseClick -= AllControlsMouseClick;
foreach (Control ctl in controlToUnhook.Controls)
{
UnhookControl(ctl);
}
}
void AllControlsMouseClick(object sender, MouseEventArgs e)
{
//do clever stuff here...
throw new NotImplementedException();
}
}
Your forms would then need to derive from MousePreviewForm not System.Windows.Forms.Form.

How to handle WndProc messages in WPF?

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.

Categories

Resources