unit test an attached behavior wpf - wpf

I am still grokking attached behaviors in general, and am at a loss to see how to write a unit test for one.
I pasted some code below from Sacha Barber's Cinch framework that allows a window to be closed via attached behavior. Can somewone show me an example unit test for it?
Thanks!
Berryl
#region Close
/// <summary>Dependency property which holds the ICommand for the Close event</summary>
public static readonly DependencyProperty CloseProperty =
DependencyProperty.RegisterAttached("Close",
typeof(ICommand), typeof(Lifetime),
new UIPropertyMetadata(null, OnCloseEventInfoChanged));
/// <summary>Attached Property getter to retrieve the CloseProperty ICommand</summary>
public static ICommand GetClose(DependencyObject source)
{
return (ICommand)source.GetValue(CloseProperty);
}
/// <summary>Attached Property setter to change the CloseProperty ICommand</summary>
public static void SetClose(DependencyObject source, ICommand command)
{
source.SetValue(CloseProperty, command);
}
/// <summary>This is the property changed handler for the Close property.</summary>
private static void OnCloseEventInfoChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var win = sender as Window;
if (win == null) return;
win.Closing -= OnWindowClosing;
win.Closed -= OnWindowClosed;
if (e.NewValue == null) return;
win.Closing += OnWindowClosing;
win.Closed += OnWindowClosed;
}
/// <summary>
/// This method is invoked when the Window.Closing event is raised.
/// It checks with the ICommand.CanExecute handler
/// and cancels the event if the handler returns false.
/// </summary>
private static void OnWindowClosing(object sender, CancelEventArgs e)
{
var dpo = (DependencyObject)sender;
var ic = GetClose(dpo);
if (ic == null) return;
e.Cancel = !ic.CanExecute(GetCommandParameter(dpo));
}
/// <summary>
/// This method is invoked when the Window.Closed event is raised.
/// It executes the ICommand.Execute handler.
/// </summary>
static void OnWindowClosed(object sender, EventArgs e)
{
var dpo = (DependencyObject)sender;
var ic = GetClose(dpo);
if (ic == null) return;
ic.Execute(GetCommandParameter(dpo));
}
#endregion

You would likely use a lambda in your ICommand using a DelegateCommand or a RelayCommand. Multiple implementations of these exists all over the place and Cinch may have something similar. Really simple version (as an example, not meant for production use):
public class DelegateCommand : ICommand {
private Action _execute = null;
public void Execute( object parameter ) {
_execute();
}
public DelegateCommand( Action execute ) {
_execute = execute;
}
#region stuff that doesn't affect functionality
public bool CanExecute( object parameter ) {
return true;
}
public event EventHandler CanExecuteChanged {
add { }
remove { }
}
#endregion
}
Then your test body might look something like this:
bool wascalled = false;
var execute = new DelegateCommand(
() => {
wascalled = true;
} );
var window = new Window();
SomeClass.SetClose( window, execute );
// does the window need to be shown for Close() to work? Nope.
window.Close();
AssertIsTrue( wascalled );
This is an over-simplified example. There are of course other tests you'll want to perform, in which case you should create or find a fuller implementation of DelegateCommand that also properly implements CanExecute, among other things.

DependencyProperty changing and value coercion on their own looks like 'Impossible Dependencies' for me. Having reference to Window there makes things even trickier. I think I'd go with Humble Object pattern here...

Related

How determine fault StoryBoard

The initial problem is enough known - "Cannot animate '(0).(1)' on an immutable object instance".
There are many questions here in SO about it but all the solutions are more fixes or crutches. And most of questions are linked to concrete part of code.
Also there are few topics about this problem with possible causes:
https://wpftutorial.net/DebuggingAnimations.html
https://blogs.msdn.microsoft.com/mikehillberg/2006/09/25/tip-cannot-animate-on-an-immutable-object-instance/
I have huge corporate app where a have hundreds styles and storyboards. I can't disable them step by step and it's painstaking work to looking for problem part of code.
I look at these bug not from side of looking for in many xamls but from side of loging. I tried to research info in InvalidOperationException that is raised but there is no useful info like control place in xaml or smth else.
Also one idea is to create class inherited from Storyboard and to override methods.
But there is no methods to override.
Can someone propose how to log the internality of storyboard or other class that is responsible of animation?
At last I found sulution.
You should add classes: listener to animation, AttachedProperty and custom StoryBoard.
public static class TriggerTracing
{
static TriggerTracing()
{
// Initialise WPF Animation tracing and add a TriggerTraceListener
PresentationTraceSources.Refresh();
PresentationTraceSources.AnimationSource.Listeners.Clear();
PresentationTraceSources.AnimationSource.Listeners.Add(new TriggerTraceListener());
PresentationTraceSources.AnimationSource.Switch.Level = SourceLevels.All;
}
#region TriggerName attached property
/// <summary>
/// Gets the trigger name for the specified trigger. This will be used
/// to identify the trigger in the debug output.
/// </summary>
/// <param name="trigger">The trigger.</param>
/// <returns></returns>
public static string GetTriggerName(TriggerBase trigger)
{
return (string)trigger.GetValue(TriggerNameProperty);
}
/// <summary>
/// Sets the trigger name for the specified trigger. This will be used
/// to identify the trigger in the debug output.
/// </summary>
/// <param name="trigger">The trigger.</param>
/// <returns></returns>
public static void SetTriggerName(TriggerBase trigger, string value)
{
trigger.SetValue(TriggerNameProperty, value);
}
public static readonly DependencyProperty TriggerNameProperty =
DependencyProperty.RegisterAttached(
"TriggerName",
typeof(string),
typeof(TriggerTracing),
new UIPropertyMetadata(string.Empty));
#endregion
#region TraceEnabled attached property
/// <summary>
/// Gets a value indication whether trace is enabled for the specified trigger.
/// </summary>
/// <param name="trigger">The trigger.</param>
/// <returns></returns>
public static bool GetTraceEnabled(TriggerBase trigger)
{
return (bool)trigger.GetValue(TraceEnabledProperty);
}
/// <summary>
/// Sets a value specifying whether trace is enabled for the specified trigger
/// </summary>
/// <param name="trigger"></param>
/// <param name="value"></param>
public static void SetTraceEnabled(TriggerBase trigger, bool value)
{
trigger.SetValue(TraceEnabledProperty, value);
}
public static readonly DependencyProperty TraceEnabledProperty =
DependencyProperty.RegisterAttached(
"TraceEnabled",
typeof(bool),
typeof(TriggerTracing),
new UIPropertyMetadata(false, OnTraceEnabledChanged));
private static void OnTraceEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var triggerBase = d as EventTrigger;
if (triggerBase == null)
return;
if (!(e.NewValue is bool))
return;
if ((bool)e.NewValue)
{
// insert dummy story-boards which can later be traced using WPF animation tracing
var storyboard = new TriggerTraceStoryboard(triggerBase, TriggerTraceStoryboardType.Enter);
triggerBase.Actions.Insert(0, new BeginStoryboard() { Storyboard = storyboard });
//storyboard = new TriggerTraceStoryboard(triggerBase, TriggerTraceStoryboardType.Exit);
//triggerBase.ExitActions.Insert(0, new BeginStoryboard() { Storyboard = storyboard });
}
else
{
// remove the dummy storyboards
//foreach (TriggerActionCollection actionCollection in new[] { triggerBase.EnterActions, triggerBase.ExitActions })
foreach (TriggerActionCollection actionCollection in new[] { triggerBase.Actions })
{
foreach (TriggerAction triggerAction in actionCollection)
{
BeginStoryboard bsb = triggerAction as BeginStoryboard;
if (bsb != null && bsb.Storyboard != null && bsb.Storyboard is TriggerTraceStoryboard)
{
actionCollection.Remove(bsb);
break;
}
}
}
}
}
#endregion
private enum TriggerTraceStoryboardType
{
Enter, Exit
}
/// <summary>
/// A dummy storyboard for tracing purposes
/// </summary>
private class TriggerTraceStoryboard : Storyboard
{
public TriggerTraceStoryboardType StoryboardType { get; private set; }
public TriggerBase TriggerBase { get; private set; }
public TriggerTraceStoryboard(TriggerBase triggerBase, TriggerTraceStoryboardType storyboardType)
{
TriggerBase = triggerBase;
StoryboardType = storyboardType;
}
}
/// <summary>
/// A custom tracelistener.
/// </summary>
private class TriggerTraceListener : TraceListener
{
public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string format, params object[] args)
{
base.TraceEvent(eventCache, source, eventType, id, format, args);
if (format.StartsWith("Storyboard has begun;"))
{
TriggerTraceStoryboard storyboard = args[1] as TriggerTraceStoryboard;
if (storyboard != null)
{
// add a breakpoint here to see when your trigger has been
// entered or exited
// the element being acted upon
object targetElement = args[5];
// the namescope of the element being acted upon
INameScope namescope = (INameScope)args[7];
TriggerBase triggerBase = storyboard.TriggerBase;
string triggerName = GetTriggerName(storyboard.TriggerBase);
var str = "";
var element = targetElement as DependencyObject;
while (element != null)
{
str += element.ToString() + Environment.NewLine;
element = VisualTreeHelper.GetParent(element);
}
LoggingInfrastructure.DefaultLogger.Log(...);
}
}
}
public override void Write(string message)
{
}
public override void WriteLine(string message)
{
}
}
Then you could add property to xaml where you need:
<EventTrigger Ui:TriggerTracing.TriggerName="CopyTextBlockStyle PreviewMouseLeftButtonDown"
Ui:TriggerTracing.TraceEnabled="True" RoutedEvent="PreviewMouseLeftButtonDown">

Catel and setting usercontrol's DataContext

I've tried to find a solution myself but I was not able, for some reason the DataContext is not correctly set up in the usercontrol's viewmodel
The idea is to have a single usercontrol that permits to perform a query on a fixed collection and allows the user to drop a treeviewitem that holds an item of the collection (in case the user have the treeview open)
In my main view I've defined :
<views:PortfolioChooserView x:Name="PortfolioChooserView" DataContext="{Binding PortfolioCompleteBox}" Height="25" LoadDefaultValue="True" />
Where PortfolioCompleteBox is a ViewModel defined in the MainViewModel as
public PortfolioChooserViewModel PortfolioCompleteBox
{
get { return GetValue<PortfolioChooserViewModel>(PortfolioChooserViewModelProperty); }
set { SetValue(PortfolioChooserViewModelProperty, value); }
}
public static readonly PropertyData PortfolioChooserViewModelProperty = RegisterProperty("PortfolioCompleteBox", typeof(PortfolioChooserViewModel));
public MainViewModel(ICreditLimitRepository creditLimitRepository, IDynamicContainer dynamicContainer)
{
this.creditLimitRepository = creditLimitRepository;
this.dynamicContainer = dynamicContainer;
LoadCreditLimitsCommand = new Command<object>(OnLoadCreditLimitsExecute, (() => OnLoadCreditLimitsCanExecute));
var viewModelFactory = this.GetServiceLocator().ResolveType<IViewModelFactory>();
PortfolioCompleteBox = viewModelFactory.CreateViewModel<PortfolioChooserViewModel>(null);
Model = new FiltersLoadModel();
}
My problem is that on the PortFolioChooserView I've the DataContext set to null (and I got 2 calls to the PortFolioChooserViewModel, one from the MainViewModel and the other one from the PortFolioChooserView's viewmodel locator)
public partial class PortfolioChooserView
{
private PortfolioChooserViewModel viewModel;
readonly bool isFirstLoad = true;
/// <summary>
/// Initializes a new instance of the <see cref="PortfolioChooserView"/> class.
/// </summary>
///
public PortfolioChooserView()
{
InitializeComponent();
if (isFirstLoad)
{
PortfolioCompleteBox.AllowDrop = true;
DragDropManager.AddPreviewDragOverHandler(PortfolioCompleteBox, OnElementDragOver);
DragDropManager.AddDropHandler(PortfolioCompleteBox, OnElementDrop);
isFirstLoad = false;
this.Loaded += PortfolioChooserView_Loaded;
this.DataContextChanged += PortfolioChooserView_DataContextChanged;
}
}
void PortfolioChooserView_DataContextChanged(object sender, System.Windows.DependencyPropertyChangedEventArgs e)
{
int t = 0;
}
void PortfolioChooserView_Loaded(object sender, System.Windows.RoutedEventArgs e)
{
viewModel = (PortfolioChooserViewModel)this.DataContext;
}
private void OnElementDragOver(object sender, Telerik.Windows.DragDrop.DragEventArgs e)
{
var options = Telerik.Windows.DragDrop.DragDropPayloadManager.GetDataFromObject(e.Data, TreeViewDragDropOptions.Key) as TreeViewDragDropOptions;
if (options != null)
{
var visual = options.DragVisual as TreeViewDragVisual;
if (visual != null) visual.IsDropPossible = true;
}
e.Handled = true;
}
private void OnElementDrop(object sender, Telerik.Windows.DragDrop.DragEventArgs e)
{
var context = ((IPortfolioAutoComplete)this.DataContext);
context.SetPortfolioAutoCompleteBox(e);
}
public static readonly DependencyProperty LoadDefaultValueProperty = DependencyProperty.Register(
"LoadDefaultValue", typeof(bool), typeof(PortfolioChooserView), new PropertyMetadata(default(bool)));
public bool LoadDefaultValue
{
get { return (bool)GetValue(LoadDefaultValueProperty); }
set { SetValue(LoadDefaultValueProperty, value); }
}
}
What am I doing wrong?
Thanks
Don't try to manage your own vm's
Catel will automatically accept a parent-vm as it's own vm as long as they are compatible. You don't need to handle this manually in your view loading in the view.
Instead of creating a VM in the parent VM, use a model only (so the vm only cares about what the VM itself should do). Then set the DC of the PortfolioChooserView to the model. Then the vm of the child view can accept the model in the ctor and be managed on it's own.
There are much better ways to communicate between vm's then trying to micro-manage like you are doing now. As always, see the docs.

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.

CommandManager.InvalidateRequerySuggested() isn't fast enough. What can I do?

Short Version
Calls to CommandManager.InvalidateRequerySuggested() take far longer to take effect than I would like (1-2 second delay before UI controls become disabled).
Long Version
I have a system where I submit tasks to a background-thread based task processor. This submit happens on the WPF UI thread.
When this submit happens, the object that manages my background thread does two things:
It raises a "busy" event (still on the UI thread) that several view models respond to; when they receive this event, they set an IsEnabled flag on themselves to false. Controls in my views, which are databound to this property, are immediately grayed out, which is what I would expect.
It informs my WPF ICommand objects that they should not be allowed to execute (again, still on the UI thread). Because there is nothing like INotifyPropertyChanged for ICommand objects, I am forced to call CommandManager.InvalidateRequerySuggested() to force WPF to reconsider all of my command objects' CanExecute states (yes, I actually do need to do this: otherwise, none of these controls become disabled). Unlike item 1, though, it takes a significantly longer time for my buttons/menu items/etc that are using ICommand objects to visually change to a disabled state than it does for the UI controls that have their IsEnabled property manually set.
The problem is, from a UX point of view, this looks awful; half of my controls are immediately grayed out (because their IsEnabled property is set to false), and then a full 1-2 seconds later, the other half of my controls follow suit (because their CanExecute methods are finally re-evaluated).
So, part 1 of my question:
As silly as it sounds to ask, is there a way I can make CommandManager.InvalidateRequerySuggested() do it's job faster? I suspect that there isn't.
Fair enough, part 2 of my question:
How can I work around this? I'd prefer all of my controls be disabled at the same time. It just looks unprofessional and awkward otherwise. Any ideas? :-)
CommandManager.InvalidateRequerySuggested() tries to validate all commands, which is totally ineffective (and in your case slow) - on every change, you are asking every command to recheck its CanExecute()!
You'd need the command to know on which objects and properties is its CanExecute dependent, and suggest requery only when they change. That way, if you change a property of an object, only commands that depend on it will change their state.
This is how I solved the problem, but at first, a teaser:
// in ViewModel's constructor - add a code to public ICommand:
this.DoStuffWithParameterCommand = new DelegateCommand<object>(
parameter =>
{
//do work with parameter (remember to check against null)
},
parameter =>
{
//can this command execute? return true or false
}
)
.ListenOn(whichObject, n => n.ObjectProperty /*type safe!*/, this.Dispatcher /*we need to pass UI dispatcher here*/)
.ListenOn(anotherObject, n => n.AnotherObjectProperty, this.Dispatcher); // chain calling!
The command is listening on NotifyPropertyChanged events from object that affect whether it can execute, and invokes the check only when a requery is needed.
Now, a lot of code (part of our in-house framework) to do this:
I use DelegateCommand from Prism, that looks like this:
/// <summary>
/// This class allows delegating the commanding logic to methods passed as parameters,
/// and enables a View to bind commands to objects that are not part of the element tree.
/// </summary>
public class DelegateCommand : ICommand
{
#region Constructors
/// <summary>
/// Constructor
/// </summary>
public DelegateCommand(Action executeMethod)
: this(executeMethod, null, false)
{
}
/// <summary>
/// Constructor
/// </summary>
public DelegateCommand(Action executeMethod, Func<bool> canExecuteMethod)
: this(executeMethod, canExecuteMethod, false)
{
}
/// <summary>
/// Constructor
/// </summary>
public DelegateCommand(Action executeMethod, Func<bool> canExecuteMethod, bool isAutomaticRequeryDisabled)
{
if (executeMethod == null)
{
throw new ArgumentNullException("executeMethod");
}
_executeMethod = executeMethod;
_canExecuteMethod = canExecuteMethod;
_isAutomaticRequeryDisabled = isAutomaticRequeryDisabled;
this.RaiseCanExecuteChanged();
}
#endregion
#region Public Methods
/// <summary>
/// Method to determine if the command can be executed
/// </summary>
public bool CanExecute()
{
if (_canExecuteMethod != null)
{
return _canExecuteMethod();
}
return true;
}
/// <summary>
/// Execution of the command
/// </summary>
public void Execute()
{
if (_executeMethod != null)
{
_executeMethod();
}
}
/// <summary>
/// Property to enable or disable CommandManager's automatic requery on this command
/// </summary>
public bool IsAutomaticRequeryDisabled
{
get
{
return _isAutomaticRequeryDisabled;
}
set
{
if (_isAutomaticRequeryDisabled != value)
{
if (value)
{
CommandManagerHelper.RemoveHandlersFromRequerySuggested(_canExecuteChangedHandlers);
}
else
{
CommandManagerHelper.AddHandlersToRequerySuggested(_canExecuteChangedHandlers);
}
_isAutomaticRequeryDisabled = value;
}
}
}
/// <summary>
/// Raises the CanExecuteChaged event
/// </summary>
public void RaiseCanExecuteChanged()
{
OnCanExecuteChanged();
}
/// <summary>
/// Protected virtual method to raise CanExecuteChanged event
/// </summary>
protected virtual void OnCanExecuteChanged()
{
CommandManagerHelper.CallWeakReferenceHandlers(_canExecuteChangedHandlers);
}
#endregion
#region ICommand Members
/// <summary>
/// ICommand.CanExecuteChanged implementation
/// </summary>
public event EventHandler CanExecuteChanged
{
add
{
if (!_isAutomaticRequeryDisabled)
{
CommandManager.RequerySuggested += value;
}
CommandManagerHelper.AddWeakReferenceHandler(ref _canExecuteChangedHandlers, value, 2);
}
remove
{
if (!_isAutomaticRequeryDisabled)
{
CommandManager.RequerySuggested -= value;
}
CommandManagerHelper.RemoveWeakReferenceHandler(_canExecuteChangedHandlers, value);
}
}
bool ICommand.CanExecute(object parameter)
{
return CanExecute();
}
void ICommand.Execute(object parameter)
{
Execute();
}
#endregion
#region Data
private readonly Action _executeMethod = null;
private readonly Func<bool> _canExecuteMethod = null;
private bool _isAutomaticRequeryDisabled = false;
private List<WeakReference> _canExecuteChangedHandlers;
#endregion
}
/// <summary>
/// This class allows delegating the commanding logic to methods passed as parameters,
/// and enables a View to bind commands to objects that are not part of the element tree.
/// </summary>
/// <typeparam name="T">Type of the parameter passed to the delegates</typeparam>
public class DelegateCommand<T> : ICommand
{
#region Constructors
/// <summary>
/// Constructor
/// </summary>
public DelegateCommand(Action<T> executeMethod)
: this(executeMethod, null, false)
{
}
/// <summary>
/// Constructor
/// </summary>
public DelegateCommand(Action<T> executeMethod, Func<T, bool> canExecuteMethod)
: this(executeMethod, canExecuteMethod, false)
{
}
/// <summary>
/// Constructor
/// </summary>
public DelegateCommand(Action<T> executeMethod, Func<T, bool> canExecuteMethod, bool isAutomaticRequeryDisabled)
{
if (executeMethod == null)
{
throw new ArgumentNullException("executeMethod");
}
_executeMethod = executeMethod;
_canExecuteMethod = canExecuteMethod;
_isAutomaticRequeryDisabled = isAutomaticRequeryDisabled;
}
#endregion
#region Public Methods
/// <summary>
/// Method to determine if the command can be executed
/// </summary>
public bool CanExecute(T parameter)
{
if (_canExecuteMethod != null)
{
return _canExecuteMethod(parameter);
}
return true;
}
/// <summary>
/// Execution of the command
/// </summary>
public void Execute(T parameter)
{
if (_executeMethod != null)
{
_executeMethod(parameter);
}
}
/// <summary>
/// Raises the CanExecuteChaged event
/// </summary>
public void RaiseCanExecuteChanged()
{
OnCanExecuteChanged();
}
/// <summary>
/// Protected virtual method to raise CanExecuteChanged event
/// </summary>
protected virtual void OnCanExecuteChanged()
{
CommandManagerHelper.CallWeakReferenceHandlers(_canExecuteChangedHandlers);
}
/// <summary>
/// Property to enable or disable CommandManager's automatic requery on this command
/// </summary>
public bool IsAutomaticRequeryDisabled
{
get
{
return _isAutomaticRequeryDisabled;
}
set
{
if (_isAutomaticRequeryDisabled != value)
{
if (value)
{
CommandManagerHelper.RemoveHandlersFromRequerySuggested(_canExecuteChangedHandlers);
}
else
{
CommandManagerHelper.AddHandlersToRequerySuggested(_canExecuteChangedHandlers);
}
_isAutomaticRequeryDisabled = value;
}
}
}
#endregion
#region ICommand Members
/// <summary>
/// ICommand.CanExecuteChanged implementation
/// </summary>
public event EventHandler CanExecuteChanged
{
add
{
if (!_isAutomaticRequeryDisabled)
{
CommandManager.RequerySuggested += value;
}
CommandManagerHelper.AddWeakReferenceHandler(ref _canExecuteChangedHandlers, value, 2);
}
remove
{
if (!_isAutomaticRequeryDisabled)
{
CommandManager.RequerySuggested -= value;
}
CommandManagerHelper.RemoveWeakReferenceHandler(_canExecuteChangedHandlers, value);
}
}
bool ICommand.CanExecute(object parameter)
{
// if T is of value type and the parameter is not
// set yet, then return false if CanExecute delegate
// exists, else return true
if (parameter == null &&
typeof(T).IsValueType)
{
return (_canExecuteMethod == null);
}
return CanExecute((T)parameter);
}
void ICommand.Execute(object parameter)
{
Execute((T)parameter);
}
#endregion
#region Data
private readonly Action<T> _executeMethod = null;
private readonly Func<T, bool> _canExecuteMethod = null;
private bool _isAutomaticRequeryDisabled = false;
private List<WeakReference> _canExecuteChangedHandlers;
#endregion
}
/// <summary>
/// This class contains methods for the CommandManager that help avoid memory leaks by
/// using weak references.
/// </summary>
internal class CommandManagerHelper
{
internal static void CallWeakReferenceHandlers(List<WeakReference> handlers)
{
if (handlers != null)
{
// Take a snapshot of the handlers before we call out to them since the handlers
// could cause the array to me modified while we are reading it.
EventHandler[] callees = new EventHandler[handlers.Count];
int count = 0;
for (int i = handlers.Count - 1; i >= 0; i--)
{
WeakReference reference = handlers[i];
EventHandler handler = reference.Target as EventHandler;
if (handler == null)
{
// Clean up old handlers that have been collected
handlers.RemoveAt(i);
}
else
{
callees[count] = handler;
count++;
}
}
// Call the handlers that we snapshotted
for (int i = 0; i < count; i++)
{
EventHandler handler = callees[i];
handler(null, EventArgs.Empty);
}
}
}
internal static void AddHandlersToRequerySuggested(List<WeakReference> handlers)
{
if (handlers != null)
{
foreach (WeakReference handlerRef in handlers)
{
EventHandler handler = handlerRef.Target as EventHandler;
if (handler != null)
{
CommandManager.RequerySuggested += handler;
}
}
}
}
internal static void RemoveHandlersFromRequerySuggested(List<WeakReference> handlers)
{
if (handlers != null)
{
foreach (WeakReference handlerRef in handlers)
{
EventHandler handler = handlerRef.Target as EventHandler;
if (handler != null)
{
CommandManager.RequerySuggested -= handler;
}
}
}
}
internal static void AddWeakReferenceHandler(ref List<WeakReference> handlers, EventHandler handler)
{
AddWeakReferenceHandler(ref handlers, handler, -1);
}
internal static void AddWeakReferenceHandler(ref List<WeakReference> handlers, EventHandler handler, int defaultListSize)
{
if (handlers == null)
{
handlers = (defaultListSize > 0 ? new List<WeakReference>(defaultListSize) : new List<WeakReference>());
}
handlers.Add(new WeakReference(handler));
}
internal static void RemoveWeakReferenceHandler(List<WeakReference> handlers, EventHandler handler)
{
if (handlers != null)
{
for (int i = handlers.Count - 1; i >= 0; i--)
{
WeakReference reference = handlers[i];
EventHandler existingHandler = reference.Target as EventHandler;
if ((existingHandler == null) || (existingHandler == handler))
{
// Clean up old handlers that have been collected
// in addition to the handler that is to be removed.
handlers.RemoveAt(i);
}
}
}
}
}
I have then written a ListenOn extension method, that 'binds' the command to a property, and invokes its RaiseCanExecuteChanged:
public static class DelegateCommandExtensions
{
/// <summary>
/// Makes DelegateCommnand listen on PropertyChanged events of some object,
/// so that DelegateCommnand can update its IsEnabled property.
/// </summary>
public static DelegateCommand ListenOn<ObservedType, PropertyType>
(this DelegateCommand delegateCommand,
ObservedType observedObject,
Expression<Func<ObservedType, PropertyType>> propertyExpression,
Dispatcher dispatcher)
where ObservedType : INotifyPropertyChanged
{
//string propertyName = observedObject.GetPropertyName(propertyExpression);
string propertyName = NotifyPropertyChangedBaseExtensions.GetPropertyName(propertyExpression);
observedObject.PropertyChanged += (sender, e) =>
{
if (e.PropertyName == propertyName)
{
if (dispatcher != null)
{
ThreadTools.RunInDispatcher(dispatcher, delegateCommand.RaiseCanExecuteChanged);
}
else
{
delegateCommand.RaiseCanExecuteChanged();
}
}
};
return delegateCommand; //chain calling
}
/// <summary>
/// Makes DelegateCommnand listen on PropertyChanged events of some object,
/// so that DelegateCommnand can update its IsEnabled property.
/// </summary>
public static DelegateCommand<T> ListenOn<T, ObservedType, PropertyType>
(this DelegateCommand<T> delegateCommand,
ObservedType observedObject,
Expression<Func<ObservedType, PropertyType>> propertyExpression,
Dispatcher dispatcher)
where ObservedType : INotifyPropertyChanged
{
//string propertyName = observedObject.GetPropertyName(propertyExpression);
string propertyName = NotifyPropertyChangedBaseExtensions.GetPropertyName(propertyExpression);
observedObject.PropertyChanged += (object sender, PropertyChangedEventArgs e) =>
{
if (e.PropertyName == propertyName)
{
if (dispatcher != null)
{
ThreadTools.RunInDispatcher(dispatcher, delegateCommand.RaiseCanExecuteChanged);
}
else
{
delegateCommand.RaiseCanExecuteChanged();
}
}
};
return delegateCommand; //chain calling
}
}
You then need the following extension to NotifyPropertyChanged
/// <summary>
/// <see cref="http://dotnet.dzone.com/news/silverlightwpf-implementing"/>
/// </summary>
public static class NotifyPropertyChangedBaseExtensions
{
/// <summary>
/// Raises PropertyChanged event.
/// To use: call the extension method with this: this.OnPropertyChanged(n => n.Title);
/// </summary>
/// <typeparam name="T">Property owner</typeparam>
/// <typeparam name="TProperty">Type of property</typeparam>
/// <param name="observableBase"></param>
/// <param name="expression">Property expression like 'n => n.Property'</param>
public static void OnPropertyChanged<T, TProperty>(this T observableBase, Expression<Func<T, TProperty>> expression) where T : INotifyPropertyChangedWithRaise
{
observableBase.OnPropertyChanged(GetPropertyName<T, TProperty>(expression));
}
public static string GetPropertyName<T, TProperty>(Expression<Func<T, TProperty>> expression) where T : INotifyPropertyChanged
{
if (expression == null)
throw new ArgumentNullException("expression");
var lambda = expression as LambdaExpression;
MemberExpression memberExpression;
if (lambda.Body is UnaryExpression)
{
var unaryExpression = lambda.Body as UnaryExpression;
memberExpression = unaryExpression.Operand as MemberExpression;
}
else
{
memberExpression = lambda.Body as MemberExpression;
}
if (memberExpression == null)
throw new ArgumentException("Please provide a lambda expression like 'n => n.PropertyName'");
MemberInfo memberInfo = memberExpression.Member;
if (String.IsNullOrEmpty(memberInfo.Name))
throw new ArgumentException("'expression' did not provide a property name.");
return memberInfo.Name;
}
}
where INotifyPropertyChangedWithRaise is this (it estabilishes standard interface for raising NotifyPropertyChanged events):
public interface INotifyPropertyChangedWithRaise : INotifyPropertyChanged
{
void OnPropertyChanged(string propertyName);
}
Last piece of puzzle is this:
public class ThreadTools
{
public static void RunInDispatcher(Dispatcher dispatcher, Action action)
{
RunInDispatcher(dispatcher, DispatcherPriority.Normal, action);
}
public static void RunInDispatcher(Dispatcher dispatcher, DispatcherPriority priority, Action action)
{
if (action == null) { return; }
if (dispatcher.CheckAccess())
{
// we are already on thread associated with the dispatcher -> just call action
try
{
action();
}
catch (Exception ex)
{
//Log error here!
}
}
else
{
// we are on different thread, invoke action on dispatcher's thread
dispatcher.BeginInvoke(
priority,
(Action)(
() =>
{
try
{
action();
}
catch (Exception ex)
{
//Log error here!
}
})
);
}
}
}
This solution is a reduced version of the solution proposed by Tomáš Kafka(thanks to Tomas for describing his solution in detail)in this thread.
In Tomas's solution he had
1) DelegateCommand
2) CommandManagerHelper
3) DelegateCommandExtensions
4) NotifyPropertyChangedBaseExtensions
5) INotifyPropertyChangedWithRaise
6) ThreadTools
This solution has
1) DelegateCommand
2) DelegateCommandExtensions method and NotifyPropertyChangedBaseExtensions method in Delegate Command itself.
Note Since our wpf application follows MVVM pattern and we handle commands at viewmodel level which executes in UI thread we don't need to get the reference to UI disptacher.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq.Expressions;
using System.Reflection;
using System.Windows.Input;
namespace ExampleForDelegateCommand
{
public class DelegateCommand : ICommand
{
public Predicate<object> CanExecuteDelegate { get; set; }
private List<INotifyPropertyChanged> propertiesToListenTo;
private List<WeakReference> ControlEvent;
public DelegateCommand()
{
ControlEvent= new List<WeakReference>();
}
public List<INotifyPropertyChanged> PropertiesToListenTo
{
get { return propertiesToListenTo; }
set
{
propertiesToListenTo = value;
}
}
private Action<object> executeDelegate;
public Action<object> ExecuteDelegate
{
get { return executeDelegate; }
set
{
executeDelegate = value;
ListenForNotificationFrom((INotifyPropertyChanged)executeDelegate.Target);
}
}
public static ICommand Create(Action<object> exec)
{
return new SimpleCommand { ExecuteDelegate = exec };
}
#region ICommand Members
public bool CanExecute(object parameter)
{
if (CanExecuteDelegate != null)
return CanExecuteDelegate(parameter);
return true; // if there is no can execute default to true
}
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
ControlEvent.Add(new WeakReference(value));
}
remove
{
CommandManager.RequerySuggested -= value;
ControlEvent.Remove(ControlEvent.Find(r => ((EventHandler) r.Target) == value));
}
}
public void Execute(object parameter)
{
if (ExecuteDelegate != null)
ExecuteDelegate(parameter);
}
#endregion
public void RaiseCanExecuteChanged()
{
if (ControlEvent != null && ControlEvent.Count > 0)
{
ControlEvent.ForEach(ce =>
{
if(ce.Target!=null)
((EventHandler) (ce.Target)).Invoke(null, EventArgs.Empty);
});
}
}
public DelegateCommand ListenOn<TObservedType, TPropertyType>(TObservedType viewModel, Expression<Func<TObservedType, TPropertyType>> propertyExpression) where TObservedType : INotifyPropertyChanged
{
string propertyName = GetPropertyName(propertyExpression);
viewModel.PropertyChanged += (PropertyChangedEventHandler)((sender, e) =>
{
if (e.PropertyName == propertyName) RaiseCanExecuteChanged();
});
return this;
}
public void ListenForNotificationFrom<TObservedType>(TObservedType viewModel) where TObservedType : INotifyPropertyChanged
{
viewModel.PropertyChanged += (PropertyChangedEventHandler)((sender, e) =>
{
RaiseCanExecuteChanged();
});
}
private string GetPropertyName<T, TProperty>(Expression<Func<T, TProperty>> expression) where T : INotifyPropertyChanged
{
var lambda = expression as LambdaExpression;
MemberInfo memberInfo = GetmemberExpression(lambda).Member;
return memberInfo.Name;
}
private MemberExpression GetmemberExpression(LambdaExpression lambda)
{
MemberExpression memberExpression;
if (lambda.Body is UnaryExpression)
{
var unaryExpression = lambda.Body as UnaryExpression;
memberExpression = unaryExpression.Operand as MemberExpression;
}
else
memberExpression = lambda.Body as MemberExpression;
return memberExpression;
}
}}
Explanation of the solution:
Normally when we bind a UI element(Button)to the ICommand implementation the WPF Button registers for a Event "CanExecuteChanged" in ICommand implementation .If your Icommand implementation for "CanExecuteChanged" hook to the CommandManager's RequesySuggest event(read this article http://joshsmithonwpf.wordpress.com/2008/06/17/allowing-commandmanager-to-query-your-icommand-objects/) then when ever CommandManager detects conditions that might change the ability of a command to execute(changes like Focus shifts and some keyboard events) , CommandManager’s RequerySuggested event occurs which in turn will cause Button'e delegate to be called since we hooked the buttos's delgate to CommandManager’s RequerySuggested in the implementation of "CanExecuteChanged" in our DelegateCommand .
But the problem is that ComandManager is not able to always detect the changes. Hence the solution it to raise "CanExecuteChanged" when our command implementation(DelegateCommand) detects there is a change.Normally when we declare the delagate for ICommand's CanExecute in our viewmodel we bind to properties declared in our viewmodel and our ICommand implementation can listen for "propertychanged" events on these properties. Thats what the "ListenForNotificationFrom" method of the DelegateCommand does. In case the client code does not register for specific property changes the DelegateCommand by defaults listens to any property change on the view model where command is declared and defined.
"ControlEvent" in DelegateCommand which is list of EventHandler that stores the Button's
"CanExecuteChange EventHandler" is declared as weak reference to avoid memory leaks.
How will ViewModel use this DelegateCommand
There are 2 ways to use it.
(the second usage is more specific to the properties that you want the Command to listen to.
delegateCommand = new DelegateCommand
{
ExecuteDelegate = Search,
CanExecuteDelegate = (r) => !IsBusy
};
anotherDelegateCommand = new DelegateCommand
{
ExecuteDelegate = SearchOne,
CanExecuteDelegate = (r) => !IsBusyOne
}.ListenOn(this, n => n.IsBusyOne);
A detailed ViewModel
public class ExampleViewModel
{
public SearchViewModelBase()
{
delegateCommand = new DelegateCommand
{
ExecuteDelegate = Search,
CanExecuteDelegate = (r) => !IsBusy
};
anotherDelegateCommand = new DelegateCommand
{
ExecuteDelegate = SearchOne,
CanExecuteDelegate = (r) => !IsBusyOne
}.ListenOn(this, n => n.IsBusyOne);
}
private bool isBusy;
public virtual bool IsBusy
{
get { return isBusy; }
set
{
if (isBusy == value) return;
isBusy = value;
NotifyPropertyChanged(MethodBase.GetCurrentMethod());
}
}
private bool isBusyOne;
public virtual bool IsBusyOne
{
get { return isBusyOne; }
set
{
if (isBusyOne == value) return;
isBusyOne = value;
NotifyPropertyChanged(MethodBase.GetCurrentMethod());
}
}
private void Search(object obj)
{
IsBusy = true;
new SearchService().Search(Callback);
}
public void Callback(ServiceResponse response)
{
IsBusy = false;
}
private void Search(object obj)
{
IsBusyOne = true;
new SearchService().Search(CallbackOne);
}
public void CallbackOne(ServiceResponse response)
{
IsBusyOne = false;
}
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private void NotifyPropertyChanged(MethodBase methodBase)
{
string methodName = methodBase.Name;
if (!methodName.StartsWith("set_"))
{
var ex = new ArgumentException("MethodBase must refer to a Property Setter method.");
throw ex;
}
NotifyPropertyChanged(methodName.Substring(4));
}
}
Tomas has a nice solution, but pls note there's a serious bug in that the CanExecute will not always fire when bound to a Button due to this :
// Call the handlers that we snapshotted
for (int i = 0; i < count; i++)
{
EventHandler handler = callees[i];
handler(null, EventArgs.Empty);
}
The 'null' parameter passed in causes issues with the CanExecuteChangedEventManager (used by the WPF Button class to listen to changes on any Command bound to it). Specifically, the CanExecuteChangedEventManager maintains a collection of weak events that need to be invoked to determine if the command Can-Execute() but this collection is keyed by the 'sender'.
The fix is simple and works for me - change the signature to
internal static void CallWeakReferenceHandlers(ICommand sender, List<WeakReference> handlers)
{
....
handler(sender, EventArgs.Empty);
}
Sorry I haven't described it too well - in a bit of a rush to catch up with my dev now after taking a few hours to figure this out !
I would suggest looking into ReactiveUI and specifically at the ICommand implementation it provides, ReactiveCommand. It uses a different approach than DelegateCommand/RelayCommand which are implemented with delegates for CanExecute that must be actively evaluated. ReactiveCommand's value for CanExecute is pushed using IObservables.
is there a way I can make CommandManager.InvalidateRequerySuggested() do it's job faster?
Yes, there is way to make it work faster!
Implement Command to keep / cache CanExecuteState in a boolean variable.
Implement RaiseCanExecuteChanged method to recalculate CanExecuteState and if it really changed to raise CanExecuteChanged event.
Implement CanExecute method to simply return CanExecuteState.
When InvalidateRequerySuggested method is invoked Command subscribers will only read CanExecuteState variable by invoking CanExecute method and check if it changed or not. That's almost zero overhead. All Commands will be disabled / enabled almost the same time.
All work will be done in RaiseCanExecuteChanged method that will be called only once for a Command and only for a limited set of Commands.
Try writing your own binding that calls your RaiseCanExecuteChanged() within converts? it is easier
Just to clarify:
You want to fire an update of CanExecute when Command property changed
Create your own binding class that detect changes in the Command property and then calls RaiseCanExecuteChanged()
Use this binding in CommandParameter
Worked for me.

When Clearing an ObservableCollection, There are No Items in e.OldItems

I have something here that is really catching me off guard.
I have an ObservableCollection of T that is filled with items. I also have an event handler attached to the CollectionChanged event.
When you Clear the collection it causes an CollectionChanged event with e.Action set to NotifyCollectionChangedAction.Reset. Ok, that's normal. But what is weird is that neither e.OldItems or e.NewItems has anything in it. I would expect e.OldItems to be filled with all items that were removed from the collection.
Has anyone else seen this? And if so, how have they gotten around it?
Some background: I am using the CollectionChanged event to attach and detach from another event and thus if I don't get any items in e.OldItems ... I won't be able to detach from that event.
CLARIFICATION:
I do know that the documentation doesn't outright state that it has to behave this way. But for every other action, it is notifying me of what it has done. So, my assumption is that it would tell me ... in the case of Clear/Reset as well.
Below is the sample code if you wish to reproduce it yourself. First off the xaml:
<Window
x:Class="ObservableCollection.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1"
Height="300"
Width="300"
>
<StackPanel>
<Button x:Name="addButton" Content="Add" Width="100" Height="25" Margin="10" Click="addButton_Click"/>
<Button x:Name="moveButton" Content="Move" Width="100" Height="25" Margin="10" Click="moveButton_Click"/>
<Button x:Name="removeButton" Content="Remove" Width="100" Height="25" Margin="10" Click="removeButton_Click"/>
<Button x:Name="replaceButton" Content="Replace" Width="100" Height="25" Margin="10" Click="replaceButton_Click"/>
<Button x:Name="resetButton" Content="Reset" Width="100" Height="25" Margin="10" Click="resetButton_Click"/>
</StackPanel>
</Window>
Next, the code behind:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Collections.ObjectModel;
namespace ObservableCollection
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
_integerObservableCollection.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(_integerObservableCollection_CollectionChanged);
}
private void _integerObservableCollection_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case System.Collections.Specialized.NotifyCollectionChangedAction.Add:
break;
case System.Collections.Specialized.NotifyCollectionChangedAction.Move:
break;
case System.Collections.Specialized.NotifyCollectionChangedAction.Remove:
break;
case System.Collections.Specialized.NotifyCollectionChangedAction.Replace:
break;
case System.Collections.Specialized.NotifyCollectionChangedAction.Reset:
break;
default:
break;
}
}
private void addButton_Click(object sender, RoutedEventArgs e)
{
_integerObservableCollection.Add(25);
}
private void moveButton_Click(object sender, RoutedEventArgs e)
{
_integerObservableCollection.Move(0, 19);
}
private void removeButton_Click(object sender, RoutedEventArgs e)
{
_integerObservableCollection.RemoveAt(0);
}
private void replaceButton_Click(object sender, RoutedEventArgs e)
{
_integerObservableCollection[0] = 50;
}
private void resetButton_Click(object sender, RoutedEventArgs e)
{
_integerObservableCollection.Clear();
}
private ObservableCollection<int> _integerObservableCollection = new ObservableCollection<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 };
}
}
It doesn't claim to include the old items, because Reset doesn't mean that the list has been cleared
It means that some dramatic thing has taken place, and the cost of working out the add/removes would most likely exceed the cost of just re-scanning the list from scratch... so that's what you should do.
MSDN suggests an example of the entire collection being re-sorted as a candidate for reset.
To reiterate. Reset doesn't mean clear, it means Your assumptions about the list are now invalid. Treat it as if it's an entirely new list. Clear happens to be one instance of this, but there could well be others.
Some examples:
I've had a list like this with a lot of items in it, and it has been databound to a WPF ListView to display on-screen.
If you clear the list and raise the .Reset event, the performance is pretty much instant, but if you instead raise many individual .Remove events, the performance is terrible, as WPF removes the items one by one.
I've also used .Reset in my own code to indicate that the list has been re-sorted, rather than issuing thousands of individual Move operations. As with Clear, there is a large performance hit when when raising many individual events.
We had the same issue here. The Reset action in CollectionChanged does not include the OldItems. We had a workaround: we used instead the following extension method:
public static void RemoveAll(this IList list)
{
while (list.Count > 0)
{
list.RemoveAt(list.Count - 1);
}
}
We ended up not supporting the Clear() function, and throwing a NotSupportedException in CollectionChanged event for Reset actions. The RemoveAll will trigger a Remove action in CollectionChanged event, with the proper OldItems.
Okay, I know this is a very old question but I have come up with a good solution to the issue and thought I would share.
This solution takes inspiration from a lot of the great answers here but has the following advantages:
No need to create a new class and override methods from ObservableCollection
Does not tamper with the workings of NotifyCollectionChanged (so no messing with Reset)
Does not make use of reflection
Here is the code:
public static void Clear<T>(this ObservableCollection<T> collection, Action<ObservableCollection<T>> unhookAction)
{
unhookAction.Invoke(collection);
collection.Clear();
}
This extension method simply takes an Action which will be invoked before the collection is cleared.
Another option is to replace the Reset event with a single Remove event that has all the cleared items in its OldItems property as follows:
public class ObservableCollectionNoReset<T> : ObservableCollection<T>
{
protected override void ClearItems()
{
List<T> removed = new List<T>(this);
base.ClearItems();
base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed));
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (e.Action != NotifyCollectionChangedAction.Reset)
base.OnCollectionChanged(e);
}
// Constructors omitted
...
}
Advantages:
No need to subscribe to an additional event (as required by accepted answer)
Doesn't generate an event for each object removed (some other proposed solutions result in multiple Removed events).
Subscriber only needs to check NewItems & OldItems on any event to add/remove event handlers as required.
Disadvantages:
No Reset event
Small (?) overhead creating copy of list.
???
EDIT 2012-02-23
Unfortunately, when bound to WPF list based controls, Clearing a ObservableCollectionNoReset collection with multiple elements will result in an exception "Range actions not supported".
To be used with controls with this limitation, I changed the ObservableCollectionNoReset class to:
public class ObservableCollectionNoReset<T> : ObservableCollection<T>
{
// Some CollectionChanged listeners don't support range actions.
public Boolean RangeActionsSupported { get; set; }
protected override void ClearItems()
{
if (RangeActionsSupported)
{
List<T> removed = new List<T>(this);
base.ClearItems();
base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed));
}
else
{
while (Count > 0 )
base.RemoveAt(Count - 1);
}
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (e.Action != NotifyCollectionChangedAction.Reset)
base.OnCollectionChanged(e);
}
public ObservableCollectionNoReset(Boolean rangeActionsSupported = false)
{
RangeActionsSupported = rangeActionsSupported;
}
// Additional constructors omitted.
}
This isn't as efficient when RangeActionsSupported is false (the default) because one Remove notification is generated per object in the collection
Ok, even though I still wish that ObservableCollection behaved as I wished ... the code below is what I ended up doing. Basically, I created a new collection of T called TrulyObservableCollection and overrided the ClearItems method which I then used to raise a Clearing event.
In the code that uses this TrulyObservableCollection, I use this Clearing event to loop through the items that are still in the collection at that point to do the detach on the event that I was wishing to detach from.
Hope this approach helps someone else as well.
public class TrulyObservableCollection<T> : ObservableCollection<T>
{
public event EventHandler<EventArgs> Clearing;
protected virtual void OnClearing(EventArgs e)
{
if (Clearing != null)
Clearing(this, e);
}
protected override void ClearItems()
{
OnClearing(EventArgs.Empty);
base.ClearItems();
}
}
I've found a solution that allows the user to both capitalize on the efficiency of adding or removing many items at a time while only firing one event - and satisfy the needs of UIElements to get the Action.Reset event args while all other users would like a list of elements added and removed.
This solution involves overriding the CollectionChanged event. When we go to fire this event, we can actually look at the target of each registered handler and determine their type. Since only ICollectionView classes require NotifyCollectionChangedAction.Reset args when more than one item changes, we can single them out, and give everyone else proper event args that contain the full list of items removed or added. Below is the implementation.
public class BaseObservableCollection<T> : ObservableCollection<T>
{
//Flag used to prevent OnCollectionChanged from firing during a bulk operation like Add(IEnumerable<T>) and Clear()
private bool _SuppressCollectionChanged = false;
/// Overridden so that we may manually call registered handlers and differentiate between those that do and don't require Action.Reset args.
public override event NotifyCollectionChangedEventHandler CollectionChanged;
public BaseObservableCollection() : base(){}
public BaseObservableCollection(IEnumerable<T> data) : base(data){}
#region Event Handlers
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if( !_SuppressCollectionChanged )
{
base.OnCollectionChanged(e);
if( CollectionChanged != null )
CollectionChanged.Invoke(this, e);
}
}
//CollectionViews raise an error when they are passed a NotifyCollectionChangedEventArgs that indicates more than
//one element has been added or removed. They prefer to receive a "Action=Reset" notification, but this is not suitable
//for applications in code, so we actually check the type we're notifying on and pass a customized event args.
protected virtual void OnCollectionChangedMultiItem(NotifyCollectionChangedEventArgs e)
{
NotifyCollectionChangedEventHandler handlers = this.CollectionChanged;
if( handlers != null )
foreach( NotifyCollectionChangedEventHandler handler in handlers.GetInvocationList() )
handler(this, !(handler.Target is ICollectionView) ? e : new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
#endregion
#region Extended Collection Methods
protected override void ClearItems()
{
if( this.Count == 0 ) return;
List<T> removed = new List<T>(this);
_SuppressCollectionChanged = true;
base.ClearItems();
_SuppressCollectionChanged = false;
OnCollectionChangedMultiItem(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removed));
}
public void Add(IEnumerable<T> toAdd)
{
if( this == toAdd )
throw new Exception("Invalid operation. This would result in iterating over a collection as it is being modified.");
_SuppressCollectionChanged = true;
foreach( T item in toAdd )
Add(item);
_SuppressCollectionChanged = false;
OnCollectionChangedMultiItem(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new List<T>(toAdd)));
}
public void Remove(IEnumerable<T> toRemove)
{
if( this == toRemove )
throw new Exception("Invalid operation. This would result in iterating over a collection as it is being modified.");
_SuppressCollectionChanged = true;
foreach( T item in toRemove )
Remove(item);
_SuppressCollectionChanged = false;
OnCollectionChangedMultiItem(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new List<T>(toRemove)));
}
#endregion
}
I tackled this one in a slightly different manner as I wanted to register to one event and handle all additions and removals in the event handler. I started off overriding the collection changed event and redirecting reset actions to removal actions with a list of items. This all went wrong as I was using the observable collection as an items source for a collection view and got "Range actions not supported".
I finally created a new event called CollectionChangedRange which acts in the manner I expected the inbuilt version to act.
I can't imagine why this limitation would be allowed and hope that this post at least stops others from going down the dead end that I did.
/// <summary>
/// An observable collection with support for addrange and clear
/// </summary>
/// <typeparam name="T"></typeparam>
[Serializable]
[TypeConverter(typeof(ExpandableObjectConverter))]
public class ObservableCollectionRange<T> : ObservableCollection<T>
{
private bool _addingRange;
[field: NonSerialized]
public event NotifyCollectionChangedEventHandler CollectionChangedRange;
protected virtual void OnCollectionChangedRange(NotifyCollectionChangedEventArgs e)
{
if ((CollectionChangedRange == null) || _addingRange) return;
using (BlockReentrancy())
{
CollectionChangedRange(this, e);
}
}
public void AddRange(IEnumerable<T> collection)
{
CheckReentrancy();
var newItems = new List<T>();
if ((collection == null) || (Items == null)) return;
using (var enumerator = collection.GetEnumerator())
{
while (enumerator.MoveNext())
{
_addingRange = true;
Add(enumerator.Current);
_addingRange = false;
newItems.Add(enumerator.Current);
}
}
OnCollectionChangedRange(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, newItems));
}
protected override void ClearItems()
{
CheckReentrancy();
var oldItems = new List<T>(this);
base.ClearItems();
OnCollectionChangedRange(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, oldItems));
}
protected override void InsertItem(int index, T item)
{
CheckReentrancy();
base.InsertItem(index, item);
OnCollectionChangedRange(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, index));
}
protected override void MoveItem(int oldIndex, int newIndex)
{
CheckReentrancy();
var item = base[oldIndex];
base.MoveItem(oldIndex, newIndex);
OnCollectionChangedRange(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Move, item, newIndex, oldIndex));
}
protected override void RemoveItem(int index)
{
CheckReentrancy();
var item = base[index];
base.RemoveItem(index);
OnCollectionChangedRange(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, index));
}
protected override void SetItem(int index, T item)
{
CheckReentrancy();
var oldItem = base[index];
base.SetItem(index, item);
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, oldItem, item, index));
}
}
/// <summary>
/// A read only observable collection with support for addrange and clear
/// </summary>
/// <typeparam name="T"></typeparam>
[Serializable]
[TypeConverter(typeof(ExpandableObjectConverter))]
public class ReadOnlyObservableCollectionRange<T> : ReadOnlyObservableCollection<T>
{
[field: NonSerialized]
public event NotifyCollectionChangedEventHandler CollectionChangedRange;
public ReadOnlyObservableCollectionRange(ObservableCollectionRange<T> list) : base(list)
{
list.CollectionChangedRange += HandleCollectionChangedRange;
}
private void HandleCollectionChangedRange(object sender, NotifyCollectionChangedEventArgs e)
{
OnCollectionChangedRange(e);
}
protected virtual void OnCollectionChangedRange(NotifyCollectionChangedEventArgs args)
{
if (CollectionChangedRange != null)
{
CollectionChangedRange(this, args);
}
}
}
This is how ObservableCollection works, you can work around this by keeping your own list outside of the ObservableCollection (adding to the list when action is Add, remove when action is Remove etc.) then you can get all the removed items (or added items) when action is Reset by comparing your list with the ObservableCollection.
Another option is to create your own class that implements IList and INotifyCollectionChanged, then you can attach and detach events from within that class (or set OldItems on Clear if you like) - it's really not difficult, but it is a lot of typing.
For the scenario of attaching and detaching event handlers to the elements of the ObservableCollection there is also a "client-side" solution. In the event handling code you can check if the sender is in the ObservableCollection using the Contains method. Pro: you can work with any existing ObservableCollection. Cons: the Contains method runs with O(n) where n is the number of elements in the ObservableCollection. So this is a solution for small ObservableCollections.
Another "client-side" solution is to use an event handler in the middle. Just register all events to the event handler in the middle. This event handler in turn notifies the real event handler trough a callback or an event. If a Reset action occurs remove the callback or event create a new event handler in the middle and forget about the old one. This approach also works for big ObservableCollections. I used this for the PropertyChanged event (see code below).
/// <summary>
/// Helper class that allows to "detach" all current Eventhandlers by setting
/// DelegateHandler to null.
/// </summary>
public class PropertyChangedDelegator
{
/// <summary>
/// Callback to the real event handling code.
/// </summary>
public PropertyChangedEventHandler DelegateHandler;
/// <summary>
/// Eventhandler that is registered by the elements.
/// </summary>
/// <param name="sender">the element that has been changed.</param>
/// <param name="e">the event arguments</param>
public void PropertyChangedHandler(Object sender, PropertyChangedEventArgs e)
{
if (DelegateHandler != null)
{
DelegateHandler(sender, e);
}
else
{
INotifyPropertyChanged s = sender as INotifyPropertyChanged;
if (s != null)
s.PropertyChanged -= PropertyChangedHandler;
}
}
}
Looking at the NotifyCollectionChangedEventArgs, it appears that OldItems only contains items changed as a result of Replace, Remove, or Move action. It doesn't indicate that it will contain anything on Clear. I suspect that Clear fires the event, but does not registered the removed items and does not invoke the Remove code at all.
Well, I decided to get dirty with it myself.
Microsoft put a LOT of work into always making sure the NotifyCollectionChangedEventArgs doesn't have any data when calling a reset. I'm assuming this was a performance/memory decision. If you are resetting a collection with 100,000 elements, I'm assuming they didn't want to duplicate all those elements.
But seeing as my collections never have more then 100 elements, I don't see a problem with it.
Anyway I created an inherited class with the following method:
protected override void ClearItems()
{
CheckReentrancy();
List<TItem> oldItems = new List<TItem>(Items);
Items.Clear();
OnPropertyChanged(new PropertyChangedEventArgs("Count"));
OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
NotifyCollectionChangedEventArgs e =
new NotifyCollectionChangedEventArgs
(
NotifyCollectionChangedAction.Reset
);
FieldInfo field =
e.GetType().GetField
(
"_oldItems",
BindingFlags.Instance | BindingFlags.NonPublic
);
field.SetValue(e, oldItems);
OnCollectionChanged(e);
}
The ObservableCollection as well as the INotifyCollectionChanged interface are clearly written with a specific use in mind: UI building and its specific performance characteristics.
When you want notifications of collection changes then you are generally only interested in Add and Remove events.
I use the following interface:
using System;
using System.Collections.Generic;
/// <summary>
/// Notifies listeners of the following situations:
/// <list type="bullet">
/// <item>Elements have been added.</item>
/// <item>Elements are about to be removed.</item>
/// </list>
/// </summary>
/// <typeparam name="T">The type of elements in the collection.</typeparam>
interface INotifyCollection<T>
{
/// <summary>
/// Occurs when elements have been added.
/// </summary>
event EventHandler<NotifyCollectionEventArgs<T>> Added;
/// <summary>
/// Occurs when elements are about to be removed.
/// </summary>
event EventHandler<NotifyCollectionEventArgs<T>> Removing;
}
/// <summary>
/// Provides data for the NotifyCollection event.
/// </summary>
/// <typeparam name="T">The type of elements in the collection.</typeparam>
public class NotifyCollectionEventArgs<T> : EventArgs
{
/// <summary>
/// Gets or sets the elements.
/// </summary>
/// <value>The elements.</value>
public IEnumerable<T> Items
{
get;
set;
}
}
I've also written my own overload of Collection where:
ClearItems raises Removing
InsertItem raises Added
RemoveItem raises Removing
SetItem raises Removing and Added
Of course, AddRange can be added as well.
I was just going through some of the charting code in the Silverlight and WPF toolkits and noticed that they also solved this problem (in a kind of similar way) ... and I thought I would go ahead and post their solution.
Basically, they also created a derived ObservableCollection and overrode ClearItems, calling Remove on each item being cleared.
Here is the code:
/// <summary>
/// An observable collection that cannot be reset. When clear is called
/// items are removed individually, giving listeners the chance to detect
/// each remove event and perform operations such as unhooking event
/// handlers.
/// </summary>
/// <typeparam name="T">The type of item in the collection.</typeparam>
public class NoResetObservableCollection<T> : ObservableCollection<T>
{
public NoResetObservableCollection()
{
}
/// <summary>
/// Clears all items in the collection by removing them individually.
/// </summary>
protected override void ClearItems()
{
IList<T> items = new List<T>(this);
foreach (T item in items)
{
Remove(item);
}
}
}
This is a hot subject ... because in my opinion, Microsoft did not do its job properly ... yet again. Don't misunderstand me, I like Microsoft, but they are not perfect!
I read most of the previous comments. I agree with all those who think that Microsoft did not programmed Clear() properly.
In my opinion, at least, it needs an argument to make it possible to detach objects from an event ... but I also understand the impact of it. Then, I thought up this proposed solution.
I hope it will make everybody happy, or at least, most everyone ...
Eric
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Reflection;
namespace WpfUtil.Collections
{
public static class ObservableCollectionExtension
{
public static void RemoveAllOneByOne<T>(this ObservableCollection<T> obsColl)
{
foreach (T item in obsColl)
{
while (obsColl.Count > 0)
{
obsColl.RemoveAt(0);
}
}
}
public static void RemoveAll<T>(this ObservableCollection<T> obsColl)
{
if (obsColl.Count > 0)
{
List<T> removedItems = new List<T>(obsColl);
obsColl.Clear();
NotifyCollectionChangedEventArgs e =
new NotifyCollectionChangedEventArgs
(
NotifyCollectionChangedAction.Remove,
removedItems
);
var eventInfo =
obsColl.GetType().GetField
(
"CollectionChanged",
BindingFlags.Instance | BindingFlags.NonPublic
);
if (eventInfo != null)
{
var eventMember = eventInfo.GetValue(obsColl);
// note: if eventMember is null
// nobody registered to the event, you can't call it.
if (eventMember != null)
eventMember.GetType().GetMethod("Invoke").
Invoke(eventMember, new object[] { obsColl, e });
}
}
}
}
}
To keep it simple why don't you override the ClearItem method and do whatever you want there ie Detach the items from the event.
public class PeopleAttributeList : ObservableCollection<PeopleAttributeDto>, {
{
protected override void ClearItems()
{
Do what ever you want
base.ClearItems();
}
rest of the code omitted
}
Simple, clean, and contain within the collection code.
I had the same issue, and this was my solution. It seems to work. Does anyone see any potential problems with this approach?
// overriden so that we can call GetInvocationList
public override event NotifyCollectionChangedEventHandler CollectionChanged;
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
NotifyCollectionChangedEventHandler collectionChanged = CollectionChanged;
if (collectionChanged != null)
{
lock (collectionChanged)
{
foreach (NotifyCollectionChangedEventHandler handler in collectionChanged.GetInvocationList())
{
try
{
handler(this, e);
}
catch (NotSupportedException ex)
{
// this will occur if this collection is used as an ItemsControl.ItemsSource
if (ex.Message == "Range actions are not supported.")
{
handler(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
else
{
throw ex;
}
}
}
}
}
}
Here are some other useful methods in my class:
public void SetItems(IEnumerable<T> newItems)
{
Items.Clear();
foreach (T newItem in newItems)
{
Items.Add(newItem);
}
NotifyCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
public void AddRange(IEnumerable<T> newItems)
{
int index = Count;
foreach (T item in newItems)
{
Items.Add(item);
}
NotifyCollectionChangedEventArgs e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new List<T>(newItems), index);
NotifyCollectionChanged(e);
}
public void RemoveRange(int startingIndex, int count)
{
IList<T> oldItems = new List<T>();
for (int i = 0; i < count; i++)
{
oldItems.Add(Items[startingIndex]);
Items.RemoveAt(startingIndex);
}
NotifyCollectionChangedEventArgs e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new List<T>(oldItems), startingIndex);
NotifyCollectionChanged(e);
}
// this needs to be overridden to avoid raising a NotifyCollectionChangedEvent with NotifyCollectionChangedAction.Reset, which our other lists don't support
new public void Clear()
{
RemoveRange(0, Count);
}
public void RemoveWhere(Func<T, bool> criterion)
{
List<T> removedItems = null;
int startingIndex = default(int);
int contiguousCount = default(int);
for (int i = 0; i < Count; i++)
{
T item = Items[i];
if (criterion(item))
{
if (removedItems == null)
{
removedItems = new List<T>();
startingIndex = i;
contiguousCount = 0;
}
Items.RemoveAt(i);
removedItems.Add(item);
contiguousCount++;
}
else if (removedItems != null)
{
NotifyCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removedItems, startingIndex));
removedItems = null;
i = startingIndex;
}
}
if (removedItems != null)
{
NotifyCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removedItems, startingIndex));
}
}
private void NotifyCollectionChanged(NotifyCollectionChangedEventArgs e)
{
OnPropertyChanged(new PropertyChangedEventArgs("Count"));
OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
OnCollectionChanged(e);
}
I found another "simple" solution deriving from ObservableCollection, but it is not very elegant because it uses Reflection... If you like it here is my solution:
public class ObservableCollectionClearable<T> : ObservableCollection<T>
{
private T[] ClearingItems = null;
protected override void OnCollectionChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case System.Collections.Specialized.NotifyCollectionChangedAction.Reset:
if (this.ClearingItems != null)
{
ReplaceOldItems(e, this.ClearingItems);
this.ClearingItems = null;
}
break;
}
base.OnCollectionChanged(e);
}
protected override void ClearItems()
{
this.ClearingItems = this.ToArray();
base.ClearItems();
}
private static void ReplaceOldItems(System.Collections.Specialized.NotifyCollectionChangedEventArgs e, T[] olditems)
{
Type t = e.GetType();
System.Reflection.FieldInfo foldItems = t.GetField("_oldItems", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
if (foldItems != null)
{
foldItems.SetValue(e, olditems);
}
}
}
Here I save the current elements in an array field in the ClearItems method, then I intercept the call of OnCollectionChanged and overwrite the e._oldItems private field (through Reflections) before launching base.OnCollectionChanged
You can override ClearItems method and raise event with Remove action and OldItems .
public class ObservableCollection<T> : System.Collections.ObjectModel.ObservableCollection<T>
{
protected override void ClearItems()
{
CheckReentrancy();
var items = Items.ToList();
base.ClearItems();
OnPropertyChanged(new PropertyChangedEventArgs("Count"));
OnPropertyChanged(new PropertyChangedEventArgs("Item[]"));
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, items, -1));
}
}
Part of System.Collections.ObjectModel.ObservableCollection<T> realization:
public class ObservableCollection<T> : Collection<T>, INotifyCollectionChanged, INotifyPropertyChanged
{
protected override void ClearItems()
{
CheckReentrancy();
base.ClearItems();
OnPropertyChanged(CountString);
OnPropertyChanged(IndexerName);
OnCollectionReset();
}
private void OnPropertyChanged(string propertyName)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
private void OnCollectionReset()
{
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
private const string CountString = "Count";
private const string IndexerName = "Item[]";
}
http://msdn.microsoft.com/en-us/library/system.collections.specialized.notifycollectionchangedaction(VS.95).aspx
Please read this documentation with your eyes open and your brain turned on.
Microsoft did everything right. You must re-scan your collection when it throws a Reset notification for you. You get a Reset notification because throwing Add/Remove for each item (being removed from and added back to collection) is too expensive.
Orion Edwards is completely right (respect, man). Please think wider when reading the documentation.
If your ObservableCollection is not getting clear, then you may try this below code. it may help you:
private TestEntities context; // This is your context
context.Refresh(System.Data.Objects.RefreshMode.StoreWins, context.UserTables); // to refresh the object context

Resources