Missing WM_Messages on WPF - c#

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

Related

Deadlock when creating WPF window in a thread

I am creating a new window in a thread in .Net Framework 4.8 WPF application.
Most of time it works. However sometimes it hangs even when the app starts.
The dispatcher in the main thread and the dispatcher in the new thread wait each other.
You can refer to the following window to check threads.
The following is the entire .cs code to reproduce the phenomenon. 'NewWindow' is a class derived from 'System.Windows.Window' and it does not have to have anything in it to reproduce the phenomenon(For clarification I also put code for 'NewWindow' at the end of this question).
using System;
using System.Threading;
using System.Windows;
using System.Windows.Interop;
namespace WindowCreateTest
{
public partial class MainWindow : Window
{
NewWindow newWindow = null;
Thread uiThread = null;
public MainWindow()
{
InitializeComponent();
}
public void ShowNewWindow(IntPtr handle)
{
uiThread = new Thread(() =>
{
Thread.CurrentThread.Name = "ShowNewWindow Thread";
if (newWindow == null)
newWindow = new NewWindow();
WindowInteropHelper helper = new WindowInteropHelper(newWindow);
helper.Owner = handle;
newWindow.Show();
System.Windows.Threading.Dispatcher.Run();
});
uiThread.SetApartmentState(ApartmentState.STA);
uiThread.Start();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
var h = new WindowInteropHelper(this).Handle;
ShowNewWindow(h);
}
}
}
You might wonder why I'm creating a new window in a newly created thread not in a main thread. The reason is that, this code is used in MAF(Managed Add-ins and Extensibility Framework or System.AddIn) dlls which is activated in an external process.
WindowInteropHelper helper = new WindowInteropHelper(newWindow);
helper.Owner = handle;
The reason I put the above two lines is
to make 'NewWindow' minimized when I minimize 'MainWindow'
to make 'NewWindow' always be placed over 'MainWindow'.
And I guess this is the root cause of the deadlock.
using System.Windows;
namespace WindowCreateTest
{
public partial class NewWindow : Window
{
public NewWindow()
{
InitializeComponent();
}
}
}
Thank you for reading and any advice will be welcomed and appreciated.

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

How to recreate windows form application when main window is closed

I have windows forms app. When it's closed the main window is disposed and then when user click on tray window is recreated - it works. However I have werid problem when I try to bring application back when using FileSystemWatcher. Idea is simple, when file is changed application is brought back. However in this case application appears but hangs and then dissapears. The shape of window comes back but is unresponsive, moving mouse on window shows like app is "thinking" or "hanging", the app doesn't have icon on taskbar.
My guess is that this is connected with threads/synchronization but I have no idea how to make it work again. I tried many different things connected with threading but failed. I cannot create this window again in UI thread because as I understand I can write _mainWindow.BeginInvoke but I can't do that before I create this form.
I have created the minimal working example that demonstrates the issue. It is available at https://gitlab.com/virtual92/getting-forms-up or here:
Program.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace GettingFormsUp
{
static class Program
{
private static bool hideFlag = true;
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var initApplicationContext = new InitApplicationContext();
Console.WriteLine();
var fileSystemWatcher = new FileSystemWatcher(Path.GetFullPath("../.."), "watcher");
fileSystemWatcher.Changed += (sender, args) =>
{
Console.WriteLine("Watched");
initApplicationContext.Show();
};
fileSystemWatcher.EnableRaisingEvents = true;
Application.Run(initApplicationContext);
}
private class InitApplicationContext : ApplicationContext
{
private static MainWindow _mainWindow;
public InitApplicationContext()
{
NewForm();
}
private void NewForm()
{
Console.WriteLine("Creating new MainWindow");
_mainWindow = new MainWindow();
_mainWindow.Invoke((MethodInvoker) delegate
{
_mainWindow.Show();
});
}
public void Show()
{
if (_mainWindow == null || _mainWindow.IsDisposed)
{
NewForm();
}
else if (!_mainWindow.Visible)
{
_mainWindow.BeginInvoke((MethodInvoker) delegate
{
Console.WriteLine("showing");
_mainWindow.Show();
});
}
}
public void Delete()
{
if (_mainWindow != null && !_mainWindow.IsDisposed)
{
_mainWindow.Dispose();
}
}
}
}
}
MainWindow.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace GettingFormsUp
{
public class MainWindow : Form
{
public MainWindow()
{
CreateHandle();
InitializeComponent();
}
private System.ComponentModel.IContainer components = null;
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Text = "Form1";
}
}
}
How can I make it work?
The problem with your code is that when you create the window again, it's being created in the thread used to raise the FileSystemWatcher.Changed event, which is a background thread, not the thread that the Application.Run() method is using to pump window messages. So that background thread winds up owning the window.
Messages for the window are sent to the thread that owns it, but that thread doesn't have a message pumping loop, so the window never sees the messages. Those messages are critical, as they are what handle everything about the interaction with the window, both user input and everything involved in drawing the window (except for the bare minimum, which the Windows desktop manager handles). Those messages are even used to handle things like calls to Control.Invoke() and Control.BeginInvoke(). Without the message-pumping loop, BeginInvoke() delegates will never be handled, and Invoke() will never even return.
There are a variety of ways to address this. Simply not disposing the window in the first place could be an option (you can override OnFormClosing(), canceling the event and hiding the window instead). Then the call to _mainWindow.Invoke() will always go to the correct thread and work like you expect:
public partial class MainWindow : Form
{
// ...
protected override void OnFormClosing(FormClosingEventArgs e)
{
Visible = false;
e.Cancel = true;
base.OnFormClosing(e);
}
// ...
}
Alternatively, you can capture the main thread's SynchronizationContext object, which can be used to do the same thing as Control.Invoke()/BeginInvoke(). The key to that technique is that until you've created a Winforms component in a thread, that thread won't have a SynchronizationContext. In your code, the form is created when you create the InitApplicationContext object, so capturing the SynchronizationContext after that will work:
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var initApplicationContext = new InitApplicationContext();
SynchronizationContext context = SynchronizationContext.Current;
Console.WriteLine();
string path = Path.GetFullPath("../..");
Console.WriteLine($"Watching {path}");
var fileSystemWatcher = new FileSystemWatcher(path, "watcher");
fileSystemWatcher.Changed += (sender, args) =>
{
context.Post(o =>
{
Console.WriteLine("Watched");
initApplicationContext.Show();
}, null);
};
fileSystemWatcher.EnableRaisingEvents = true;
Application.Run(initApplicationContext);
}
If you take this approach, then of course you don't need the call to Control.Invoke() when creating the window. It's superfluous the first time anyway, and because you'll be using SynchronizationContext from the FileSystemWatcher.Changed event handler in the subsequent instances, it's also unneeded then:
private void NewForm()
{
Console.WriteLine("Creating new MainWindow");
_mainWindow = new MainWindow();
_mainWindow.Show();
}

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

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