I have an WPF User control in which I create a RoutedEventHandler. I want to raise an event notifying every time its height changes:
Wpfusercontrol.designer.cs:
public partial class Wpfusercontrol: System.Windows.Controls.UserControl
{
public static readonly RoutedEvent HeightChangedEvent = EventManager.RegisterRoutedEvent(
"HeightChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(Wpfusercontrol));
public event RoutedEventHandler HeightChanged
{
add { AddHandler(HeightChangedEvent, value); }
remove { RemoveHandler(HeightChangedEvent, value); }
}
private void UserControl_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (e.HeightChanged && HeightChangedEvent != null)
{
RaiseEvent(new RoutedEventArgs(HeightChangedEvent));
}
}
}
Then this WPF user control is hosted in an ElementHost
WindowsFormsHostControl.Designer.cs:
partial class WindowsFormsHostControl
{
private void InitializeComponent()
{
this.ElementHostFormControl = new System.Windows.Forms.Integration.ElementHost();
this.Wpfusercontrol= new Wpfusercontrol();
this.SuspendLayout();
//
// ElementHostFormControl
//
this.ElementHostFormControl.Dock = System.Windows.Forms.DockStyle.Fill;
this.ElementHostFormControl.Location = new System.Drawing.Point(0, 0);
this.ElementHostFormControl.Margin = new System.Windows.Forms.Padding(2);
this.ElementHostFormControl.Name = "ElementHostFormControl";
this.ElementHostFormControl.Size = new System.Drawing.Size(75, 78);
this.ElementHostFormControl.TabIndex = 0;
this.ElementHostFormControl.Child = this.Wpfusercontrol;
//
// WindowsFormsHostControl
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Controls.Add(this.ElementHostFormControl);
this.Margin = new System.Windows.Forms.Padding(2);
this.Name = "WindowsFormsHostControl";
this.Size = new System.Drawing.Size(75, 78);
this.ResumeLayout(false);
}
private System.Windows.Forms.Integration.ElementHost ElementHostFormControl;
private Wpfusercontrol Wpfusercontrol;
}
WindowsFormsHostControl.cs:
public partial class WindowsFormsHostControl: System.Windows.Forms.UserControl
{
private RoutedEventHandler heightChangedEventHandler;
public WindowsFormsHostControl()
{
InitializeComponent();
}
public WindowsFormsHostControl(RoutedEventHandler heightChangedEventHandler) : this()
{
this.heightChangedEventHandler = heightChangedEventHandler;
this.Wpfusercontrol.HeightChanged += this.heightChangedEventHandler;
}
public void SubscribeHeightChanged()
{
this.Wpfusercontrol.HeightChanged += this.heightChangedEventHandler;
}
public void UnsubscribeHeightChanged()
{
this.Wpfusercontrol.HeightChanged -= this.heightChangedEventHandler;
}
}
This WindowsFormsHostControl is embedded within an UI object called custom task pane which is kind of UI container for VSTO Outlook Add-ins. This custom task pane has a button to resize its height but it does not provide an event to catch it. So when you resize the height of that custom task pane, the height of the wpf user control changes as well, so through the routed event in the wpf user control I know when the custom task pane is resized and I catch the event.
Now from one class in my VSTO Outlook Add-in application (which in fact is a winforms app), I perform below things:
private WindowsFormsHostControl windowsFormsHostControl = null;
this.windowsFormsHostControl = new WindowsFormsHostControl(this.WpfUserControl_HeightChanged);
System.Windows.Fomrs.Timer t;
private void WpfUserControl_HeightChanged(object sender, System.Windows.RoutedEventArgs e)
{
// Dome some stuff
...
t = new System.Windows.Fomrs.Timer();
t.Tick += new EventHandler(Update);
t.Interval = 100;
t.Enable = true;
}
private void Update(object sender, EventArgs e)
{
// Some more stuf....
....
// In below lines I update the height of the custom task pane (VSTO Outlook UI object) which in turn causes the WPF user control to resize its height as well. So then, I am trying to unsubscribe from the wpf routed event, then update the height for custom task pane, and finally subscribe again to the wpf routed event. I do this to prevent routed event in wpf user control fires again.
this.windowsFormsHostControl.UnsubscribeHeightChanged();
// here I update the height for custom task pane
this.windowsFormsHostControl.SubscribeHeightChanged();
}
The problem is that it looks like the line:
this.windowsFormsHostControl.UnsubscribeHeightChanged();
is not working because the routed event in the wpf user control continues raising each time I execute the line of code between UnsubscribeHeightChanged and SubscribeHeightChanged.
So what am i doing wrong?
I have a Winforms application that calls CorelDraw via it's API and then does some work on an image before saving it. I have tried setting this using Threads, BackgroundWorker, and a class that inherited BackgroundWorker but also allow for Thread.Abort. Every way I have done the work, I continuously run into one of 2 issues. Either the thread is not run in the background, in which case I am unable to press any buttons on the form because it is frozen or the thread runs in the background but immediately stops processing anything when I hit the first call to CorelDraw.
Are there any alternative solutions? The old way we handled this was by having a watcher WinForm spawn another Process() that would do everything in CorelDraw. The process called with Process() saves important information into a .txt file for the parent process to check and if the child works on the same image for too long, the parent calls Kill() on it. So far, this has been a clunky and error prone way to do things, which is why I am looking for a better solution.
Designer Code:
namespace Tasks
{
partial class Form1
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.button1 = new System.Windows.Forms.Button();
this.button2 = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// button1
//
this.button1.Location = new System.Drawing.Point(41, 43);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(75, 23);
this.button1.TabIndex = 0;
this.button1.Text = "button1";
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// button2
//
this.button2.Location = new System.Drawing.Point(131, 43);
this.button2.Name = "button2";
this.button2.Size = new System.Drawing.Size(75, 23);
this.button2.TabIndex = 1;
this.button2.Text = "button2";
this.button2.UseVisualStyleBackColor = true;
this.button2.Click += new System.EventHandler(this.button2_Click);
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(240, 116);
this.Controls.Add(this.button2);
this.Controls.Add(this.button1);
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.Button button1;
private System.Windows.Forms.Button button2;
}
}
This is the form code:
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Tasks
{
public partial class Form1 : Form
{
CancellationTokenSource cts;
public Form1()
{
InitializeComponent();
}
private void button2_Click(object sender, EventArgs e)
{
MessageBox.Show(this,"This button still works :)");
}
private async void button1_Click(object sender, EventArgs e)
{
cts = new CancellationTokenSource();
await CreateTask();
}
private async Task CreateTask()
{
//Create a progress object that can be used within the task
Progress<string> mProgress; //you can set this to Int for ProgressBar
//Set the Action to a function that will catch the progress sent within the task
Action<string> progressTarget = ReportProgress;
//Your new Progress with the included action function
mProgress = new Progress<string>(progressTarget);
//start your task
string result = await MyProcess(mProgress);
}
private Task<string> MyProcess(IProgress<string> myProgress)
{
return Task.Run(() =>
{
//To report Progress back to your UI thread
myProgress.Report("Starting program now...");
//Start your Corel Draw program here.
Process.Start("C:\\WINDOWS\\system32\\notepad.exe").WaitForExit();
//You can return the Image after your done editing it
return "Program has been closed";
}, cts.Token);
}
private void ReportProgress(string message)
{
//typically to update a progress bar or whatever
MessageBox.Show(this, message);
}
}
}
If you create async task you can run your Corel application in the background and then when it is closed it will be finish the task. I added in extra stuff in there like Progress and cts is used to cancel the task in case the parent window is closed.
I have some window, which can have many instances opened in single application.
I track all of them in a static dictionary.
The window must be closed when escape pressed if the window is active. And if the window is closed through escape i need activate other window, stored in a dictionary if there is one. I also need to consider current windows z-order and activate the top most of them, but for now it doesn`t matter.
So, when i have some windows opened and try to close them subsequently pressing escape what i get is that at some moment all of the windows left are closed simultaneously.
Here is the code example:
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private int _count;
private void Button_Click(object sender, RoutedEventArgs e)
{
Window1.Run(this, ++_count);
}
}
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
private static Dictionary<int, Window1> _opened =
new Dictionary<int, Window1>();
private int _key;
public Window1()
{
InitializeComponent();
KeyDown += OnKeyDown;
}
private void OnKeyDown(object sender, KeyEventArgs ea)
{
if (ea.Key == Key.Escape)
{
Close();
}
}
public static void Run(Window owner, int key)
{
Window1 w = null;
if (_opened.TryGetValue(key, out w))
{
w.Activate();
}
else
{
w = new Window1{_key = key};
w.Closed += (s, e) =>
{
var win = s as Window1;
_opened.Remove(win._key);
if (_opened.Count > 0)
{
_opened.First().Value.Activate();
}
};
_opened.Add(key, w);
w.Show();
}
}
}
}
Update#1
Thanks to Potecaru Tudor, he found another solution:
The solution I found was to set e.Handled = true in your KeyDown
handler after calling Close()
Solution Code:
private void OnKeyDown(object sender, KeyEventArgs ea)
{
if (ea.Key == Key.Escape)
{
ea.Handled = true;
Close();
}
}
Update#2
And here is another, not so elegant solution, just for the record
...
_opened.Remove(win._key);
if (_opened.Count > 0)
{
// i suppose here is the error hidden
var w2 = _opened.First().Value;
w2.Dispatcher.BeginInvoke(new Action(() => w2.Activate()));
}
...
I tried to reproduce your issue with no success, the windows are closing just fine one by one. You could try calling the Close() method only if Window.IsActive property is set on true.
EDIT I managed to reproduce your issue.
The solution I found was to set e.Handled = true in your KeyDown handler after calling Close()
I suppose the issue is that the windows become active one after another before the first one gets to be closed, so they also received the KeyDown event firing. The weird thing is it only happens in my sample for the first windows you're opening. After you're closing all of them and start opening others this will not reproduce.
Only way i have found is to delay next window activation, like this:
...
_opened.Remove(win._key);
if (_opened.Count > 0)
{
// i suppose here is the error hidden
var w2 = _opened.First().Value;
w2.Dispatcher.BeginInvoke(new Action(() => w2.Activate()));
}
...
Any other suggestions would be appreciated! Thank you!
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.
If two Window is opened mainly A and B, how to close Window A using code that written on Window B.
Your best bet would be to create a property on Window B that you pass the creating Window to. Something like this. I have a Window named MainWindow and a second Window named Window2.
Main Window
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
Window2 secondForm;
public MainWindow()
{
InitializeComponent();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
secondForm = new Window2();
secondForm.setCreatingForm =this;
secondForm.Show();
}
}
}
Window2
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for Window2.xaml
/// </summary>
public partial class Window2 : Window
{
Window creatingForm;
public Window2()
{
InitializeComponent();
}
public Window setCreatingForm
{
get { return creatingForm; }
set { creatingForm = value; }
}
private void button1_Click(object sender, RoutedEventArgs e)
{
if (creatingForm != null)
creatingForm.Close();
}
}
}
In respose to your comment, closing a window that was created by another form is as easy as calling the Close Method of the created Form:
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
Window2 secondForm;
public MainWindow()
{
InitializeComponent();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
if (secondForm == null)
{
secondForm = new Window2();
secondForm.Show();
}
else
secondForm.Activate();
}
private void button2_Click(object sender, RoutedEventArgs e)
{
if (secondForm != null)
{
secondForm.Close();
secondForm = new Window2();
//How ever you are passing information to the secondWindow
secondForm.Show();
}
}
}
}
Here is a way to close any window from any other window. You can modify it to work with multiple instances by giving your windows some unique identifier and then just searching for that in the foreach loop.
public static class Helper
{
public static void CloseWindowOfWhichThereIsOnlyOne<T>()
{
Assembly currentAssembly = Assembly.GetExecutingAssembly();
foreach (Window w in Application.Current.Windows)
{
if (w.GetType().Assembly == currentAssembly && w is T)
{
w.Close();
break;
}
}
}
}
Or with a unique identifier "fudge":
public static void CloseWIndowUsingIdentifier(string windowTag)
{
Assembly currentAssembly = Assembly.GetExecutingAssembly();
foreach (Window w in Application.Current.Windows)
{
if (w.GetType().Assembly == currentAssembly && w.Tag.Equals(windowTag))
{
w.Close();
break;
}
}
}
I like this better than the suggested solution because you don't need to mess with your windows, other than to give them unique tags. I've only been using this for small projects where there is no risk of things not being unique, I'm not going to lose track of 10-12 windows!
The other suggested solution is a little silly (I don't have 50 karma to comment on it) as you could just call win.close() if you already had a reference to the object...
it is very simple make one public class and method like this
class Helper
{
public static void CloseWindow(Window x)
{
Assembly currentAssembly = Assembly.GetExecutingAssembly();
// int count = Application.Current.Windows;
foreach (Window w in Application.Current.Windows)
{
//Form f = Application.OpenForms[i];
if (w.GetType().Assembly == currentAssembly && w==x)
{
w.Close();
}
}
}
}
now call this function from where you want close window like this .
Helper.CloseWindow(win);//win is object of window which you want to close.
hope this helps.
foreach (Window w in Application.Current.Windows)
{
if (w.Name != "Main_Window_wind" )
{
w.Visibility = System.Windows.Visibility.Hidden;
}
}
//name is the x:Name="Main_Window_wind" in xaml
You can close now as hiden all windows without closing the named Main_Window_wind , you can add another window to be not closed with this in if: w.Name != "Main_Window_wind" && w.Name != "AnyOther_Window_wind" &&...
a quicker way is this:
for (int intCounter = App.Current.Windows.Count - 1; intCounter > -1; intCounter--)
{
if (App.Current.Windows[intCounter].Name != "Main_Window_wind")
App.Current.Windows[intCounter].Visibility = System.Windows.Visibility.Hidden;
}