WPF Window LocationChanged ended - wpf

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.

Related

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.

How do I prevent the taskbar from appearing with a WPF non-fullscreen window?

I have a large C++ codebase with native Windows GUI that runs fullscreen.
A part of it shall be exchanged by a WPF window shown on top of it.
The window is set up like this:
<Window WindowState="Normal"
WindowStyle="None"
ShowInTaskbar="False"
AllowsTransparency="True"
/>
This blends the window seamless into the rest of the application.
The invocation of the window is done from C++/CLI like this:
Windows::Window^ w = window();
Windows::Interop::WindowInteropHelper iHelp(w);
iHelp.Owner = System::IntPtr(_parentHwnd);
w->Show();
The _parentHwnd is the HWND of the native application.
As said the application is always shown fullscreen.
When I now click on the WPF window the Windows taskbar will appear.
How do I prevent the taskbar from appearing?
I have used this class (an idea was found somewhere on the Net) to hide/show the taskbar:
public static class Taskbar
{
[DllImport("user32.dll")]
private static extern int FindWindow(string className, string windowText);
[DllImport("user32.dll")]
private static extern int ShowWindow(int hwnd, int command);
private const int SW_HIDE = 0;
private const int SW_SHOW = 1;
public static int Handle
{
get
{
return FindWindow("Shell_TrayWnd", "");
}
}
public static int StartHandle
{
get
{
return FindWindow("Button", "Start");
}
}
public static void Show()
{
ShowWindow(Handle, SW_SHOW);
ShowWindow(StartHandle, SW_SHOW);
}
public static void Hide()
{
ShowWindow(Handle, SW_HIDE);
ShowWindow(StartHandle, SW_HIDE);
}
}
Works on Windows XP/Vista/7.
With Flot2011 answer this I was able to pretend that my window belongs to another fullscreen window. For those curious about the missing parts - here are they:
We implement handler for the activated and deactivated events
<Window
Activated="DisplayWindow_Activated"
Deactivated="DisplayWindow_Deactivated"
/>
The event handler look like this
private void DisplayWindow_Activated(object sender, EventArgs e)
{
var screen = System.Windows.Forms.Screen.FromRectangle(
new System.Drawing.Rectangle(
(int)this.Left, (int)this.Top,
(int)this.Width, (int)this.Height));
if( screen.Primary )
Taskbar.Hide();
}
private void DisplayWindow_Deactivated(object sender, EventArgs e)
{
var screen = System.Windows.Forms.Screen.FromRectangle(
new System.Drawing.Rectangle(
(int)this.Left, (int)this.Top,
(int)this.Width, (int)this.Height));
if( screen.Primary )
Taskbar.Show();
}
So what happens now? The taskbar will vanish in the main application because it is fullscreen. When I click on the overlaid window the taskbar would be activated but is deactivated by the propert event. So the mashup behaves like one fullscreen application.
The screen part is to handle multi-monitor setups. We should only hide the taskbar if the (mashup) application is shown fullscreen on the primary monitor. The current solution assumes that WPF-window and underlying fullscreen application are on the same screen.
If you use the screen stuff, don't forget to include references to System.Drawing and System.Window.Forms. Usings are not such a great idea here because the namespaces collide with stuff from .net.

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.

How to restore a minimized Window in code-behind?

This is somewhat of a mundane question but it seems to me there is no in-built method for it in WPF. There only seems to be the WindowState property which being an enum does not help since i cannot tell whether the Window was in the Normal or Maximized state before being minimized.
When clicking the taskbar icon the window is being restored just as expected, assuming its prior state, but i cannot seem to find any defined method which does that.
So i have been wondering if i am just missing something or if i need to use some custom interaction logic.
(I'll post my current solution as answer)
Not sure this will work for everybody, but I ran into this today and someone on the team suggested "have you tried Normal"?
Turns out he was right. The following seems to nicely restore your window:
if (myWindow.WindowState == WindowState.Minimized)
myWindow.WindowState = WindowState.Normal;
That works just fine, restoring the window to Maximized if needed. It seems critical to check for the minimized state first as calling WindowState.Normal a second time will "restore" your window to its non-maximized state.
SystemCommands class has a static method called RestoreWindow that restores the window to previous state.
SystemCommands.RestoreWindow(this); // this being the current window
[Note : SystemCommands class is part of .NET 4.5+ (MSDN Ref) for projects that target to earlier versions of Framework can use the WPF Shell extension (MSDN Ref)]
WPF's point of view is that this is an OS feature. If you want to mess around with OS features you might have to get your hands dirty. Luckily they have provided us with the tools to do so. Here is a UN-minimize method that takes a WPF window and uses WIN32 to accomplish the effect without recording any state:
public static class Win32
{
public static void Unminimize(Window window)
{
var hwnd = (HwndSource.FromVisual(window) as HwndSource).Handle;
ShowWindow(hwnd, ShowWindowCommands.Restore);
}
[DllImport("user32.dll")]
private static extern bool ShowWindow(IntPtr hWnd, ShowWindowCommands nCmdShow);
private enum ShowWindowCommands : int
{
/// <summary>
/// Activates and displays the window. If the window is minimized or
/// maximized, the system restores it to its original size and position.
/// An application should specify this flag when restoring a minimized window.
/// </summary>
Restore = 9,
}
}
For some reason,
WindowState = WindowState.Normal;
didn't work for me.
So I used following code & it worked..
Show();
WindowState = WindowState.Normal;
Here is how i get it to restore right now: I handle the StateChanged event to keep track of the last state that was not Minimized
WindowState _lastNonMinimizedState = WindowState.Maximized;
private void Window_StateChanged(object sender, EventArgs e)
{
if (this.WindowState != System.Windows.WindowState.Minimized)
{
_lastNonMinimizedState = WindowState;
}
}
To restore i then have to set that WindowState respectively:
this.WindowState = _lastNonMinimizedState;
Hmmm, the accepted answer did not work for me. The "maximized" window, when recalled from the task bar would end up centering itself (displaying in its Normal size, even though its state is Maximized) on the screen and things like dragging the window by its title bar ended up not working. Eventually (pretty much by trial-and-error), I figured out how to do it. Thanks to #H.B. and #Eric Liprandi for guiding me to the answer! Code follows:
private bool windowIsMinimized = false;
private WindowState lastNonMinimizedState = WindowState.Normal;
private void Window_StateChanged(object sender, EventArgs e)
{
if (this.windowIsMinimized)
{
this.windowIsMinimized = false;
this.WindowState = WindowState.Normal;
this.WindowState = this.lastNonMinimizedState;
}
else if (this.WindowState == WindowState.Minimized)
{
this.windowIsMinimized = true;
}
}
private void Window_MinimizeButtonClicked(object sender, MouseButtonEventArgs e)
{
this.lastNonMinimizedState = this.WindowState;
this.WindowState = WindowState.Minimized;
this.windowIsMinimized = true;
}
private void Window_MaximizeRestoreButtonClicked(object sender, MouseButtonEventArgs e)
{
if (this.WindowState == WindowState.Normal)
{
this.WindowState = WindowState.Maximized;
}
else
{
this.WindowState = WindowState.Normal;
}
this.lastNonMinimizedState = this.WindowState;
}
In native Windows you can restore your window to a previous state with ShowWindow(SW_RESTORE):
Activates and displays the window. If the window is minimized or maximized, the system restores it to its original size and position. An application should specify this flag when restoring a minimized window.
There's surely .Net counterpart to that.

WPF Mousedown => No MouseLeave Event

I'm building a Windows Presentation Foundation control with Microsoft Blend.
When I leave my control by pressing the left-mouse-button, the MouseLeave-Event is not raised. Why not?
This is intended behaviour: When you are doing mousedown on a control and leaving the control, the control STILL retains its "capture" on the mouse, meaning the control won't fire the MouseLeave-Event. The Mouse-Leave Event instead will be fired, once the Mousebutton is released outside of the control.
To avoid this, you can simple tell your control NOT to capture the mouse at all:
private void ControlMouseDown(System.Object sender, System.Windows.Forms.MouseEventArgs e)
{
Control control = (Control) sender;
control.Capture = false; //release capture.
}
Now the MouseLeave Event will be fired even when moving out while a button is pressed.
If you need the Capture INSIDE the Control, you need to put in more effort:
Start tracking the mouseposition manually, when the mousekey is pressed
Compare the position with the Top, Left and Size Attributes of the control in question.
Decide whether you need to stop the control capturing your mouse or not.
public partial class Form1 : Form
{
private Point point;
private Boolean myCapture = false;
public Form1()
{
InitializeComponent();
}
private void button1_MouseDown(object sender, MouseEventArgs e)
{
myCapture = true;
}
private void button1_MouseMove(object sender, MouseEventArgs e)
{
if (myCapture)
{
point = Cursor.Position;
if (!(point.X > button1.Left && point.X < button1.Left + button1.Size.Width && point.Y > button1.Top && point.Y < button1.Top + button1.Size.Height))
{
button1.Capture = false; //this will release the capture and trigger the MouseLeave event immediately.
myCapture = false;
}
}
}
private void button1_MouseLeave(object sender, EventArgs e)
{
MessageBox.Show("Mouse leaving");
}
}
of course you need to stop the own tracking ( myCapture=false;) on MouseUp. Forgot that one :)
When I don't get mouse events I expect I typically use Snoop to help me understand what is happening.
Here are a couple of links:
1- Snoop (a WPF utility)
2- CodePlex project for Snoop
And for completeness and historical reasons (not the bounty - it doesn't make sense having two duplicate questions - you should probably move it into one if not too late)...
I made a thorough solution using global mouse hook here (approach 2)
WPF: mouse leave event doesn't trigger with mouse down
And simplified its use - you can use it by binding to commands in your view-model - e.g.
my:Hooks.EnterCommand="{Binding EnterCommand}"
my:Hooks.LeaveCommand="{Binding LeaveCommand}"
my:Hooks.MouseMoveCommand="{Binding MoveCommand}"
...more details in there
Old question but I came across the same problem with a Button (MouseLeave does not fire while MouseDown because MouseDown Captures the Mouse...)
This is how I solved it anyway:
element.GotMouseCapture += element_MouseCaptured;
static void element_MouseCaptured(object sender, MouseEventArgs e)
{
FrameworkElement element = (FrameworkElement)sender;
element.ReleaseMouseCapture();
}
Hope that helps someone looking for a quick fix :P

Resources