Wpf window title bar mouse activity - wpf

I'm trying to get event on window title bar (for a standard wpf window).
A simple mouse down event (no matter the button) would be enough.
The idea is that i want to perform action on a custom control (which contain a textbox) when user click outside the control.
For now i'm adding two events handler to the control to help that:
UIElement.IsKeyboardFocusWithinChanged and Window.PreviewMouseDown. It works almost perfectly with this two event, except when user click on the window title bar as none of these two events are being fired...
Any ideas ? Thanks

You can use the following code to check if a window border has been clicked:
private const int WM_NCLBUTTONDOWN = 0x00a1;
// added after window loaded
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)
{
switch (msg)
{
case WM_NCLBUTTONDOWN:
// your action
break;
}
return IntPtr.Zero;
}

Related

WPF MessageBox not working

In my WPF I've tried System.Windows.MessageBox.Show, System.Windows.Forms.MessageBox.Show, and Xceed.Wpf.Toolkit.MessageBox.Show (from the Wpf Toolkit).
Every one of these methods shows the message box exactly how I want it too. The problem is that none of the buttons work. I click OK, I click cancel, I click the X, none of the buttons do anything. And with the toolkit message box I can't move the message box either. The buttons don't depress either. It's as if they're disabled, but I have no idea why.
EDIT: I'm using Prism and MEF to compose the application. Inside my module I have a view that is being displayed in a region in my Shell Window. The view is a UserControl with a button.
<UserControl>
<Grid>
<Button content="click me" Click="Button_OnClick"/>
</Grid>
</UserControl>
In the code behind I have the OnClick method.
private void Button_OnClick(object sender, RoutedEventArgs e)
{
System.Windows.MessageBox.Show("test");
}
The message box gets displayed, I can move it, The 'X' glows on mouse over, but neither the 'X' nor the 'OK' button, do anything.
I can provide more code as needed, I just don't want to have to include my whole application...
FIXED
The main WPF window had a borderless behavior attached to it that was processing windows messages (WndProc) and it wasn't processing the WM_NCACTIVATE message properly.
NOT WORKING:
case UnsafeNativeConstants.WM_NCACTIVATE:
handled = true;
break;
WORKING:
case UnsafeNativeConstants.WM_NCACTIVATE:
retVal = UnsafeNativeMethods.DefWindowProc(hwnd, UnsafeNativeConstants.WM_NCACTIVATE, new IntPtr(1), new IntPtr(-1));
handled = true;
break;
internal static UnsafeNativeMethods
{
[DllImport("user32", CallingConvention = CallingConvention.Winapi)]
public static extern IntPtr DefWindowProc([In] IntPtr hwnd, [In] int msg, [In] IntPtr wParam, [In] IntPtr lParam);
}
Try running your app in the debugger and pausing when the dialog is visible to ensure the message pump/loop is still running and is not deadlocked. That's the only reason I can think of that your UI could be unresponsive.

WPF Window and WndProc

The code snipper below is taken from "WPF 4 Unleashed". it demonstrates hwo in windows 7 it's possible to create the Aero Glass effect using WIN32 API. In this demo, the WndProc events procedure is used with respect to the Window instance. I noticed that in this routine there is no invocation of the default window procedure, as if there are no other events that need to be handled by that WPF window.
What brings me to post that question -- which is more of a general question regarding WPF -- is whether the events that are normally handles by WPF window (and I am sure there are many of them) are handled by some other procedure. In other words, is WPF window different than WinForms --- does it gets messages from Operating system (mouse clicking, mouse movements) by other means ?
[StructLayout(LayoutKind.Sequential)]
public struct MARGINS
{
public MARGINS(Thickness t)
{
Left = (int)t.Left;
Right = (int)t.Right;
Top = (int)t.Top;
Bottom = (int)t.Bottom;
}
public int Left;
public int Right;
public int Top;
public int Bottom;
}
public class GlassHelper
{
[DllImport("dwmapi.dll", PreserveSig=false)]
static extern void DwmExtendFrameIntoClientArea( IntPtr hWnd, ref MARGINS pMarInset);
[DllImport("dwmapi.dll", PreserveSig=false)]
static extern bool DwmIsCompositionEnabled();
public static bool ExtendGlassFrame(Window window, Thickness margin)
{
if (!DwmIsCompositionEnabled())
return false;
IntPtr hwnd = new WindowInteropHelper(window).Handle;
if (hwnd == IntPtr.Zero)
throw new InvalidOperationException(
"The Window must be shown before extending glass.");
// Set the background to transparent from both the WPF and Win32 perspectives
window.Background = Brushes.Transparent;
HwndSource.FromHwnd(hwnd).CompositionTarget.BackgroundColor =Colors.Transparent;
MARGINS margins = new MARGINS(margin);
DwmExtendFrameIntoClientArea(hwnd, ref margins);
return true;
}
}
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
// This can’t be done any earlier than the SourceInitialized event:
GlassHelper.ExtendGlassFrame(this, new Thickness(-1));
// Attach a window procedure in order to detect later enabling of desktop
// composition
IntPtr hwnd = new WindowInteropHelper(this).Handle;
HwndSource.FromHwnd(hwnd).AddHook(new HwndSourceHook(WndProc));
}
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == WM_DWMCOMPOSITIONCHANGED)
{
// Reenable glass:
GlassHelper.ExtendGlassFrame(this, new Thickness(-1));
handled = true;
}
return IntPtr.Zero;
}
private const int WM_DWMCOMPOSITIONCHANGED = 0x031E;
WPF window is the same as WinForms window in terms of using WndProc. I had no problem to put the snippet into my WPF app. In fact, I didn't find any WndProc related code, that didn't work in WPF so far.
WPF windows are like Windows Forms windows and like classic Windows windows in that they all have a Message Loop for receiving messages and a WindowProc (the actual name can be whatever the programmer chooses to make it) to process them. All the windows can be subclassed (as in About Window Procedures), at least at the level below WPF. I don't know if WndProc for Windows Forms or for WPF are subclassing the relevant windows but they might be.

Keyboards inputs in a WPF user control are not sent to the WinForms container

We have a WinForms application that we are progressively converting to WPF. At this point the application's main form is a Form (WinForm) that contains a vertical sidebar built in WPF. The sidebar is hosted in an ElementHost control.
In the main form, KeyPreview is set to true and we override OnKeyDown() to process application wide keyboard shortcuts. When the sidebar has the focus, keyboard events are not sent to OnKeyDown.
What is the correct way to fix this?
Yes, it seems the KeyPreview is not consider by ElementHost, here is a workaround:
Derive from ElementHost and override ProcessCmdKey, when the base.ProcessCmdKey result says "not processed", pass the message to the parent even if it is not your main form, this way your main form will receive it because other winforms control will behave correctly. Here is a sample...
public class KeyPreviewEnabledElementHost : ElementHost
{
public KeyPreviewEnabledElementHost()
{
}
[DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
protected override bool ProcessCmdKey(ref System.Windows.Forms.Message m, System.Windows.Forms.Keys keyData)
{
bool processed = base.ProcessCmdKey(ref m, keyData);
if (!processed)
{
SendMessage(Parent.Handle, m.Msg, m.WParam, m.LParam);
}
return processed;
}
}

Mouse events are not received by a WPF ScrollViewer when hosted in a WinForms container

We have a WinForms application that we are progressively converting to WPF. At this point the application's main form is a Form (WinForms) that contains a vertical sidebar built in WPF. The sidebar is hosted in an ElementHost control.
The sidebar is made of a ScrollViewer that contains other controls. The problem is that when the focus is somewhere in the WinForms aera and I use the mouse wheel over the ScrollViewer, it does not scroll.
This is related to the WPF/WinForms integration because in a 100% WPF project, the ScrollViewer reacts to the mouse wheel even if the focus is on another control.
What is the correct way to fix this?
consider doing a message filter and when you receive WM_MOUSEWHEEL, determine if the mouse is over your WPF control. If so then send the message directly to your Element window handle.
Something like this:
System.Windows.Forms.Application.AddMessageFilter( new MouseWheelMessageFilter( YourElementInsideAnElementHost ) );
Dont forget to call RemoveMessageFilter when you go out of scope
public class MouseWheelMessageFilter : IMessageFilter
{
private const int WM_MOUSEWHEEL = 0x020A;
private FrameworkElement _element;
[DllImport("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
public MouseWheelMessageFilter(FrameworkElement element)
{
_element = element;
}
public bool PreFilterMessage(ref Message m)
{
if (m.Msg == WM_MOUSEWHEEL)
{
Rect rect = new Rect(0, 0, _element.ActualWidth, _element.ActualHeight);
Point pt = Mouse.GetPosition(_element);
if (rect.Contains(pt))
{
HwndSource hwndSource = (HwndSource)HwndSource.FromVisual(_element);
SendMessage(hwndSource.Handle, m.Msg, m.WParam, m.LParam);
return true;
}
}
return false;
}
}
Try setting the focus to the ElementHost by calling elementHost.Select()
This made the MouseWheel event work for me.

WPF Window LocationChanged ended

I have a WPF Window, and I want to determine when a user finishes moving a Window around on the desktop. I hooked up to the LocationChanged event and that's fine, but I can't figure out how to determine when the user stops moving the window (by releasing the left mouse button).
There's no event to help me determine that, something like a LocationChangedEnded event. I tried hooking up to MouseLeftButtonUp but that event is never fired.
Anyone has any ideas?
Two possible approaches would be:
You don't really know when the mouse button is raised. Instead, you wait for the window to stop sending those move events. Set up a short lived timer that starts ticking every time you receive a window move event. Reset the timer if it's already on. When you receive the timer event, e.g. after a few hundred millisecs, you could assume the user stopped moving the window. Even with a high resolution mouse, when holding down the left mouse button and trying to stay still, the jitter will keep sending move events. This approach is documented here.
Attempt to capture mouse notifications from the non-client area of the window. You could set up a window message hook to capture window messages. Once the first window move event is seen, the hook could start looking for WM_NCLBUTTONUP events. This approach avoids the timer and the guessing. However, it makes assumptions about the ways Windows allows the user to position windows, and may fail in some cases, e.g. if the user moves the user with the keyboard only (Alt+Space, M, arrow keys).
You can listen for the WM_ENTERSIZEMOVE event, which should only fire when the move is started. While the user is dragging you may receive WM_MOVING and WM_MOVE events. The latter depends on the their system settings (e.g. the window moves as they drag, versus just dragging an outline). Finally, WM_EXITSIZEMOVE would indicate when they are done.
You want to get the WM_WINDOWPOSCHANGED message, add this to your Window class:
internal enum WM
{
WINDOWPOSCHANGING = 0x0047,
}
[StructLayout(LayoutKind.Sequential)]
internal struct WINDOWPOS
{
public IntPtr hwnd;
public IntPtr hwndInsertAfter;
public int x;
public int y;
public int cx;
public int cy;
public int flags;
}
private override void OnSourceInitialized(EventArgs ea)
{
HwndSource hwndSource = (HwndSource)HwndSource.FromVisual((Window)this);
hwndSource.AddHook(DragHook);
}
private static IntPtr DragHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handeled)
{
switch ((WM)msg)
{
case WM.WINDOWPOSCHANGED:
{
WINDOWPOS pos = (WINDOWPOS)Marshal.PtrToStructure(lParam, typeof(WINDOWPOS));
if ((pos.flags & (int)SWP.NOMOVE) != 0)
{
return IntPtr.Zero;
}
Window wnd = (Window)HwndSource.FromHwnd(hwnd).RootVisual;
if (wnd == null)
{
return IntPtr.Zero;
}
// ** do whatever you need here **
// the new window position is in the pos variable
// just note that those are in Win32 "screen coordinates" not WPF device independent pixels
}
break;
}
return IntPtr.Zero;
}
You may be interested in the answer that I've posted here:
How do you disable Aero Snap in an application?
The answer contains a reliable way to detect the start/end of window moves for the purpose of disabling Aero snap.
This solution is not perfect (kind of patch) but at least it can be useful to someone.
private void Window_Loaded(object sender, RoutedEventArgs e)
{
tmr.Elapsed += Tmr_Elapsed;
}
System.Timers.Timer tmr = new System.Timers.Timer(2000);
private void Tmr_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
tmr.Stop();
Application.Current.Dispatcher.Invoke(() =>
{
//Your code for location changed ended here
});
}
private void Window_LocationChanged(object sender, EventArgs e)
{
tmr.Stop();
tmr.Start();
}
Can change the interval as per your need. I hope it helps someone.

Resources