WPF : Maximize window with WindowState Problem (application will hide windows taskbar) - wpf

I have set my main window state to "Maximized" but the problem is my application will fill the whole screen even task bar. what am i doing wrong ?
I'm using windows 2008 R2 with resolution : 1600 * 900
Here is the Xaml :
<Window x:Class="RadinFarazJamAutomationSystem.wndMain"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:local="clr-namespace:RadinFarazJamAutomationSystem"
xmlns:mwt="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero"
Title="MyName" FontFamily="Tahoma" FontSize="12" Name="mainWindow" WindowState="Maximized" xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation"
xmlns:my="clr-namespace:RadinFarazJamAutomationSystem.Calendare.UC" mc:Ignorable="d" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" Loaded="mainWindow_Loaded" FlowDirection="LeftToRight"
ResizeMode="NoResize" Closed="mainWindow_Closed">

You can set the MaxHeight property of that window to SystemParameters.MaximizedPrimaryScreenHeight using the constructor.
public MainWindow()
{
InitializeComponent();
this.MaxHeight = SystemParameters.MaximizedPrimaryScreenHeight;
}

To continue with my previous remark. I use the following code in my applications (source: Maximizing WPF windows
using WinInterop = System.Windows.Interop;
using System.Runtime.InteropServices;
public MainWindow()
{
InitializeComponent();
winMain.SourceInitialized += new EventHandler(win_SourceInitialized);
}
#region Avoid hiding task bar upon maximalisation
private static System.IntPtr WindowProc(
System.IntPtr hwnd,
int msg,
System.IntPtr wParam,
System.IntPtr lParam,
ref bool handled)
{
switch (msg)
{
case 0x0024:
WmGetMinMaxInfo(hwnd, lParam);
handled = true;
break;
}
return (System.IntPtr)0;
}
void win_SourceInitialized(object sender, EventArgs e)
{
System.IntPtr handle = (new WinInterop.WindowInteropHelper(this)).Handle;
WinInterop.HwndSource.FromHwnd(handle).AddHook(new WinInterop.HwndSourceHook(WindowProc));
}
private static void WmGetMinMaxInfo(System.IntPtr hwnd, System.IntPtr lParam)
{
MINMAXINFO mmi = (MINMAXINFO)Marshal.PtrToStructure(lParam, typeof(MINMAXINFO));
// Adjust the maximized size and position to fit the work area of the correct monitor
int MONITOR_DEFAULTTONEAREST = 0x00000002;
System.IntPtr monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
if (monitor != System.IntPtr.Zero)
{
MONITORINFO monitorInfo = new MONITORINFO();
GetMonitorInfo(monitor, monitorInfo);
RECT rcWorkArea = monitorInfo.rcWork;
RECT rcMonitorArea = monitorInfo.rcMonitor;
mmi.ptMaxPosition.x = Math.Abs(rcWorkArea.left - rcMonitorArea.left);
mmi.ptMaxPosition.y = Math.Abs(rcWorkArea.top - rcMonitorArea.top);
mmi.ptMaxSize.x = Math.Abs(rcWorkArea.right - rcWorkArea.left);
mmi.ptMaxSize.y = Math.Abs(rcWorkArea.bottom - rcWorkArea.top);
}
Marshal.StructureToPtr(mmi, lParam, true);
}
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
/// <summary>
/// x coordinate of point.
/// </summary>
public int x;
/// <summary>
/// y coordinate of point.
/// </summary>
public int y;
/// <summary>
/// Construct a point of coordinates (x,y).
/// </summary>
public POINT(int x, int y)
{
this.x = x;
this.y = y;
}
}
[StructLayout(LayoutKind.Sequential)]
public struct MINMAXINFO
{
public POINT ptReserved;
public POINT ptMaxSize;
public POINT ptMaxPosition;
public POINT ptMinTrackSize;
public POINT ptMaxTrackSize;
};
void win_Loaded(object sender, RoutedEventArgs e)
{
winMain.WindowState = WindowState.Maximized;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public class MONITORINFO
{
/// <summary>
/// </summary>
public int cbSize = Marshal.SizeOf(typeof(MONITORINFO));
/// <summary>
/// </summary>
public RECT rcMonitor = new RECT();
/// <summary>
/// </summary>
public RECT rcWork = new RECT();
/// <summary>
/// </summary>
public int dwFlags = 0;
}
[StructLayout(LayoutKind.Sequential, Pack = 0)]
public struct RECT
{
/// <summary> Win32 </summary>
public int left;
/// <summary> Win32 </summary>
public int top;
/// <summary> Win32 </summary>
public int right;
/// <summary> Win32 </summary>
public int bottom;
/// <summary> Win32 </summary>
public static readonly RECT Empty = new RECT();
/// <summary> Win32 </summary>
public int Width
{
get { return Math.Abs(right - left); } // Abs needed for BIDI OS
}
/// <summary> Win32 </summary>
public int Height
{
get { return bottom - top; }
}
/// <summary> Win32 </summary>
public RECT(int left, int top, int right, int bottom)
{
this.left = left;
this.top = top;
this.right = right;
this.bottom = bottom;
}
/// <summary> Win32 </summary>
public RECT(RECT rcSrc)
{
this.left = rcSrc.left;
this.top = rcSrc.top;
this.right = rcSrc.right;
this.bottom = rcSrc.bottom;
}
/// <summary> Win32 </summary>
public bool IsEmpty
{
get
{
// BUGBUG : On Bidi OS (hebrew arabic) left > right
return left >= right || top >= bottom;
}
}
/// <summary> Return a user friendly representation of this struct </summary>
public override string ToString()
{
if (this == RECT.Empty) { return "RECT {Empty}"; }
return "RECT { left : " + left + " / top : " + top + " / right : " + right + " / bottom : " + bottom + " }";
}
/// <summary> Determine if 2 RECT are equal (deep compare) </summary>
public override bool Equals(object obj)
{
if (!(obj is Rect)) { return false; }
return (this == (RECT)obj);
}
/// <summary>Return the HashCode for this struct (not garanteed to be unique)</summary>
public override int GetHashCode()
{
return left.GetHashCode() + top.GetHashCode() + right.GetHashCode() + bottom.GetHashCode();
}
/// <summary> Determine if 2 RECT are equal (deep compare)</summary>
public static bool operator ==(RECT rect1, RECT rect2)
{
return (rect1.left == rect2.left && rect1.top == rect2.top && rect1.right == rect2.right && rect1.bottom == rect2.bottom);
}
/// <summary> Determine if 2 RECT are different(deep compare)</summary>
public static bool operator !=(RECT rect1, RECT rect2)
{
return !(rect1 == rect2);
}
}
[DllImport("user32")]
internal static extern bool GetMonitorInfo(IntPtr hMonitor, MONITORINFO lpmi);
[DllImport("user32.dll")]
static extern bool GetCursorPos(ref Point lpPoint);
[DllImport("User32")]
internal static extern IntPtr MonitorFromWindow(IntPtr handle, int flags);
#endregion

I simplified Geoffrey's answer a bit so you don't have to p/invoke anything.
public MainWindow()
{
InitializeComponent();
winMain.SourceInitialized += new EventHandler(win_SourceInitialized);
}
void win_SourceInitialized( object sender, System.EventArgs e )
{
var handle = (new WindowInteropHelper( _attachedElement )).Handle;
var handleSource = HwndSource.FromHwnd( handle );
if ( handleSource == null )
return;
handleSource.AddHook( WindowProc );
}
private static IntPtr WindowProc( IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled )
{
switch ( msg )
{
case 0x0024:/* WM_GETMINMAXINFO */
WmGetMinMaxInfo( hwnd, lParam );
handled = true;
break;
}
return (IntPtr)0;
}
private static void WmGetMinMaxInfo( IntPtr hwnd, IntPtr lParam )
{
var mmi = (MINMAXINFO)Marshal.PtrToStructure( lParam, typeof( MINMAXINFO ) );
// Adjust the maximized size and position to fit the work area of the correct monitor
var currentScreen = System.Windows.Forms.Screen.FromHandle( hwnd );
var workArea = currentScreen.WorkingArea;
var monitorArea = currentScreen.Bounds;
mmi.ptMaxPosition.x = Math.Abs( workArea.Left - monitorArea.Left );
mmi.ptMaxPosition.y = Math.Abs( workArea.Top - monitorArea.Top );
mmi.ptMaxSize.x = Math.Abs( workArea.Right - workArea.Left );
mmi.ptMaxSize.y = Math.Abs( workArea.Bottom - workArea.Top );
Marshal.StructureToPtr( mmi, lParam, true );
}
}
[StructLayout( LayoutKind.Sequential )]
public struct MINMAXINFO
{
public POINT ptReserved;
public POINT ptMaxSize;
public POINT ptMaxPosition;
public POINT ptMinTrackSize;
public POINT ptMaxTrackSize;
};
[StructLayout( LayoutKind.Sequential )]
public struct POINT
{
/// <summary>
/// x coordinate of point.
/// </summary>
public int x;
/// <summary>
/// y coordinate of point.
/// </summary>
public int y;
/// <summary>
/// Construct a point of coordinates (x,y).
/// </summary>
public POINT( int x, int y )
{
this.x = x;
this.y = y;
}
}

This one works the best...
public MainWindow()
{
InitializeComponent();
MaxHeight = SystemParameters.VirtualScreenHeight;
MaxWidth = SystemParameters.VirtualScreenWidth;
}

I've implemented solution which considers multiple displays case based on the Mike Weinhardt answer in the thread. If you want to maximize/minimize window programmatically with your own buttons you can use this.
Solution:
// To get a handle to the specified monitor
[DllImport("user32.dll")]
private static extern IntPtr MonitorFromWindow(IntPtr hwnd, int dwFlags);
// To get the working area of the specified monitor
[DllImport("user32.dll")]
private static extern bool GetMonitorInfo(HandleRef hmonitor, [In, Out] MonitorInfoEx monitorInfo);
private static MonitorInfoEx GetMonitorInfo(Window window, IntPtr monitorPtr) {
var monitorInfo = new MonitorInfoEx();
monitorInfo.cbSize = Marshal.SizeOf(monitorInfo);
GetMonitorInfo(new HandleRef(window, monitorPtr), monitorInfo);
return monitorInfo;
}
private static void Minimize(Window window) {
if(window == null) {
return;
}
window.WindowState = WindowState.Minimized;
}
private static void Restore(Window window) {
if(window == null) {
return;
}
window.WindowState = WindowState.Normal;
window.ResizeMode = ResizeMode.CanResizeWithGrip;
}
private static void Maximize(Window window) {
window.ResizeMode = ResizeMode.NoResize;
// Get handle for nearest monitor to this window
var wih = new WindowInteropHelper(window);
// Nearest monitor to window
const int MONITOR_DEFAULTTONEAREST = 2;
var hMonitor = MonitorFromWindow(wih.Handle, MONITOR_DEFAULTTONEAREST);
// Get monitor info
var monitorInfo = GetMonitorInfo(window, hMonitor);
// Create working area dimensions, converted to DPI-independent values
var source = HwndSource.FromHwnd(wih.Handle);
if(source?.CompositionTarget == null) {
return;
}
var matrix = source.CompositionTarget.TransformFromDevice;
var workingArea = monitorInfo.rcWork;
var dpiIndependentSize =
matrix.Transform(
new Point(workingArea.Right - workingArea.Left,
workingArea.Bottom - workingArea.Top));
// Maximize the window to the device-independent working area ie
// the area without the taskbar.
window.Top = workingArea.Top;
window.Left = workingArea.Left;
window.MaxWidth = dpiIndependentSize.X;
window.MaxHeight = dpiIndependentSize.Y;
window.WindowState = WindowState.Maximized;
}
Auxilary structs:
// Rectangle (used by MONITORINFOEX)
[StructLayout(LayoutKind.Sequential)]
public struct Rect {
public int Left;
public int Top;
public int Right;
public int Bottom;
}
// Monitor information (used by GetMonitorInfo())
[StructLayout(LayoutKind.Sequential)]
public class MonitorInfoEx {
public int cbSize;
public Rect rcMonitor; // Total area
public Rect rcWork; // Working area
public int dwFlags;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x20)]
public char[] szDevice;
}
p.s. to hide default buttons use:
WindowStyle="None"
in the window XAML.

You can set MaxHeight property in the xaml code, like that:
MaxHeight="{x:Static SystemParameters.MaximizedPrimaryScreenHeight}"

I wanted the opposite (with WindowStyle=None), but reversing this solution also works for your case:
// prevent it from overlapping the taskbar
// "minimize" it
WindowStyle = WindowStyle.SingleBorderWindow;
// Maximize it again. This time it will respect the taskbar.
WindowStyle = WindowStyle.None;
WindowState = WindowState.Maximized;
// app is now borderless fullscreen, showing the taskbar again
What I did for my case:
// make it always overlap the taskbar
// From windowed to maximized without border and window bar
WindowStyle = WindowStyle.None;
WindowState = WindowState.Maximized;
// Now the window does not overlap the taskbar
Hide();
Show();
// Now it does (because it's re-opened)

Related

WPF CaptureMouse does not capture mouse events outside of the main window

I've created a trivial WPF application that tries to capture the mouse, but it stops getting mousemove events after the mouse leaves the window. Strangely, I do get a mouseup event outside the window.
I've tried a couple variants of mousecapture, but nothing works. I've also tried watching for MouseLost event and I don't see it when the mouse goes outside of the window. It just see it when I release the mouse button.
Here is my MainWindow class. I get Mouse move events as long as the mouse is in the window, but if I click and drag the mouse out of the window, I stop getting move events.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
MouseDown += MainWindow_MouseDown;
MouseUp += MainWindow_MouseUp;
MouseMove += MainWindow_MouseMove;
LostMouseCapture += MainWindow_LostMouseCapture;
}
private void MainWindow_LostMouseCapture(object sender, MouseEventArgs e)
{
Debug.WriteLine("Lost Mouse");
}
private void MainWindow_MouseMove(object sender, MouseEventArgs e)
{
Debug.WriteLine("P: " + Mouse.GetPosition(this));
}
private void MainWindow_MouseUp(object sender, MouseButtonEventArgs e)
{
Debug.WriteLine("Releasing");
ReleaseMouseCapture();
}
private void MainWindow_MouseDown(object sender, MouseButtonEventArgs e)
{
Debug.WriteLine("Capturing");
CaptureMouse();
// This does not work either: Mouse.Capture(this, CaptureMode.SubTree);
}
}
I am expecting to see all the mousemove events so that I can drag the window, but I only see the mouseup event outside, and the mousemoves only happen if the cursor is inside the window.
After some research, I have an answer for this question. WPF applications can't see the mouse when it leaves the app window, so if you want to have some custom dragging behavior it is necessary to use interrop to capture the mouse globally. I created the following class to enable DPI-aware window dragging across multiple monitors for any WPF window:
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Forms;
using System.Windows.Input;
using System.Windows.Media;
[assembly: DisableDpiAwareness]
namespace Talisman
{
// --------------------------------------------------------------------------
/// <summary>
/// Enables dragging of a WPF window in a way that is per-monitor DPI sensitive.
///
/// HOW TO USE
/// Add a DraggingLogic member variable and put this code in your window constructor:
/// _draggingLogic = new DraggingLogic(this);
///
/// If you want to do special things when the window moves or when it is clicked:
/// _draggingLogic.OnPositionChanged += (xm, ym) => {/* whatever you want here */};
/// _draggingLogic.OnClick += () => {/* whatever you want here */};
///
/// </summary>
// --------------------------------------------------------------------------
public class DraggingLogic
{
public event Action<double, double> OnPositionChanged;
public event Action OnClick;
/// <summary>
/// Factor to convert Horizontal screen coordinates
/// </summary>
public double DpiCorrectionX { get; set; }
/// <summary>
/// Factor to convertVertical screen coordinates
/// </summary>
public double DpiCorrectionY { get; set; }
public double WpfDpiX { get; set; }
public double WpfDpiY { get; set; }
#region INTERROP - Mouse interaction
private static int _mouseHookHandle;
private delegate int HookProc(int nCode, int wParam, IntPtr lParam);
private static HookProc _mouseDelegate;
private const int WH_MOUSE_LL = 14;
private const int WM_LBUTTONUP = 0x0202;
private const int WM_MOUSEMOVE = 0x0200;
[StructLayout(LayoutKind.Sequential)]
private struct POINT
{
public int x;
public int y;
}
[StructLayout(LayoutKind.Sequential)]
private struct MSLLHOOKSTRUCT
{
public POINT pt;
public uint mouseData;
public uint flags;
public uint time;
public IntPtr dwExtraInfo;
}
[DllImport("user32.dll", CharSet = CharSet.Auto,
CallingConvention = CallingConvention.StdCall, SetLastError = true)]
private static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, int dwThreadId);
[DllImport("user32.dll", CharSet = CharSet.Auto,
CallingConvention = CallingConvention.StdCall, SetLastError = true)]
private static extern int UnhookWindowsHookEx(int idHook);
[DllImport("user32.dll", CharSet = CharSet.Auto,
CallingConvention = CallingConvention.StdCall)]
private static extern int CallNextHookEx(int idHook, int nCode, int wParam, IntPtr lParam);
[DllImport("kernel32.dll")]
public static extern IntPtr GetModuleHandle(string name);
#endregion
#region INTERROP - DPI
[DllImport("User32.dll")]
private static extern IntPtr MonitorFromPoint([In]System.Drawing.Point pt, [In]uint dwFlags);
[DllImport("Shcore.dll")]
private static extern IntPtr GetDpiForMonitor([In]IntPtr hmonitor, [In]DpiType dpiType, [Out]out uint dpiX, [Out]out uint dpiY);
[DllImport("Shcore.dll")]
private static extern IntPtr SetProcessDpiAwareness([In]DpiAwareness dpiAwareness);
public enum DpiType
{
Effective = 0,
Angular = 1,
Raw = 2,
}
public enum DpiAwareness
{
Unaware = 0,
System = 1,
PerMonitor = 2,
}
#endregion
Screen _currentScreen;
Window _dragMe;
bool _dragging = false;
double _dragDelta = 0;
Point _lastMousePosition;
Point _mouseStickyPosition;
// --------------------------------------------------------------------------
/// <summary>
/// Get resource text using a loose naming scheme
/// </summary>
// --------------------------------------------------------------------------
public DraggingLogic(Window dragme)
{
var result = SetProcessDpiAwareness(DpiAwareness.PerMonitor);
dragme.MouseDown += HandleMouseDown;
dragme.MouseMove += HandleMouseMove;
dragme.MouseUp += HandleMouseUp;
dragme.Loaded += Dragme_Loaded;
_dragMe = dragme;
}
// --------------------------------------------------------------------------
/// <summary>
/// Dragme_Loaded - can't find DPI until the window is loaded
/// </summary>
// --------------------------------------------------------------------------
private void Dragme_Loaded(object sender, RoutedEventArgs e)
{
var source = PresentationSource.FromVisual(_dragMe);
WpfDpiX = 96.0 * source.CompositionTarget.TransformToDevice.M11;
WpfDpiY = 96.0 * source.CompositionTarget.TransformToDevice.M22;
}
// --------------------------------------------------------------------------
/// <summary>
/// Figure out scaling for the DPI on a certain monitor
/// </summary>
// --------------------------------------------------------------------------
public void CalculateDpiScaleFactors(Screen screen, DpiType dpiType)
{
var point = new System.Drawing.Point(screen.Bounds.Left + 1, screen.Bounds.Top + 1);
var monitor = MonitorFromPoint(point, 2/*MONITOR_DEFAULTTONEAREST*/);
Debug.WriteLine($"Monitor: {monitor}");
var result = GetDpiForMonitor(monitor, dpiType, out var monitorDpiX, out var monitorDpiY);
if(result != IntPtr.Zero)
{
monitorDpiX = monitorDpiY = 96;
}
DpiCorrectionX = 96.0 / monitorDpiX;
DpiCorrectionY = 96.0 / monitorDpiY;
}
// --------------------------------------------------------------------------
/// <summary>
/// Mouse Down
/// </summary>
// --------------------------------------------------------------------------
private void HandleMouseDown(object sender, MouseButtonEventArgs e)
{
var window = sender as Window;
if (e.LeftButton == MouseButtonState.Pressed)
{
_dragging = true;
_dragDelta = 0;
_mouseStickyPosition = Mouse.GetPosition(window);
_lastMousePosition = window.PointToScreen(Mouse.GetPosition(window));
_currentScreen = GetScreenFromPoint(_lastMousePosition);
CalculateDpiScaleFactors(_currentScreen, DpiType.Effective);
CaptureGlobalMouse();
e.Handled = true;
}
}
// --------------------------------------------------------------------------
/// <summary>
/// Mouse Move
/// </summary>
// --------------------------------------------------------------------------
private void HandleMouseMove(object sender, System.Windows.Input.MouseEventArgs e)
{
if (_dragging)
{
e.Handled = true;
}
}
// --------------------------------------------------------------------------
/// <summary>
/// HandleGlobalMouseMove
/// </summary>
// --------------------------------------------------------------------------
private void HandleGlobalMouseMove(Point mouseLocation)
{
var newPosition = mouseLocation; // This arrives without DPI correction
var screen = GetScreenFromPoint(newPosition);
// We need to do some fix up when we drag to another screen because
// the DPI on the other screen could be different
if(screen != null && screen.DeviceName != _currentScreen.DeviceName)
{
CalculateDpiScaleFactors(screen, DpiType.Effective);
_lastMousePosition = newPosition;
// Move the window to match the mouse position
_dragMe.Left = (newPosition.X - _mouseStickyPosition.X)* DpiCorrectionX;
_dragMe.Top = (newPosition.Y - _mouseStickyPosition.Y)* DpiCorrectionY;
_currentScreen = screen;
}
var xMove = (newPosition.X - _lastMousePosition.X)* DpiCorrectionX;
var yMove = (newPosition.Y - _lastMousePosition.Y)* DpiCorrectionY;
_dragMe.Left += xMove;
_dragMe.Top += yMove;
_dragDelta += (_lastMousePosition - newPosition).Length;
_lastMousePosition = newPosition;
OnPositionChanged?.Invoke(xMove, yMove);
}
// --------------------------------------------------------------------------
/// <summary>
/// GetScreenFromPoint - return the screen from a raw point (presumably mouse coordinate)
/// </summary>
// --------------------------------------------------------------------------
public Screen GetScreenFromPoint(Point point)
{
foreach (Screen screen in Screen.AllScreens)
{
if (screen.ContainsPoint(point.X, point.Y)) return screen;
}
return null;
}
// --------------------------------------------------------------------------
/// <summary>
/// Mouse Up
/// </summary>
// --------------------------------------------------------------------------
private void HandleMouseUp(object sender, MouseButtonEventArgs e)
{
if (_dragging)
{
var window = sender as Window;
// if the user didn't actually drag, then we want to treat this as a click
if (_dragDelta < 3)
{
OnClick?.Invoke();
}
_dragging = false;
ReleaseGlobalMouse();
if(e != null) e.Handled = true;
}
}
// --------------------------------------------------------------------------
/// <summary>
/// MouseHookProc- allows us to handle global mouse events
/// </summary>
// --------------------------------------------------------------------------
private int MouseHookProc(int nCode, int wParam, IntPtr lParam)
{
if (nCode >= 0)
{
switch (wParam)
{
case WM_LBUTTONUP: HandleMouseUp(this, null); break;
case WM_MOUSEMOVE:
{
var mouseHookStruct = (MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(MSLLHOOKSTRUCT));
HandleGlobalMouseMove(new Point(mouseHookStruct.pt.x, mouseHookStruct.pt.y));
break;
}
}
}
return CallNextHookEx(_mouseHookHandle, nCode, wParam, lParam);
}
// --------------------------------------------------------------------------
/// <summary>
/// CaptureGlobalMouse
/// </summary>
// --------------------------------------------------------------------------
private void CaptureGlobalMouse()
{
if (_mouseHookHandle == 0)
{
_mouseDelegate = MouseHookProc;
_mouseHookHandle = SetWindowsHookEx(WH_MOUSE_LL,
_mouseDelegate,
GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName),
0);
if (_mouseHookHandle == 0)
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
}
// --------------------------------------------------------------------------
/// <summary>
/// ReleaseGlobalMouse
/// </summary>
// --------------------------------------------------------------------------
private void ReleaseGlobalMouse()
{
if (_mouseHookHandle != 0)
{
int result = UnhookWindowsHookEx(_mouseHookHandle);
_mouseHookHandle = 0;
_mouseDelegate = null;
if (result == 0)
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
}
}
}
you have to use mouse capture on the object itself so in MouseDown you want ((IInputElement)sender).CaptureMouse() and in MouseUp ((IInputElement)sender).ReleaseMouseCapture().
Alternatively, you can also use MainWindow.CaptureMouse().
private void MainWindow_MouseUp(object sender, MouseButtonEventArgs e)
{
Debug.WriteLine("Releasing");
((IInputElement)sender).ReleaseMouseCapture()
}
private void MainWindow_MouseDown(object sender, MouseButtonEventArgs e)
{
Debug.WriteLine("Capturing");
((IInputElement)sender).CaptureMouse()
// This does not work either: Mouse.Capture(this, CaptureMode.SubTree);
}

Execute program capturing screenshots from windows service

I'm writing a windows service, which will manage some agent program capturing screenshots. Agent program works fine, no problems at all - it just takes screenshot and saves it into bmp file.
But when i try to execute this agent program from my service - it doesnt't work, all i get is black pictures (as if i tried to capture screenshot directly from my service)
My code inside service is:
ServiceStatus.dwCurrentState = SERVICE_RUNNING;
SetServiceStatus (hStatus, &ServiceStatus);
SHELLEXECUTEINFO seInfo;
seInfo.cbSize = sizeof(SHELLEXECUTEINFO);
seInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
seInfo.hwnd = NULL;
seInfo.lpVerb = TEXT("open");
seInfo.lpFile = TEXT("D:\\dev\\work\\agent.exe");
seInfo.lpDirectory = TEXT("D:\\dev\\work\\");
seInfo.nShow = SW_SHOWNORMAL;
seInfo.hInstApp = NULL;
ShellExecuteEx(&seInfo);
I tried to change service properties - to allow it interacting with desktop, change service user from SYSTEM to my local account - nothing helpes.
What can i do to make it work properely?
UPD. Well, i suppose, this happenes because my service executes program in session0. I tried CreateProcess() and CreateProcessAsUser() as well - no result. So how can i create procees NOT in session0?
Aliostad did all the heavy lifting for this. Take a look at his code, it's an excellent start. Keep in mind, you'll need a separate app to take the screenshot since a new process has to be kicked off while impersonating the logged in user.
https://stackoverflow.com/a/4147868/125406
Here is my version of it (fully based on Aliostad's code). It adds command line params, a wait for the process to finish and return of the exit code.
public static class ProcessAsCurrentUser
{
/// <summary>
/// Connection state of a session.
/// </summary>
public enum ConnectionState
{
/// <summary>
/// A user is logged on to the session.
/// </summary>
Active,
/// <summary>
/// A client is connected to the session.
/// </summary>
Connected,
/// <summary>
/// The session is in the process of connecting to a client.
/// </summary>
ConnectQuery,
/// <summary>
/// This session is shadowing another session.
/// </summary>
Shadowing,
/// <summary>
/// The session is active, but the client has disconnected from it.
/// </summary>
Disconnected,
/// <summary>
/// The session is waiting for a client to connect.
/// </summary>
Idle,
/// <summary>
/// The session is listening for connections.
/// </summary>
Listening,
/// <summary>
/// The session is being reset.
/// </summary>
Reset,
/// <summary>
/// The session is down due to an error.
/// </summary>
Down,
/// <summary>
/// The session is initializing.
/// </summary>
Initializing
}
[StructLayout(LayoutKind.Sequential)]
class SECURITY_ATTRIBUTES
{
public int nLength;
public IntPtr lpSecurityDescriptor;
public int bInheritHandle;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
struct STARTUPINFO
{
public Int32 cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public Int32 dwX;
public Int32 dwY;
public Int32 dwXSize;
public Int32 dwYSize;
public Int32 dwXCountChars;
public Int32 dwYCountChars;
public Int32 dwFillAttribute;
public Int32 dwFlags;
public Int16 wShowWindow;
public Int16 cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
[StructLayout(LayoutKind.Sequential)]
internal struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public int dwProcessId;
public int dwThreadId;
}
enum LOGON_TYPE
{
LOGON32_LOGON_INTERACTIVE = 2,
LOGON32_LOGON_NETWORK,
LOGON32_LOGON_BATCH,
LOGON32_LOGON_SERVICE,
LOGON32_LOGON_UNLOCK = 7,
LOGON32_LOGON_NETWORK_CLEARTEXT,
LOGON32_LOGON_NEW_CREDENTIALS
}
enum LOGON_PROVIDER
{
LOGON32_PROVIDER_DEFAULT,
LOGON32_PROVIDER_WINNT35,
LOGON32_PROVIDER_WINNT40,
LOGON32_PROVIDER_WINNT50
}
[Flags]
enum CreateProcessFlags : uint
{
CREATE_BREAKAWAY_FROM_JOB = 0x01000000,
CREATE_DEFAULT_ERROR_MODE = 0x04000000,
CREATE_NEW_CONSOLE = 0x00000010,
CREATE_NEW_PROCESS_GROUP = 0x00000200,
CREATE_NO_WINDOW = 0x08000000,
CREATE_PROTECTED_PROCESS = 0x00040000,
CREATE_PRESERVE_CODE_AUTHZ_LEVEL = 0x02000000,
CREATE_SEPARATE_WOW_VDM = 0x00000800,
CREATE_SHARED_WOW_VDM = 0x00001000,
CREATE_SUSPENDED = 0x00000004,
CREATE_UNICODE_ENVIRONMENT = 0x00000400,
DEBUG_ONLY_THIS_PROCESS = 0x00000002,
DEBUG_PROCESS = 0x00000001,
DETACHED_PROCESS = 0x00000008,
EXTENDED_STARTUPINFO_PRESENT = 0x00080000,
INHERIT_PARENT_AFFINITY = 0x00010000
}
[StructLayout(LayoutKind.Sequential)]
public struct WTS_SESSION_INFO
{
public int SessionID;
[MarshalAs(UnmanagedType.LPTStr)] public string WinStationName;
public ConnectionState State;
}
[DllImport("wtsapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern Int32 WTSEnumerateSessions(IntPtr hServer, int reserved, int version,
ref IntPtr sessionInfo, ref int count);
[DllImport("advapi32.dll", EntryPoint = "CreateProcessAsUserW", SetLastError = true, CharSet = CharSet.Auto)]
static extern bool CreateProcessAsUser(
IntPtr hToken,
string lpApplicationName,
string lpCommandLine,
IntPtr lpProcessAttributes,
IntPtr lpThreadAttributes,
bool bInheritHandles,
UInt32 dwCreationFlags,
IntPtr lpEnvironment,
string lpCurrentDirectory,
ref STARTUPINFO lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern Int32 WaitForSingleObject(IntPtr handle, Int32 wait);
public const Int32 INFINITE = -1;
public const Int32 WAIT_ABANDONED = 0x80;
public const Int32 WAIT_OBJECT_0 = 0x00;
public const Int32 WAIT_TIMEOUT = 0x102;
public const Int32 WAIT_FAILED = -1;
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool GetExitCodeProcess(IntPtr hProcess, out uint exitCode);
[DllImport("wtsapi32.dll")]
public static extern void WTSFreeMemory(IntPtr memory);
[DllImport("kernel32.dll")]
private static extern UInt32 WTSGetActiveConsoleSessionId();
[DllImport("wtsapi32.dll", SetLastError = true)]
static extern int WTSQueryUserToken(UInt32 sessionId, out IntPtr Token);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public extern static bool DuplicateTokenEx(
IntPtr hExistingToken,
uint dwDesiredAccess,
IntPtr lpTokenAttributes,
int ImpersonationLevel,
int TokenType,
out IntPtr phNewToken);
private const int TokenImpersonation = 2;
private const int SecurityIdentification = 1;
private const int MAXIMUM_ALLOWED = 0x2000000;
private const int TOKEN_DUPLICATE = 0x2;
private const int TOKEN_QUERY = 0x00000008;
/// <summary>
/// Launches a process for the current logged on user if there are any.
/// If none, return false as well as in case of
///
/// ##### !!! BEWARE !!! #### ------------------------------------------
/// This code will only work when running in a windows service (where it is really needed)
/// so in case you need to test it, it needs to run in the service. Reason
/// is a security privileg which only services have (SE_??? something, cant remember)!
/// </summary>
/// <param name="processExe"></param>
/// <returns></returns>
public static uint CreateProcessAsCurrentUser(string processExe, string commandLine)
{
IntPtr duplicate = new IntPtr();
STARTUPINFO info = new STARTUPINFO();
PROCESS_INFORMATION procInfo = new PROCESS_INFORMATION();
Debug.WriteLine(string.Format("CreateProcessAsCurrentUser. processExe: " + processExe));
IntPtr p = GetCurrentUserToken();
bool result = DuplicateTokenEx(p, MAXIMUM_ALLOWED | TOKEN_QUERY | TOKEN_DUPLICATE, IntPtr.Zero,
SecurityIdentification, SecurityIdentification, out duplicate);
Debug.WriteLine(string.Format("DuplicateTokenEx result: {0}", result));
Debug.WriteLine(string.Format("duplicate: {0}", duplicate));
if (result)
{
//NOTE: CREATE_NO_WINDOW will hide the console
//If there are commandline options, pass them AND the exe in
//commandLine and leave processExe empty.
result = CreateProcessAsUser(duplicate, processExe, commandLine,
// IntPtr.Zero, IntPtr.Zero, false, (UInt32) CreateProcessFlags.CREATE_NEW_CONSOLE, IntPtr.Zero, null,
IntPtr.Zero, IntPtr.Zero, false, (UInt32)CreateProcessFlags.CREATE_NO_WINDOW, IntPtr.Zero, null,
ref info, out procInfo);
Debug.WriteLine(string.Format("CreateProcessAsUser result: {0}", result));
}
if (p.ToInt32() != 0)
{
Marshal.Release(p);
Debug.WriteLine(string.Format("Released handle p: {0}", p));
}
if (duplicate.ToInt32() != 0)
{
Marshal.Release(duplicate);
Debug.WriteLine(string.Format("Released handle duplicate: {0}", duplicate));
}
//Wait for the process to complete
WaitForSingleObject(procInfo.hProcess, (int)INFINITE);
//Get and return the exit code
uint exitcode;
GetExitCodeProcess(procInfo.hProcess, out exitcode);
return exitcode;
}
public static int GetCurrentSessionId()
{
uint sessionId = WTSGetActiveConsoleSessionId();
Debug.WriteLine(string.Format("sessionId: {0}", sessionId));
if (sessionId == 0xFFFFFFFF)
return -1;
else
return (int)sessionId;
}
public static bool IsUserLoggedOn()
{
List<WTS_SESSION_INFO> wtsSessionInfos = ListSessions();
Debug.WriteLine(string.Format("Number of sessions: {0}", wtsSessionInfos.Count));
int activeSessionCount = 0;
foreach (var session in wtsSessionInfos)
{
if (session.State == ConnectionState.Active)
activeSessionCount++;
}
//return wtsSessionInfos.Where(x => x.State == ConnectionState.Active).Count() > 0;
return activeSessionCount > 0;
}
private static IntPtr GetCurrentUserToken()
{
List<WTS_SESSION_INFO> wtsSessionInfos = ListSessions();
int sessionId = 0;
foreach (var session in wtsSessionInfos)
{
if (session.State == ConnectionState.Active)
{
sessionId = session.SessionID;
break;
}
}
//Old Linq method
//int sessionId = wtsSessionInfos.Where(x => x.State == ConnectionState.Active).FirstOrDefault().SessionID;
//int sessionId = GetCurrentSessionId();
Debug.WriteLine(string.Format("sessionId: {0}", sessionId));
if (sessionId == int.MaxValue)
{
return IntPtr.Zero;
}
else
{
IntPtr p = new IntPtr();
int result = WTSQueryUserToken((UInt32)sessionId, out p);
Debug.WriteLine(string.Format("WTSQueryUserToken result: {0}", result));
Debug.WriteLine(string.Format("WTSQueryUserToken p: {0}", p));
return p;
}
}
public static List<WTS_SESSION_INFO> ListSessions()
{
IntPtr server = IntPtr.Zero;
List<WTS_SESSION_INFO> ret = new List<WTS_SESSION_INFO>();
try
{
IntPtr ppSessionInfo = IntPtr.Zero;
Int32 count = 0;
Int32 retval = WTSEnumerateSessions(IntPtr.Zero, 0, 1, ref ppSessionInfo, ref count);
Int32 dataSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO));
Int64 current = (int)ppSessionInfo;
if (retval != 0)
{
for (int i = 0; i < count; i++)
{
WTS_SESSION_INFO si =
(WTS_SESSION_INFO)Marshal.PtrToStructure((System.IntPtr)current,
typeof(WTS_SESSION_INFO));
current += dataSize;
ret.Add(si);
}
WTSFreeMemory(ppSessionInfo);
}
}
catch (Exception exception)
{
Debug.WriteLine(exception.ToString());
}
return ret;
}
}
We pass a filename as a parameter to the screenshot app and then wait for it to complete. The service then picks up the file and processes and deletes it.
We also found another way around this using named pipes. I'm less familiar with that solution.

Properly maximizing WPF window with WindowStyle=None

There are two problems with WPF windows when the WindowStyle=None option is used.
The window covers the Taskbar when maximized.
Once maximized, the window cannot be dragged down to unmaximize.
How can these problems be corrected?
Preferably without using Windows.Forms.
There are other answers to these problems online. However none of them take into acount how the solution will perform on setups with multiple monitors. Especially if the primary monitor is not the left-most in the setup.
I designed this code taking into account single and multiple monitors setups.
This solution also does not bring in Windows.Forms as a reference, it uses unmanagaged calls.
XAML
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Background="AliceBlue" WindowStyle="None" Height="350" Width="525" SourceInitialized="Window_SourceInitialized">
<Grid>
<Rectangle Name="rctHeader" Height="40" VerticalAlignment="Top" Fill="CadetBlue" PreviewMouseLeftButtonDown="rctHeader_PreviewMouseLeftButtonDown" PreviewMouseLeftButtonUp="rctHeader_PreviewMouseLeftButtonUp" PreviewMouseMove="rctHeader_PreviewMouseMove"/>
</Grid>
</Window>
Code Behind
using System.Runtime.InteropServices;
using System.Windows.Interop;
private bool mRestoreIfMove = false;
public MainWindow()
{
InitializeComponent();
}
void Window_SourceInitialized(object sender, EventArgs e)
{
IntPtr mWindowHandle = (new WindowInteropHelper(this)).Handle;
HwndSource.FromHwnd(mWindowHandle).AddHook(new HwndSourceHook(WindowProc));
}
private static System.IntPtr WindowProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
switch (msg)
{
case 0x0024:
WmGetMinMaxInfo(hwnd, lParam);
break;
}
return IntPtr.Zero;
}
private static void WmGetMinMaxInfo(System.IntPtr hwnd, System.IntPtr lParam)
{
POINT lMousePosition;
GetCursorPos(out lMousePosition);
IntPtr lPrimaryScreen = MonitorFromPoint(new POINT(0, 0), MonitorOptions.MONITOR_DEFAULTTOPRIMARY);
MONITORINFO lPrimaryScreenInfo = new MONITORINFO();
if (GetMonitorInfo(lPrimaryScreen, lPrimaryScreenInfo) == false)
{
return;
}
IntPtr lCurrentScreen = MonitorFromPoint(lMousePosition, MonitorOptions.MONITOR_DEFAULTTONEAREST);
MINMAXINFO lMmi = (MINMAXINFO)Marshal.PtrToStructure(lParam, typeof(MINMAXINFO));
if (lPrimaryScreen.Equals(lCurrentScreen) == true)
{
lMmi.ptMaxPosition.X = lPrimaryScreenInfo.rcWork.Left;
lMmi.ptMaxPosition.Y = lPrimaryScreenInfo.rcWork.Top;
lMmi.ptMaxSize.X = lPrimaryScreenInfo.rcWork.Right - lPrimaryScreenInfo.rcWork.Left;
lMmi.ptMaxSize.Y = lPrimaryScreenInfo.rcWork.Bottom - lPrimaryScreenInfo.rcWork.Top;
}
else
{
lMmi.ptMaxPosition.X = lPrimaryScreenInfo.rcMonitor.Left;
lMmi.ptMaxPosition.Y = lPrimaryScreenInfo.rcMonitor.Top;
lMmi.ptMaxSize.X = lPrimaryScreenInfo.rcMonitor.Right - lPrimaryScreenInfo.rcMonitor.Left;
lMmi.ptMaxSize.Y = lPrimaryScreenInfo.rcMonitor.Bottom - lPrimaryScreenInfo.rcMonitor.Top;
}
Marshal.StructureToPtr(lMmi, lParam, true);
}
private void SwitchWindowState()
{
switch (WindowState)
{
case WindowState.Normal:
{
WindowState = WindowState.Maximized;
break;
}
case WindowState.Maximized:
{
WindowState = WindowState.Normal;
break;
}
}
}
private void rctHeader_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (e.ClickCount == 2)
{
if ((ResizeMode == ResizeMode.CanResize) || (ResizeMode == ResizeMode.CanResizeWithGrip))
{
SwitchWindowState();
}
return;
}
else if (WindowState == WindowState.Maximized)
{
mRestoreIfMove = true;
return;
}
DragMove();
}
private void rctHeader_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
mRestoreIfMove = false;
}
private void rctHeader_PreviewMouseMove(object sender, MouseEventArgs e)
{
if (mRestoreIfMove)
{
mRestoreIfMove = false;
double percentHorizontal = e.GetPosition(this).X / ActualWidth;
double targetHorizontal = RestoreBounds.Width * percentHorizontal;
double percentVertical = e.GetPosition(this).Y / ActualHeight;
double targetVertical = RestoreBounds.Height * percentVertical;
WindowState = WindowState.Normal;
POINT lMousePosition;
GetCursorPos(out lMousePosition);
Left = lMousePosition.X - targetHorizontal;
Top = lMousePosition.Y - targetVertical;
DragMove();
}
}
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GetCursorPos(out POINT lpPoint);
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr MonitorFromPoint(POINT pt, MonitorOptions dwFlags);
enum MonitorOptions : uint
{
MONITOR_DEFAULTTONULL = 0x00000000,
MONITOR_DEFAULTTOPRIMARY = 0x00000001,
MONITOR_DEFAULTTONEAREST = 0x00000002
}
[DllImport("user32.dll")]
static extern bool GetMonitorInfo(IntPtr hMonitor, MONITORINFO lpmi);
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int X;
public int Y;
public POINT(int x, int y)
{
this.X = x;
this.Y = y;
}
}
[StructLayout(LayoutKind.Sequential)]
public struct MINMAXINFO
{
public POINT ptReserved;
public POINT ptMaxSize;
public POINT ptMaxPosition;
public POINT ptMinTrackSize;
public POINT ptMaxTrackSize;
};
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
public class MONITORINFO
{
public int cbSize = Marshal.SizeOf(typeof(MONITORINFO));
public RECT rcMonitor = new RECT();
public RECT rcWork = new RECT();
public int dwFlags = 0;
}
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left, Top, Right, Bottom;
public RECT(int left, int top, int right, int bottom)
{
this.Left = left;
this.Top = top;
this.Right = right;
this.Bottom = bottom;
}
}
I have a nice quick and dirty solution. Try following code when maximize your none bordered window:
if (WindowState == WindowState.Normal)
{
WindowStyle = WindowStyle.SingleBorderWindow;
WindowState = WindowState.Maximized;
WindowStyle = WindowStyle.None;
}
The trick is to set the WindowStyle to SingleBorderWindow then maximize the window and set it back to None.
Such a nice code leebickmtu!
I had a little issue with multiple monitors, in windows 10 : since there is a taskBar on each screen, if you maximize your window on a secondary screen his taskbar become hidden.
I just modify a bit this method, in order to have relative position from any screen :
private static void WmGetMinMaxInfo(System.IntPtr hwnd, System.IntPtr lParam)
{
POINT lMousePosition;
GetCursorPos(out lMousePosition);
IntPtr lCurrentScreen = MonitorFromPoint(lMousePosition, MonitorOptions.MONITOR_DEFAULTTONEAREST);
MINMAXINFO lMmi = (MINMAXINFO)Marshal.PtrToStructure(lParam, typeof(MINMAXINFO));
MONITORINFO lCurrentScreenInfo = new MONITORINFO();
if (GetMonitorInfo(lCurrentScreen, lCurrentScreenInfo) == false)
{
return;
}
//Position relative pour notre fenĂȘtre
lMmi.ptMaxPosition.X = lCurrentScreenInfo.rcWork.Left - lCurrentScreenInfo.rcMonitor.Left;
lMmi.ptMaxPosition.Y = lCurrentScreenInfo.rcWork.Top - lCurrentScreenInfo.rcMonitor.Top;
lMmi.ptMaxSize.X = lCurrentScreenInfo.rcWork.Right - lCurrentScreenInfo.rcWork.Left;
lMmi.ptMaxSize.Y = lCurrentScreenInfo.rcWork.Bottom - lCurrentScreenInfo.rcWork.Top;
Marshal.StructureToPtr(lMmi, lParam, true);
}
Hope this help...
leebickmtu's answer is basically correct, but has some extraneous code and picks the monitor based one where the cursor is, rather than where the window is. That would have the wrong behavior if the mouse is on a different monitor and the user presses Win+Up to maximize. We should be using MonitorFromWindow to identify the monitor to maximize to.
Put the following in your window codebehind:
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
((HwndSource)PresentationSource.FromVisual(this)).AddHook(HookProc);
}
public static IntPtr HookProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == WM_GETMINMAXINFO)
{
// We need to tell the system what our size should be when maximized. Otherwise it will
// cover the whole screen, including the task bar.
MINMAXINFO mmi = (MINMAXINFO)Marshal.PtrToStructure(lParam, typeof(MINMAXINFO));
// Adjust the maximized size and position to fit the work area of the correct monitor
IntPtr monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
if (monitor != IntPtr.Zero)
{
MONITORINFO monitorInfo = new MONITORINFO();
monitorInfo.cbSize = Marshal.SizeOf(typeof(MONITORINFO));
GetMonitorInfo(monitor, ref monitorInfo);
RECT rcWorkArea = monitorInfo.rcWork;
RECT rcMonitorArea = monitorInfo.rcMonitor;
mmi.ptMaxPosition.X = Math.Abs(rcWorkArea.Left - rcMonitorArea.Left);
mmi.ptMaxPosition.Y = Math.Abs(rcWorkArea.Top - rcMonitorArea.Top);
mmi.ptMaxSize.X = Math.Abs(rcWorkArea.Right - rcWorkArea.Left);
mmi.ptMaxSize.Y = Math.Abs(rcWorkArea.Bottom - rcWorkArea.Top);
}
Marshal.StructureToPtr(mmi, lParam, true);
}
return IntPtr.Zero;
}
private const int WM_GETMINMAXINFO = 0x0024;
private const uint MONITOR_DEFAULTTONEAREST = 0x00000002;
[DllImport("user32.dll")]
private static extern IntPtr MonitorFromWindow(IntPtr handle, uint flags);
[DllImport("user32.dll")]
private static extern bool GetMonitorInfo(IntPtr hMonitor, ref MONITORINFO lpmi);
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
public RECT(int left, int top, int right, int bottom)
{
this.Left = left;
this.Top = top;
this.Right = right;
this.Bottom = bottom;
}
}
[StructLayout(LayoutKind.Sequential)]
public struct MONITORINFO
{
public int cbSize;
public RECT rcMonitor;
public RECT rcWork;
public uint dwFlags;
}
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int X;
public int Y;
public POINT(int x, int y)
{
this.X = x;
this.Y = y;
}
}
[StructLayout(LayoutKind.Sequential)]
public struct MINMAXINFO
{
public POINT ptReserved;
public POINT ptMaxSize;
public POINT ptMaxPosition;
public POINT ptMinTrackSize;
public POINT ptMaxTrackSize;
}
If only one monitor is used another simple approach is to set the maximum height of the window. The System.Windows.SystemParameters class provides some usefull values e.g. PrimaryScreenHeight or MaximizedPrimaryScreenHeight.
In my sample code i use MaximizedPrimaryScreenHeight and subtract the ResizeBorderThickness i set in WindowChrome.
using System.Windows;
using System.Windows.Shell;
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Thickness resizeBorderThickness = WindowChrome.GetWindowChrome(this).ResizeBorderThickness;
this.MaxHeight = SystemParameters.MaximizedPrimaryScreenHeight - resizeBorderThickness.Top - resizeBorderThickness.Bottom;
}
}
Building on Dennis' excellent solution:
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
HandleWinMaximized();
StateChanged += MainWindow_StateChanged;
}
private void MainWindow_StateChanged(object sender, EventArgs e)
{
HandleWinMaximized();
}
private void HandleWinMaximized()
{
if (WindowState == WindowState.Maximized)
{
WindowStyle = WindowStyle.SingleBorderWindow;
WindowStyle = WindowStyle.None;
}
}

WPF: mouse leave event doesn't trigger with mouse down

I'm having an issue with mouse enter / leave events. When mouse button is pressed and hold with cursor inside the control and then cursor is moved out of the control fast enough this events don't trigger.
Could you please advice me why it happens? Is there any way to obtain these events properly?
Please check the sample project to see it in action: https://www.dropbox.com/s/w5ra2vzegjtauso/SampleApp.zip
Update. I have found the same problem here without an answer. Started bounty there.
EDIT: After Sisyphe correctly noted that the behavior did not work for elements with mouse interaction, I have rewritten the code.
The behavior can be attached to a window or any other FrameworkElement. By default, all contained elements will be monitored for MouseLeave while the left mouse button is down, and the handlers executed. The behavior can also be applied just to its associated element by setting MonitorSubControls="False".
What the behavior does, basically (refer to the comments in the code for more detail):
Is only "active" if left mouse button is pressed
Watches for mouse position changes from in- to outside of an element. In this case, executes the event handlers.
Known limitations (could all be resolved with some more effort, I reckon, but don't seem too important to me):
Does not execute handlers for transitions to a contained element ("inner" boundaries)
Does not guarantee correct order of execution of the handlers
Does not resolve that for slow transitions to the outside of the window, e.LeftButton is reported as released (bug?).
I decided not to use the Win32 hook and instead using a timer, who will not fire more than about every 0.15 seconds (despite a smaller interval set, clock drift?). For fast mouse movements, the evaluated points could be too far apart and miss an element that is just flitted across.
This script produces the output below: With the behavior attached to the window, moving inside the orangeBorder (leaves blueBorder by inner boundary with mouse button released: 0), pressing left mousebutton inside the orange border and moving (fast) outside the window executes the leave handlers (1 - 4). Releasing the mouse button outside the window, moving back in over the goldTextBox (5), pressing left mousebutton in the textbox, leaving (fast or slow) outside the window again executes the correct handlers (6 - 9).
Xaml (example):
<Window x:Class="WpfApplication1.MouseLeaveControlWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:beh="clr-namespace:WpfApplication1.Behavior"
Title="MouseLeaveControlWindow" Height="300" Width="300" x:Name="window" MouseLeave="OnMouseLeave">
<i:Interaction.Behaviors>
<beh:MonitorMouseLeaveBehavior />
</i:Interaction.Behaviors>
<Grid x:Name="grid" MouseLeave="OnMouseLeave" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Border x:Name="blueBorder" MouseLeave="OnMouseLeave" Background="SteelBlue" Margin="50" Grid.RowSpan="2" />
<Border x:Name="orangeBorder" MouseLeave="OnMouseLeave" Background="DarkOrange" Margin="70, 70, 70, 20" />
<TextBox x:Name="goldTextBox" MouseLeave="OnMouseLeave" Background="Gold" Margin="70, 20, 70, 70" Grid.Row="1" Text="I'm a TextBox" />
</Grid>
</Window>
Code behind (just for debug purpose):
public partial class MouseLeaveControlWindow : Window
{
public MouseLeaveControlWindow()
{
InitializeComponent();
}
private int i = 0;
private void OnMouseLeave(object sender, MouseEventArgs e)
{
FrameworkElement fe = (FrameworkElement)sender;
if (e.LeftButton == MouseButtonState.Pressed)
{
System.Diagnostics.Debug.WriteLine(string.Format("{0}: Left {1}.", i, fe.Name)); i++;
}
else
{
System.Diagnostics.Debug.WriteLine(string.Format("{0}: Left {1} (Released).", i, fe.Name)); i++;
}
}
}
MonitorMouseLeaveBehavior:
using System;
using System.Linq;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Timers;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interactivity;
using System.Windows.Interop;
using System.ComponentModel;
using System.Windows.Media;
using WpfApplication1.Helpers;
namespace WpfApplication1.Behavior
{
public class MonitorMouseLeaveBehavior : Behavior<FrameworkElement>
{
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool GetCursorPos(ref Win32Point pt);
[StructLayout(LayoutKind.Sequential)]
internal struct Win32Point
{
public Int32 X;
public Int32 Y;
};
[DllImport("user32.dll")]
public static extern short GetAsyncKeyState(UInt16 virtualKeyCode);
private enum VK
{
LBUTTON = 0x01
}
private bool _tracking;
private const int _interval = 1;
private Timer _checkPosTimer = new Timer(_interval);
private Dictionary<FrameworkElement, RoutedEventHandlerInfo[]> _leaveHandlersForElement = new Dictionary<FrameworkElement, RoutedEventHandlerInfo[]>();
private Window _window;
private Dictionary<FrameworkElement, Rect> _boundsByElement = new Dictionary<FrameworkElement, Rect>();
private Dictionary<FrameworkElement, bool> _wasInside = new Dictionary<FrameworkElement, bool>();
private List<FrameworkElement> _elements = new List<FrameworkElement>();
/// <summary>
/// If true, all subcontrols are monitored for the mouseleave event when left mousebutton is down.
/// True by default.
/// </summary>
public bool MonitorSubControls { get { return (bool)GetValue(MonitorSubControlsProperty); } set { SetValue(MonitorSubControlsProperty, value); } }
public static readonly DependencyProperty MonitorSubControlsProperty = DependencyProperty.Register("MonitorSubControls", typeof(bool), typeof(MonitorMouseLeaveBehavior), new PropertyMetadata(true, OnMonitorSubControlsChanged));
private static void OnMonitorSubControlsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MonitorMouseLeaveBehavior beh = (MonitorMouseLeaveBehavior)d;
beh.AddOrRemoveLogicalChildren((bool)e.NewValue);
}
/// <summary>
/// Initial actions
/// </summary>
protected override void OnAttached()
{
_window = this.AssociatedObject is Window ? (Window)this.AssociatedObject : Window.GetWindow(this.AssociatedObject); // get window
_window.SourceInitialized += (s, e) =>
{
this.AddOrRemoveLogicalChildren(this.MonitorSubControls); // get all monitored elements
this.AttachHandlers(true); // attach mousedown and sizechanged handlers
this.GetAllBounds(); // determine bounds of all elements
_checkPosTimer.Elapsed += (s1, e1) => Dispatcher.BeginInvoke((Action)(() => { CheckPosition(); }));
};
base.OnAttached();
}
protected override void OnDetaching()
{
this.AttachHandlers(false);
base.OnDetaching();
}
/// <summary>
/// Starts or stops monitoring of the AssociatedObject's logical children.
/// </summary>
/// <param name="add"></param>
private void AddOrRemoveLogicalChildren(bool add)
{
if (_window != null && _window.IsInitialized)
{
AddOrRemoveSizeChangedHandlers(false);
_elements.Clear();
if (add)
_elements.AddRange(VisualHelper.FindLogicalChildren<FrameworkElement>(this.AssociatedObject));
_elements.Add(this.AssociatedObject);
AddOrRemoveSizeChangedHandlers(true);
}
}
/// <summary>
/// Attaches/detaches size changed handlers to the monitored elements
/// </summary>
/// <param name="add"></param>
private void AddOrRemoveSizeChangedHandlers(bool add)
{
foreach (var element in _elements)
{
element.SizeChanged -= element_SizeChanged;
if (add) element.SizeChanged += element_SizeChanged;
}
}
/// <summary>
/// Adjusts the stored bounds to the changed size
/// </summary>
void element_SizeChanged(object sender, SizeChangedEventArgs e)
{
FrameworkElement fe = sender as FrameworkElement;
if (fe != null)
GetBounds(fe);
}
/// <summary>
/// Attaches/Detaches MouseLeftButtonDown and SizeChanged handlers
/// </summary>
/// <param name="attach">true: attach, false: detach</param>
private void AttachHandlers(bool attach)
{
AddOrRemoveSizeChangedHandlers(attach);
if (attach)
_window.PreviewMouseLeftButtonDown += window_PreviewMouseLeftButtonDown;
else // detach
_window.PreviewMouseLeftButtonDown -= window_PreviewMouseLeftButtonDown;
}
/// <summary>
/// Gets the bounds for all monitored elements
/// </summary>
private void GetAllBounds()
{
_boundsByElement.Clear();
foreach (var element in _elements)
GetBounds(element);
}
/// <summary>
/// Gets the bounds of the control, which are used to check if the mouse position
/// is located within. Note that this only covers rectangular control shapes.
/// </summary>
private void GetBounds(FrameworkElement element)
{
Point p1 = new Point(0, 0);
Point p2 = new Point(element.ActualWidth, element.ActualHeight);
p1 = element.TransformToVisual(_window).Transform(p1);
p2 = element.TransformToVisual(_window).Transform(p2);
if (element == _window) // window bounds need to account for the border
{
var titleHeight = SystemParameters.WindowCaptionHeight + 2 * SystemParameters.ResizeFrameHorizontalBorderHeight; // not sure about that one
var verticalBorderWidth = SystemParameters.ResizeFrameVerticalBorderWidth;
p1.Offset(-verticalBorderWidth, -titleHeight);
p2.Offset(-verticalBorderWidth, -titleHeight);
}
Rect bounds = new Rect(p1, p2);
if (_boundsByElement.ContainsKey(element))
_boundsByElement[element] = bounds;
else
_boundsByElement.Add(element, bounds);
}
/// <summary>
/// For all monitored elements, detach the MouseLeave event handlers and store them locally,
/// to be executed manually.
/// </summary>
private void RerouteLeaveHandlers()
{
foreach (var element in _elements)
{
if (!_leaveHandlersForElement.ContainsKey(element))
{
var handlers = ReflectionHelper.GetRoutedEventHandlers(element, UIElement.MouseLeaveEvent);
if (handlers != null)
{
_leaveHandlersForElement.Add(element, handlers);
foreach (var handler in handlers)
element.MouseLeave -= (MouseEventHandler)handler.Handler; // detach handlers
}
}
}
}
/// <summary>
/// Reattach all leave handlers that were detached in window_PreviewMouseLeftButtonDown.
/// </summary>
private void ReattachLeaveHandlers()
{
foreach (var kvp in _leaveHandlersForElement)
{
FrameworkElement fe = kvp.Key;
foreach (var handler in kvp.Value)
{
if (handler.Handler is MouseEventHandler)
fe.MouseLeave += (MouseEventHandler)handler.Handler;
}
}
_leaveHandlersForElement.Clear();
}
/// <summary>
/// Checks if the mouse position is inside the bounds of the elements
/// If there is a transition from inside to outside, the leave event handlers are executed
/// </summary>
private void DetermineIsInside()
{
Point p = _window.PointFromScreen(GetMousePosition());
foreach (var element in _elements)
{
if (_boundsByElement.ContainsKey(element))
{
bool isInside = _boundsByElement[element].Contains(p);
bool wasInside = _wasInside.ContainsKey(element) && _wasInside[element];
if (wasInside && !isInside)
ExecuteLeaveHandlers(element);
if (_wasInside.ContainsKey(element))
_wasInside[element] = isInside;
else
_wasInside.Add(element, isInside);
}
}
}
/// <summary>
/// Gets the mouse position relative to the screen
/// </summary>
public static Point GetMousePosition()
{
Win32Point w32Mouse = new Win32Point();
GetCursorPos(ref w32Mouse);
return new Point(w32Mouse.X, w32Mouse.Y);
}
/// <summary>
/// Gets the mouse button state. MouseEventArgs.LeftButton is notoriously unreliable.
/// </summary>
private bool IsMouseLeftButtonPressed()
{
short leftMouseKeyState = GetAsyncKeyState((ushort)VK.LBUTTON);
bool ispressed = leftMouseKeyState < 0;
return ispressed;
}
/// <summary>
/// Executes the leave handlers that were attached to the controls.
/// They have been detached previously by this behavior (see window_PreviewMouseLeftButtonDown), to prevent double execution.
/// After mouseup, they are reattached (see CheckPosition)
/// </summary>
private void ExecuteLeaveHandlers(FrameworkElement fe)
{
MouseDevice mouseDev = InputManager.Current.PrimaryMouseDevice;
MouseEventArgs mouseEvent = new MouseEventArgs(mouseDev, 0) { RoutedEvent = Control.MouseLeaveEvent };
if (_leaveHandlersForElement.ContainsKey(fe))
{
foreach (var handler in _leaveHandlersForElement[fe])
{
if (handler.Handler is MouseEventHandler)
((MouseEventHandler)handler.Handler).Invoke(fe, mouseEvent);
}
}
}
/// <summary>
/// Sets the mouse capture (events outside the window are still directed to it),
/// and tells the behavior to watch out for a missed leave event
/// </summary>
private void window_PreviewMouseLeftButtonDown(object sender, MouseEventArgs e)
{
System.Diagnostics.Debug.WriteLine("--- left mousebutton down ---"); // todo remove
this.RerouteLeaveHandlers();
_tracking = true;
_checkPosTimer.Start();
}
/// <summary>
/// Uses the _tracking field as well as left mouse button state to determine if either
/// leave event handlers should be executed, or monitoring should be stopped.
/// </summary>
private void CheckPosition()
{
if (_tracking)
{
if (IsMouseLeftButtonPressed())
{
this.DetermineIsInside();
}
else
{
_wasInside.Clear();
_tracking = false;
_checkPosTimer.Stop();
System.Diagnostics.Debug.WriteLine("--- left mousebutton up ---"); // todo remove
// invoking ReattachLeaveHandlers() immediately would rethrow MouseLeave for top grid/window
// if both a) mouse is outside window and b) mouse moves. Wait with reattach until mouse is inside window again and moves.
_window.MouseMove += ReattachHandler;
}
}
}
/// <summary>
/// Handles the first _window.MouseMove event after left mouse button was released,
/// and reattaches the MouseLeaveHandlers. Detaches itself to be executed only once.
/// </summary>
private void ReattachHandler(object sender, MouseEventArgs e)
{
ReattachLeaveHandlers();
_window.MouseMove -= ReattachHandler; // only once
}
}
}
VisualHelper.FindLogicalChildren, ReflectionHelper.GetRoutedEventHandlers:
public static List<T> FindLogicalChildren<T>(DependencyObject obj) where T : DependencyObject
{
List<T> children = new List<T>();
foreach (var child in LogicalTreeHelper.GetChildren(obj))
{
if (child != null)
{
if (child is T)
children.Add((T)child);
if (child is DependencyObject)
children.AddRange(FindLogicalChildren<T>((DependencyObject)child)); // recursive
}
}
return children;
}
/// <summary>
/// Gets the list of routed event handlers subscribed to the specified routed event.
/// </summary>
/// <param name="element">The UI element on which the event is defined.</param>
/// <param name="routedEvent">The routed event for which to retrieve the event handlers.</param>
/// <returns>The list of subscribed routed event handlers.</returns>
public static RoutedEventHandlerInfo[] GetRoutedEventHandlers(UIElement element, RoutedEvent routedEvent)
{
var routedEventHandlers = default(RoutedEventHandlerInfo[]);
// Get the EventHandlersStore instance which holds event handlers for the specified element.
// The EventHandlersStore class is declared as internal.
var eventHandlersStoreProperty = typeof(UIElement).GetProperty("EventHandlersStore", BindingFlags.Instance | BindingFlags.NonPublic);
object eventHandlersStore = eventHandlersStoreProperty.GetValue(element, null);
if (eventHandlersStore != null)
{
// Invoke the GetRoutedEventHandlers method on the EventHandlersStore instance
// for getting an array of the subscribed event handlers.
var getRoutedEventHandlers = eventHandlersStore.GetType().GetMethod("GetRoutedEventHandlers", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
routedEventHandlers = (RoutedEventHandlerInfo[])getRoutedEventHandlers.Invoke(eventHandlersStore, new object[] { routedEvent });
}
return routedEventHandlers;
}
Approach #1 - is still a valid one (as a pure managed solution) if you work out the specifics.
(capture could be given to a specific control to avoid issues, but I haven't tried)
This should help you get the events ('fixed' events).
Key is to track the mouse move when outside window (and only when mouse is down).
For that you'd need to do the capture (but slightly different than suggested as that won't work - on down/up instead).
private void Window_MouseDown(object sender, MouseEventArgs e)
{
this.CaptureMouse();
}
private void Window_MouseUp(object sender, MouseEventArgs e)
{
this.ReleaseMouseCapture();
}
private void Window_MouseLeave(object sender, MouseEventArgs e)
{
test1.Content = "Mouse left";
}
private void Window_MouseEnter(object sender, MouseEventArgs e)
{
test1.Content = "Mouse entered";
}
private void Window_MouseMove(object sender, MouseEventArgs e)
{
if (Mouse.Captured == this)
{
if (!this.IsMouseInBounds(e))
Window_MouseLeave(sender, e);
else
Window_MouseEnter(sender, e);
}
test2.Content = e.GetPosition(this).ToString();
}
private bool IsMouseInBounds(MouseEventArgs e)
{
var client = ((FrameworkElement)this.Content);
Rect bounds = new Rect(0, 0, client.ActualWidth, client.ActualHeight);
return bounds.Contains(e.GetPosition(this));
}
private Point GetRealPosition(Point mousePoint)
{
return Application.Current.MainWindow.PointFromScreen(mousePoint);
}
Note:
You'd need to finish this according to your situation. I have just 'dummy wired' the mouse move to Enter and Leave and w/o any smart algorithm there (i.e. generated enter/leave will keep on firing). I.e. add some flag to actually save the state of the enter/leave properly.
Also I'm measuring whether mouse is within the 'client bounds' of the Window. You'd need to adjust that if you need that in respect of borders etc.
Also I forgot to add the obvious - wire up the new events MouseDown="Window_MouseDown" MouseUp="Window_MouseUp"
That's "normal" behaviour. Capture the mouse inside MouseEnter handler.
Mouse.Capture(yourUIElement);
and later release it in MouseLeave,
Mouse.Capture(null);
Edited: More explanation.
WPF does not track mouse movement precisely. You can deduce that from the fact that if you capture MouseMove event, you can see that the it reports you event every 20milliseconds intervals and not by pixel precision.. more like 8 pixels per event.
Now this is not that horrible, but WPF also does not report mouse movement outside the window, if you happen to move your mouse. This is default behaviour. You can change it throuh Mouse.Capture as said.
Now, you can imagine why this problem happens. If you can move your mouse outside the window faster than mouse move report happens, then WPF still thinks that it's inside the application.
EDIT
In case you need it - I edited in a simplified wrapper for ease of use (just add commands in your
view-model)
Approach #2 - using Global Mouse Hook to track mouse move - the rest is similar to #1.
Actually, this is more of an example on how to do a global hook from C#.
In XAML you can hook up all 3 or just one, two events
my:Hooks.EnterCommand="{Binding EnterCommand}"
my:Hooks.LeaveCommand="{Binding LeaveCommand}"
my:Hooks.MouseMoveCommand="{Binding MoveCommand}"
In your view-model define commands
RelayCommand _enterCommand;
public RelayCommand EnterCommand
{
get
{
return _enterCommand ?? (_enterCommand = new RelayCommand(param =>
{
var point = (Point)param;
test1.Content = "Mouse entered";
// test2.Content = point.ToString();
},
param => true));
}
}
And the attached properties (the 'nice' wrapper)...
public static class Hooks
{
private static Dictionary<ContentControl, Action> _hash = new Dictionary<ContentControl, Action>();
#region MouseMoveCommand
public static ICommand GetMouseMoveCommand(ContentControl control) { return (ICommand)control.GetValue(MouseMoveCommandProperty); }
public static void SetMouseMoveCommand(ContentControl control, ICommand value) { control.SetValue(MouseMoveCommandProperty, value); }
public static readonly DependencyProperty MouseMoveCommandProperty =
DependencyProperty.RegisterAttached("MouseMoveCommand", typeof(ICommand), typeof(Hooks), new UIPropertyMetadata(null, OnMouseMoveCommandChanged));
static void OnMouseMoveCommandChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
ContentControl control = depObj as ContentControl;
if (control != null && e.NewValue is ICommand)
SetupMouseMove(control);
}
static void Instance_MouseMoveLL(object sender, WinHook.MouseLLMessageArgs e)
{
}
static void OnAutoGeneratingColumn(ICommand command, object sender, DataGridAutoGeneratingColumnEventArgs e)
{
if (command.CanExecute(e)) command.Execute(e);
}
#endregion
#region EnterCommand
public static ICommand GetEnterCommand(ContentControl control) { return (ICommand)control.GetValue(EnterCommandProperty); }
public static void SetEnterCommand(ContentControl control, ICommand value) { control.SetValue(EnterCommandProperty, value); }
public static readonly DependencyProperty EnterCommandProperty =
DependencyProperty.RegisterAttached("EnterCommand", typeof(ICommand), typeof(Hooks), new UIPropertyMetadata(null, OnEnterCommandChanged));
static void OnEnterCommandChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
ContentControl control = depObj as ContentControl;
if (control != null && e.NewValue is ICommand)
SetupMouseMove(control);
}
#endregion
#region LeaveCommand
public static ICommand GetLeaveCommand(ContentControl control) { return (ICommand)control.GetValue(LeaveCommandProperty); }
public static void SetLeaveCommand(ContentControl control, ICommand value) { control.SetValue(LeaveCommandProperty, value); }
public static readonly DependencyProperty LeaveCommandProperty =
DependencyProperty.RegisterAttached("LeaveCommand", typeof(ICommand), typeof(Hooks), new UIPropertyMetadata(null, OnLeaveCommandChanged));
static void OnLeaveCommandChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
ContentControl control = depObj as ContentControl;
if (control != null && e.NewValue is ICommand)
SetupMouseMove(control);
}
#endregion
static void SetupMouseMove(ContentControl control)
{
Action onmove;
if (_hash.TryGetValue(control, out onmove) == false)
{
onmove = () =>
{
var entered = false;
var moveCommand = control.GetValue(Hooks.MouseMoveCommandProperty) as ICommand;
var enterCommand = control.GetValue(Hooks.EnterCommandProperty) as ICommand;
var leaveCommand = control.GetValue(Hooks.LeaveCommandProperty) as ICommand;
// hook is invoked on the 'caller thread' (i.e. your GUI one) so it's safe
// don't forget to unhook and dispose / release it, handle unsubscribe for events
WinHook.Instance.MouseMoveLL += (s, e) =>
{
Point point = control.PointFromScreen(new Point(e.Message.Pt.X, e.Message.Pt.Y));
if (moveCommand != null && moveCommand.CanExecute(point))
moveCommand.Execute(point);
var newEntered = control.IsMouseInBounds(point); // don't use 'IsMouseOver'
if (newEntered != entered)
{
entered = newEntered;
if (entered)
{
if (enterCommand != null && enterCommand.CanExecute(point))
enterCommand.Execute(point);
}
else
{
if (leaveCommand != null && leaveCommand.CanExecute(point))
leaveCommand.Execute(point);
}
}
};
};
control.Loaded += (s, e) => onmove();
_hash[control] = onmove;
}
}
private static bool IsMouseInBounds(this ContentControl control, Point point)
{
var client = ((FrameworkElement)control.Content);
Rect bounds = new Rect(0, 0, client.ActualWidth, client.ActualHeight);
return bounds.Contains(point);
}
}
And you could use HookManager from the article.
Or the minimal hook code (mind that proper IDisoposable is required, exception handling etc.):
public sealed class WinHook : IDisposable
{
public static readonly WinHook Instance = new WinHook();
public const int WH_MOUSE_LL = 14;
public const uint WM_MOUSEMOVE = 0x0200;
public delegate void MouseLLMessageHandler(object sender, MouseLLMessageArgs e);
public delegate int HookProc(int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
public static extern int GetCurrentThreadId();
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
public static extern bool UnhookWindowsHookEx(int idHook);
[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
public static extern int CallNextHookEx(int idHook, int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr GetModuleHandle(string lpModuleName);
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int X;
public int Y;
}
[StructLayout(LayoutKind.Sequential)]
public class MouseLLHookStruct
{
public POINT Pt;
public uint mouseData;
public uint flags;
public uint time;
public uint dwExtraInfo;
}
public class MouseLLMessageArgs : EventArgs
{
public bool IsProcessed { get; set; }
public MouseLLHookStruct Message { get; private set; }
public MouseLLMessageArgs(MouseLLHookStruct message) { this.Message = message; }
}
static IntPtr GetModuleHandle()
{
using (Process process = Process.GetCurrentProcess())
using (ProcessModule module = process.MainModule)
return GetModuleHandle(module.ModuleName);
}
public event MouseLLMessageHandler MouseMoveLL;
int _hLLMouseHook = 0;
HookProc LLMouseHook;
private WinHook()
{
IntPtr hModule = GetModuleHandle();
LLMouseHook = LowLevelMouseProc;
_hLLMouseHook = SetWindowsHookEx(WH_MOUSE_LL, LLMouseHook, hModule, 0);
if (_hLLMouseHook == 0) { } // "failed w/ an error code: {0}", new Win32Exception(Marshal.GetLastWin32Error()).Message
}
public void Release()
{
if (_hLLMouseHook == 0) return;
int hhook = _hLLMouseHook;
_hLLMouseHook = 0;
bool ret = UnhookWindowsHookEx(hhook);
if (ret == false) { } // "failed w/ an error code: {0}", new Win32Exception(Marshal.GetLastWin32Error()).Message
}
public int LowLevelMouseProc(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0 && lParam.ToInt32() > 0
&& wParam.ToInt32() == (int)WM_MOUSEMOVE)
{
MouseLLHookStruct msg = (MouseLLHookStruct)Marshal.PtrToStructure(lParam, typeof(MouseLLHookStruct));
MouseLLMessageArgs args = new MouseLLMessageArgs(msg);
if (MouseMoveLL != null)
MouseMoveLL(this, args);
if (args.IsProcessed)
return -1; // return 1;
}
return CallNextHookEx(_hLLMouseHook, nCode, wParam, lParam);
}
// implement IDisposable properly and call `Release` for unmanaged resources / hook
public void Dispose() { }
}
Note: global mouse hooks are notorious for performance issues. And you cannot use the local one (recommended but most of the time useless) - as it doesn't get outside mouse moves.
Also avoid putting anything 'heavy' inside the event - or anything that 'spawns off' from it. There is a limit actually on the time you can spend processing the event - or your hook will get removed, i.e. stop working. If you need to do some processing from the event, pop up a new thread and Invoke back.
My favorite solution is actually to give the hook its own thread and then events need to be invoked - but that's out of scope and a bit more complex (you need a 'pump' in there etc.).
As to 'why' all this is necessary:
I don't like speculating, but seems that events are throttled - and the critical 'one' is missed when 'crossing the border', something like that. Anyway, it's all about the mouse moves no matter how you look at it.

How can I implement a commandable ColumnSeries in the WPF Toolkit's Chart Control

I need to be able to specify a command to run when the SelectionChanged event fires. I already know how to implement the ICommandSource interface; what I need to know is how I can just add a command to the column series to handle the SelectionChanged event.
When I inherit from the ColumnBarBaseSeries<...> base class I have to override GetAxes() and UpdateDatePoint() which I am not sure how to implement.
You can use attached behaviours to solve this problem.
Create a SelectionChangedBehaviour that wires a selectionChanged event to the element that you're attaching the behaviour to then you can binding any ICommand to that behaviour.
For more on attached behaviours -
Introduction article by Josh Smith
Overview of the concept
Another good introduction
Hope that helps
Here is some code to add an attached behavior to a ColumnSeries for a SelectionChanged Command.
public static class ColumnSeriesBehavior
{
private static DelegateCommand<object> SelectionChangedCommand;
public static DelegateCommand<object> GetSelectionChangedCommand(ColumnSeries cs)
{
return cs.GetValue(SelectionChangedCommandProperty) as DelegateCommand<object>;
}
public static void SetSelectionChangedCommand(ColumnSeries cs, DelegateCommand<object> value)
{
cs.SetValue(SelectionChangedCommandProperty, value);
}
// Using a DependencyProperty as the backing store for SelectionChangedCommand. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectionChangedCommandProperty =
DependencyProperty.RegisterAttached("SelectionChangedCommand", typeof(DelegateCommand<object>), typeof(ColumnSeriesBehavior), new UIPropertyMetadata(null, OnSelectionChangedCommandChanged));
private static void OnSelectionChangedCommandChanged(
DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
ColumnSeries item = depObj as ColumnSeries;
if (item == null)
{
return;
}
if (e.NewValue is DelegateCommand<object> == false)
{
return;
}
SelectionChangedCommand = e.NewValue as DelegateCommand<object>;
item.SelectionChanged += new System.Windows.Controls.SelectionChangedEventHandler(Column_SelectionChanged);
}
private static void Column_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
if (SelectionChangedCommand != null)
SelectionChangedCommand.Execute(sender);
}
}
And in the XAML to attach the property:
<chartingToolkit:Chart.Series>
<chartingToolkit:ColumnSeries
IsSelectionEnabled="True"
ItemsSource="{Binding YourItemSource}"
IndependentValueBinding="{Binding YourIndValue, Path=YourIndValuePath}"
DependentValueBinding="{Binding YourDepValue, Path=YourDepValuePath}"
>
<chartingToolkit:ColumnSeries.Style>
<Style>
<!-- Attaching the SelectionChangedCommand behavior -->
<Setter Property="local:ColumnSeriesBehavior.SelectionChangedCommand"
Value="{Binding YourDelegateCommand}"/>
</Style>
</chartingToolkit:ColumnSeries.Style>
</chartingToolkit:ColumnSeries>
</chartingToolkit:Chart.Series>
Here is some code that appears to work for me to implement your own CommandColumnSeries, I stole a lot of it from the source for the ColumnSeries Sealed Class:
public class CommandColumnSeries : ColumnBarBaseSeries<ColumnDataPoint>
{
#region "ICommandSource"
[Localizability(LocalizationCategory.NeverLocalize), Category("Action"), Bindable(true)]
public ICommand Command
{
get
{
return (ICommand)base.GetValue(CommandProperty);
}
set
{
base.SetValue(CommandProperty, value);
}
}
[Bindable(true), Category("Action"), Localizability(LocalizationCategory.NeverLocalize)]
public object CommandParameter
{
get
{
return base.GetValue(CommandParameterProperty);
}
set
{
base.SetValue(CommandParameterProperty, value);
}
}
[Category("Action"), Bindable(true)]
public IInputElement CommandTarget
{
get
{
return (IInputElement)base.GetValue(CommandTargetProperty);
}
set
{
base.SetValue(CommandTargetProperty, value);
}
}
public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register("CommandParameter", typeof(object), typeof(CommandColumnSeries), new FrameworkPropertyMetadata(null));
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(CommandColumnSeries), new FrameworkPropertyMetadata(null));
public static readonly DependencyProperty CommandTargetProperty = DependencyProperty.Register("CommandTarget", typeof(IInputElement), typeof(CommandColumnSeries), new FrameworkPropertyMetadata(null));
#endregion
#region public IRangeAxis DependentRangeAxis
/// <summary>
/// Gets or sets the dependent range axis.
/// </summary>
public IRangeAxis DependentRangeAxis
{
get { return GetValue(DependentRangeAxisProperty) as IRangeAxis; }
set { SetValue(DependentRangeAxisProperty, value); }
}
/// <summary>
/// Identifies the DependentRangeAxis dependency property.
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes", Justification = "This member is necessary because the base classes need to share this dependency property.")]
public static readonly DependencyProperty DependentRangeAxisProperty =
DependencyProperty.Register(
"DependentRangeAxis",
typeof(IRangeAxis),
typeof(ColumnSeries),
new PropertyMetadata(null, OnDependentRangeAxisPropertyChanged));
/// <summary>
/// DependentRangeAxisProperty property changed handler.
/// </summary>
/// <param name="d">ColumnBarBaseSeries that changed its DependentRangeAxis.</param>
/// <param name="e">Event arguments.</param>
private static void OnDependentRangeAxisPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CommandColumnSeries source = (CommandColumnSeries)d;
IRangeAxis newValue = (IRangeAxis)e.NewValue;
source.OnDependentRangeAxisPropertyChanged(newValue);
}
/// <summary>
/// DependentRangeAxisProperty property changed handler.
/// </summary>
/// <param name="newValue">New value.</param>
private void OnDependentRangeAxisPropertyChanged(IRangeAxis newValue)
{
this.InternalDependentAxis = (IAxis)newValue;
}
#endregion public IRangeAxis DependentRangeAxis
#region public IAxis IndependentAxis
/// <summary>
/// Gets or sets the independent category axis.
/// </summary>
public IAxis IndependentAxis
{
get { return GetValue(IndependentAxisProperty) as IAxis; }
set { SetValue(IndependentAxisProperty, value); }
}
/// <summary>
/// Identifies the IndependentAxis dependency property.
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1000:DoNotDeclareStaticMembersOnGenericTypes", Justification = "This member is necessary because the base classes need to share this dependency property.")]
public static readonly DependencyProperty IndependentAxisProperty =
DependencyProperty.Register(
"IndependentAxis",
typeof(IAxis),
typeof(ColumnSeries),
new PropertyMetadata(null, OnIndependentAxisPropertyChanged));
/// <summary>
/// IndependentAxisProperty property changed handler.
/// </summary>
/// <param name="d">ColumnBarBaseSeries that changed its IndependentAxis.</param>
/// <param name="e">Event arguments.</param>
private static void OnIndependentAxisPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CommandColumnSeries source = (CommandColumnSeries)d;
IAxis newValue = (IAxis)e.NewValue;
source.OnIndependentAxisPropertyChanged(newValue);
}
/// <summary>
/// IndependentAxisProperty property changed handler.
/// </summary>
/// <param name="newValue">New value.</param>
private void OnIndependentAxisPropertyChanged(IAxis newValue)
{
this.InternalIndependentAxis = (IAxis)newValue;
}
#endregion public IAxis IndependentAxis
public CommandColumnSeries()
{
this.SelectionChanged += new SelectionChangedEventHandler(CommandColumnSeries_SelectionChanged);
}
private void CommandColumnSeries_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
if (Command != null)
{
RoutedCommand routedCommand = Command as RoutedCommand;
CommandParameter = e.Source;
if (routedCommand != null)
{
routedCommand.Execute(CommandParameter, CommandTarget);
}
else
{
Command.Execute(CommandParameter);
}
}
}
protected override void GetAxes(DataPoint firstDataPoint)
{
// Taken from the source of the ColumnSeries sealed class.
GetAxes(
firstDataPoint,
(axis) => axis.Orientation == AxisOrientation.X,
() => new CategoryAxis { Orientation = AxisOrientation.X },
(axis) =>
{
IRangeAxis rangeAxis = axis as IRangeAxis;
return rangeAxis != null && rangeAxis.Origin != null && axis.Orientation == AxisOrientation.Y;
},
() =>
{
IRangeAxis rangeAxis = CreateRangeAxisFromData(firstDataPoint.DependentValue);
rangeAxis.Orientation = AxisOrientation.Y;
if (rangeAxis == null || rangeAxis.Origin == null)
{
throw new InvalidOperationException("No Suitable Axes found for plotting range axis.");
}
DisplayAxis axis = rangeAxis as DisplayAxis;
if (axis != null)
{
axis.ShowGridLines = true;
}
return rangeAxis;
});
}
protected override void UpdateDataPoint(DataPoint dataPoint)
{
// This code taken from the ColumnSeries sealed class.
if (SeriesHost == null )//|| PlotArea == null)
{
return;
}
object category = dataPoint.ActualIndependentValue ?? (IndexOf<DataPoint>(this.ActiveDataPoints, dataPoint) + 1);
Range<UnitValue> coordinateRange = GetCategoryRange(category);
if (!coordinateRange.HasData)
{
return;
}
else if (coordinateRange.Maximum.Unit != Unit.Pixels || coordinateRange.Minimum.Unit != Unit.Pixels)
{
throw new InvalidOperationException("This Series Does Not Support Radial Axes");
}
double minimum = (double)coordinateRange.Minimum.Value;
double maximum = (double)coordinateRange.Maximum.Value;
double plotAreaHeight = ActualDependentRangeAxis.GetPlotAreaCoordinate(ActualDependentRangeAxis.Range.Maximum).Value.Value;
IEnumerable<CommandColumnSeries> columnSeries = SeriesHost.Series.OfType<CommandColumnSeries>().Where(series => series.ActualIndependentAxis == ActualIndependentAxis);
int numberOfSeries = columnSeries.Count();
double coordinateRangeWidth = (maximum - minimum);
double segmentWidth = coordinateRangeWidth * 0.8;
double columnWidth = segmentWidth / numberOfSeries;
int seriesIndex = IndexOf<CommandColumnSeries>(columnSeries, this);
double dataPointY = ActualDependentRangeAxis.GetPlotAreaCoordinate(ToDouble(dataPoint.ActualDependentValue)).Value.Value;
double zeroPointY = ActualDependentRangeAxis.GetPlotAreaCoordinate(ActualDependentRangeAxis.Origin).Value.Value;
double offset = seriesIndex * Math.Round(columnWidth) + coordinateRangeWidth * 0.1;
double dataPointX = minimum + offset;
if (GetIsDataPointGrouped(category))
{
// Multiple DataPoints share this category; offset and overlap them appropriately
IGrouping<object, DataPoint> categoryGrouping = GetDataPointGroup(category);
int index = GroupIndexOf(categoryGrouping, dataPoint);
dataPointX += (index * (columnWidth * 0.2)) / (categoryGrouping.Count() - 1);
columnWidth *= 0.8;
Canvas.SetZIndex(dataPoint, -index);
}
if (CanGraph(dataPointY) && CanGraph(dataPointX) && CanGraph(zeroPointY))
{
double left = Math.Round(dataPointX);
double width = Math.Round(columnWidth);
double top = Math.Round(plotAreaHeight - Math.Max(dataPointY, zeroPointY) + 0.5);
double bottom = Math.Round(plotAreaHeight - Math.Min(dataPointY, zeroPointY) + 0.5);
double height = bottom - top + 1;
Canvas.SetLeft(dataPoint, left);
Canvas.SetTop(dataPoint, top);
dataPoint.Width = width;
dataPoint.Height = height;
}
}
private static int IndexOf<T>(IEnumerable<T> collection, T target)
{
int i = 0;
foreach (var obj in collection)
{
if (obj.Equals(target))
return i;
i++;
}
return -1;
}
private static int GroupIndexOf(IGrouping<object, DataPoint> group, DataPoint point)
{
int i = 0;
foreach (var pt in group)
{
if (pt == point)
return i;
i++;
}
return -1;
}
/// <summary>
/// Returns a value indicating whether this value can be graphed on a
/// linear axis.
/// </summary>
/// <param name="value">The value to evaluate.</param>
/// <returns>A value indicating whether this value can be graphed on a
/// linear axis.</returns>
private static bool CanGraph(double value)
{
return !double.IsNaN(value) && !double.IsNegativeInfinity(value) && !double.IsPositiveInfinity(value) && !double.IsInfinity(value);
}
/// <summary>
/// Converts an object into a double.
/// </summary>
/// <param name="value">The value to convert to a double.</param>
/// <returns>The converted double value.</returns>
private static double ToDouble(object value)
{
return Convert.ToDouble(value, CultureInfo.InvariantCulture);
}
}

Resources