I am modifying the standard WPF Grid.
On it I have several ObservableItemCollections that are dependency properties.
These dependency properties have Property Changed Callbacks that set instance event handlers to the CollectionChanged and ItemChanged events.
The Observable Collections work internally to the control and are not linked to the ViewModel.
The Grid doesn't appear to have a dispose method and the deconstructor isn't being called.
How do I release the events as I'm getting a memory leak?
Many thanks in advance.
Example Code:
public class ChDynamicGrid : Grid
{
#region Dependency Properties
public ObservableItemCollection<CHColumnDefinition> CHColumnDefinitions
{
get { return (ObservableItemCollection<CHColumnDefinition>)GetValue(CHColumnDefinitionsProperty); }
set { SetValue(CHColumnDefinitionsProperty, value); }
}
public static readonly DependencyProperty CHColumnDefinitionsProperty =
DependencyProperty.Register(
"CHColumnDefinitions",
typeof(ObservableItemCollection<CHColumnDefinition>),
typeof(ChDynamicGrid),
new PropertyMetadata(null, new PropertyChangedCallback(OnColumnDefinitionsChanged))
);
#endregion //Dependency Properties
public ChDynamicGrid() : base()
{
ShowGridLines = true;
CHColumnDefinitions = new ObservableItemCollection<CHColumnDefinition>();
}
~ChDynamicGrid()
{
this.CHColumnDefinitions.ItemChanged -= CHColumnDefinitions_ItemChanged;
this.CHColumnDefinitions.CollectionChanged -= CHColumnDefinitions_CollectionChanged;
}
private static void OnColumnDefinitionsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var g = (ChDynamicGrid)d;
if (g == null) return;
if (e.OldValue != null)
{
((ObservableItemCollection<CHColumnDefinition>)e.OldValue).ItemChanged -= g.CHColumnDefinitions_ItemChanged;
((ObservableItemCollection<CHColumnDefinition>)e.OldValue).CollectionChanged -= g.CHColumnDefinitions_CollectionChanged;
}
if (e.NewValue != null)
{
((ObservableItemCollection<CHColumnDefinition>)e.NewValue).ItemChanged += g.CHColumnDefinitions_ItemChanged;
((ObservableItemCollection<CHColumnDefinition>)e.NewValue).CollectionChanged += g.CHColumnDefinitions_CollectionChanged;
}
}
private void CHColumnDefinitions_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e){}
private void CHColumnDefinitions_ItemChanged(object sender, ItemChangedEventArgs<CHColumnDefinition> e){}
}
Related
I may have painted myself into a corner. I have a custom touch keyboard user control that uses static events to pass key presses to text boxes when they are in focus. I'm trying to make a dependency property for all text boxes to subscribe to my touch keyboard event handlers when they are in focus and unsubscribe when they lose focus.
My current problem is that the PropertyChangedCallback, IsTouchKeyboardTargetChanged never fires. I've read that this is because it is set in XAML and thus never "changes."
So far none of the other answers I have found quite apply to my pattern here. I'm not even sure that this is the right way to go about this, but it would be very convenient for me if this works. I'm following the example set my MahApps with their TextBoxHelper.
If anyone can help me resolve the PropertyChangedCallback issue, or if anyone has a better idea of how to accomplish subscribing/unsubscribing to static events when any and all text boxes fire focus events, I would be most grateful.
CS
public class TouchTextBoxHelper : DependencyObject
{
public static readonly DependencyProperty IsTouchKeyboardTargetProperty = DependencyProperty.Register("IsTouchKeyboardTarget", typeof(bool), typeof(TouchTextBoxHelper), new FrameworkPropertyMetadata(false, IsTouchKeyboardTargetChanged));
[AttachedPropertyBrowsableForType(typeof(TextBox))]
public static bool GetIsTouchKeyboardTargetEnabled(DependencyObject obj)
{
return (bool)obj.GetValue(IsTouchKeyboardTargetProperty);
}
[AttachedPropertyBrowsableForType(typeof(TextBox))]
public static void SetIsTouchKeyboardTargetEnabled(DependencyObject obj, bool value)
{
obj.SetValue(IsTouchKeyboardTargetProperty, value);
}
private static void IsTouchKeyboardTargetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var tb = d as TextBox;
if (null == tb)
{
throw new InvalidOperationException("The property 'IsTouchKeyboardTarget' may only be set on TextBox elements.");
}
if (e.OldValue != e.NewValue)
{
//tb.SetValue(SpellCheck.IsEnabledProperty, (bool)e.NewValue);
if ((bool)e.NewValue)
{
tb.GotFocus += TextBoxBaseGotFocus;
tb.LostFocus += TextBoxBaseLostFocus;
}
else
{
tb.GotFocus -= TextBoxBaseGotFocus;
tb.LostFocus -= TextBoxBaseLostFocus;
}
}
}
private static void TextBoxBaseGotFocus(object sender, RoutedEventArgs e)
{
TouchTextBoxEvents.tb = sender as TextBox;
StaticEvents.OnShowTouchKeyboard(sender, e);
StaticEvents.TouchKeyboardKeyTouch += TouchTextBoxEvents.StaticEvents_TouchKeyboardKeyTouch;
StaticEvents.TouchKeyboardSpaceTouch += TouchTextBoxEvents.StaticEvents_TouchKeyboardSpaceTouch;
StaticEvents.TouchKeyboardBackspaceTouch += TouchTextBoxEvents.StaticEvents_TouchKeyboardBackspaceTouch;
}
private static void TextBoxBaseLostFocus(object sender, RoutedEventArgs e)
{
StaticEvents.OnHideTouchKeyboard(sender, e);
StaticEvents.TouchKeyboardKeyTouch -= TouchTextBoxEvents.StaticEvents_TouchKeyboardKeyTouch;
StaticEvents.TouchKeyboardSpaceTouch -= TouchTextBoxEvents.StaticEvents_TouchKeyboardSpaceTouch;
StaticEvents.TouchKeyboardBackspaceTouch -= TouchTextBoxEvents.StaticEvents_TouchKeyboardBackspaceTouch;
}
}
public class TouchTextBoxEvents
{
public static TextBox tb = null;
public static void StaticEvents_TouchKeyboardKeyTouch(object sender, EventArgs e)
{
int i = tb.CaretIndex;
string t = (sender as Button).Content.ToString();
tb.Text = tb.Text.Insert(tb.CaretIndex, t);
tb.CaretIndex = i + 1;
}
public static void StaticEvents_TouchKeyboardBackspaceTouch(object sender, EventArgs e)
{
int i = tb.CaretIndex;
if (tb.CaretIndex == 0) return;
tb.Text = tb.Text.Remove(tb.CaretIndex - 1, 1);
tb.CaretIndex = i - 1;
}
public static void StaticEvents_TouchKeyboardSpaceTouch(object sender, EventArgs e)
{
int i = tb.CaretIndex;
tb.Text = tb.Text.Insert(tb.CaretIndex, " ");
tb.CaretIndex = i + 1;
}
}
Edit:
It turns out all I had to do was update my setter:
[AttachedPropertyBrowsableForType(typeof(TextBox))]
public static void SetIsTouchKeyboardTargetEnabled(DependencyObject obj, bool value)
{
obj.SetValue(IsTouchKeyboardTargetProperty, value);
TextBox tb = obj as TextBox;
if (tb == null) return;
if (value)
{
tb.GotFocus += TextBoxBaseGotFocus;
tb.LostFocus += TextBoxBaseLostFocus;
}
else
{
tb.GotFocus -= TextBoxBaseGotFocus;
tb.LostFocus -= TextBoxBaseLostFocus;
}
}
Edit2:
Putting the PropertyChangedCallback logic in the setter is a just bad. Fortunately, Clemens provided some great advice to use RegisterAttached instead of Register. This removed the need to derive from DependencyObject and cleared up the issue with the PropertyChangedCallback not firing.
An attached property must be registered by calling DependencyProperty.RegisterAttached instead of Register. There is also no need to derive the declaring class from DependencyObject:
public class TouchTextBoxHelper
{
public static readonly DependencyProperty IsTouchKeyboardTargetProperty =
DependencyProperty.RegisterAttached(
"IsTouchKeyboardTarget",
typeof(bool),
typeof(TouchTextBoxHelper),
new FrameworkPropertyMetadata(false, IsTouchKeyboardTargetChanged));
...
}
I have an attached property to textboxes in my view. The attached property performs validation on the textbox input and performs other chores. The attached property validation routine raises an event which is being watched by the viewmodel.
Does this "violate" MVVM reasoning by having the viewmodel obtain the invalid TextBoxes?
How will the GC deal with the static events from the attached property when the usercontrol containing the textboxes is removed?
If specific code is needed to avoid memory leaks, how is that done?
Is there a preferred way to do this?
Sorry for the long list, but Google does not address this situation.
Any and all help is appreciated. Thank you for your consideration.
(VS2010 .net 4.5)
TIA
ViewModel
class CheckInViewModel : SimpleViewModelBase
{
public CheckInViewModel()
{
InValidTextBoxes = new List<TextBox>();
Stargate_V.Helpers.ColorMaskingTextBoxBehavior.Validated += (sender, e) =>
{
if (e.valid)
InValidTextBoxes.Remove(e.sender);
else
InValidTextBoxes.Add(e.sender);
};
}
List<TextBox> InValidTextBoxes;
}
XAML
<TextBox
h:ColorMaskingTextBoxBehavior.Mask="^[MmFf]$"
Text="{Binding Sex}"
Height="24" HorizontalAlignment="Right" Margin="0,55,665,0" VerticalAlignment ="Top" Width="36" />
Attached Properity
public class ColorMaskingTextBoxBehavior : DependencyObject
{
// Entrance point from Xaml
public static readonly DependencyProperty MaskProperty = DependencyProperty.RegisterAttached("Mask",
typeof(string),
typeof(ColorMaskingTextBoxBehavior),
new FrameworkPropertyMetadata(OnMaskChanged));
...........................
// Callback from XAML initialization of the attached property.
private static void OnMaskChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
var textBox = dependencyObject as TextBox;
var mask = e.NewValue as string;
textBox.PreviewTextInput -= textBox_PreviewTextInput;
textBox.PreviewKeyDown -= textBox_PreviewKeyDown;
DataObject.RemovePastingHandler(textBox, Pasting);
DataObject.RemoveCopyingHandler(textBox, NoDragCopy);
CommandManager.RemovePreviewExecutedHandler(textBox, NoCutting);
if (mask == null)
{
textBox.ClearValue(MaskProperty);
textBox.ClearValue(MaskExpressionProperty);
}
else
{
textBox.SetValue(MaskProperty, mask);
SetMaskExpression(textBox, new Regex(mask, RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace));
textBox.PreviewTextInput += textBox_PreviewTextInput;
textBox.PreviewKeyDown += textBox_PreviewKeyDown;
DataObject.AddPastingHandler(textBox, Pasting);
DataObject.AddCopyingHandler(textBox, NoDragCopy);
CommandManager.AddPreviewExecutedHandler(textBox, NoCutting);
}
}
private static void textBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
var textBox = sender as TextBox;
var maskExpression = GetMaskExpression(textBox);
string passHex = (string)textBox.GetValue(PassColorProperty);
string failHex = (string)textBox.GetValue(FailColorProperty);
Color passColor = Extensions.ToColorFromHex(passHex);
Color failColor = Extensions.ToColorFromHex(failHex);
if (maskExpression == null)
{
return;
}
var proposedText = GetProposedText(textBox, e.Text);
if (!maskExpression.IsMatch(proposedText))
{
textBox.Background = new SolidColorBrush(failColor);
ValidationEventArgs args = new ValidationEventArgs();
args.sender = textBox;
args.valid = false;
OnValidation(args);
}
else
{
textBox.Background = new SolidColorBrush(passColor);
ValidationEventArgs args = new ValidationEventArgs();
args.sender = textBox;
args.valid = true;
OnValidation(args);
}
}
Event Called from the above code
public static event EventHandler<ValidationEventArgs> Validated;
static void OnValidation(ValidationEventArgs e)
{
EventHandler<ValidationEventArgs> handler = Validated;
if (handler != null)
{
handler(null, e);
}
}
public class ValidationEventArgs : EventArgs
{
public TextBox sender;
public bool valid;
}
Yes, I would argue that this violates MVVM. Your view model should have no knowledge of the views whatsoever. The question to always ask yourself is "can I run my application without creating any views?". In this case your view model is interacting directly with a list of TextBoxes, so the pattern is broken.
There are several ways of achieving your goal here, probably the most simple is to create a handler in your view model that gets called when your TextBox text changes:
public delegate void ValidationDelegate(bool isValid);
public class MyViewModel : ViewModelBase
{
public ValidationDelegate ValidationHandler { get { return (isValid) => OnValidate(isValid); } }
private void OnValidate(bool isValid)
{
// handle the validation event here
}
}
Now all you need is a behavior with an attached property that you can bind to this handler:
public class ValidateBehavior : Behavior<TextBox>
{
public ValidationDelegate Validated
{
get { return (ValidationDelegate)GetValue(ValidatedProperty); }
set { SetValue(ValidatedProperty, value); }
}
public static readonly DependencyProperty ValidatedProperty =
DependencyProperty.Register("Validated", typeof(ValidationDelegate), typeof(ValidateBehavior), new PropertyMetadata(null));
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.TextChanged += ValidateText;
}
protected override void OnDetaching()
{
base.OnDetaching();
this.AssociatedObject.TextChanged -= ValidateText;
}
private void ValidateText(object sender, TextChangedEventArgs e)
{
if (this.Validated != null)
{
bool isValid = true; // do text validation here
this.Validated(isValid);
}
}
}
And then finally add the behaviour to the TextBox in question and bind the handler:
<TextBox>
<i:Interaction.Behaviors>
<behaviors:ValidateBehavior Validated="{Binding ValidationHandler}"/>
</i:Interaction.Behaviors>
</TextBox>
EDIT: If you don't want to use a Blend behaviour then you can also do it with an attached behaviour:
public static class ValidateBehavior
{
public static ValidationDelegate GetValidate(TextBox textbox)
{
return (ValidationDelegate)textbox.GetValue(ValidateProperty);
}
public static void SetValidate(TextBox textbox, ValidationDelegate value)
{
textbox.SetValue(ValidateProperty, value);
}
public static readonly DependencyProperty ValidateProperty =
DependencyProperty.RegisterAttached(
"Validate",
typeof(ValidationDelegate),
typeof(ValidateBehavior),
new UIPropertyMetadata(null, OnValidateChanged));
static void OnValidateChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
var textbox = depObj as TextBox;
if (textbox == null)
return;
if (e.OldValue is ValidationDelegate)
textbox.TextChanged -= OnTextChanged;
if (e.NewValue is ValidationDelegate)
textbox.TextChanged += OnTextChanged;
}
static void OnTextChanged(object sender, RoutedEventArgs e)
{
if (!Object.ReferenceEquals(sender, e.OriginalSource))
return;
var textbox = e.OriginalSource as TextBox;
if (textbox != null)
{
var validate = GetValidate(textbox);
if (validate != null)
{
bool isValid = true; // do text validation here
validate(isValid);
}
}
}
}
And the corresponding XAML:
<TextBox behaviors:ValidateBehavior.Validate="{Binding ValidationHandler}" />
I am struggling with a dependencyproperty in a control. My dependencyproperty is an object which looks like this:
public class ChartGroupCollection : ObservableCollection<ChartGroup>, INotifyCollectionChanged
{
public void ClearDirty()
{
foreach (var grp in base.Items)
{
foreach(var run in grp.ChartRuns.Where(x=>x.IsDirty))
{
run.IsDirty = false;
}
grp.IsDirty = false;
}
}
[XmlIgnore]
public bool IsDirty //dirty flag for save prompt
{
get
{
....
}
}
}
[Serializable]
public class ChartGroup : INotifyPropertyChanged
{ ... //various properties }
The DependencyProperty is set up as here (named Tree, which is instance of a ChartGroupCollection):
public static readonly DependencyProperty TreeProperty = DependencyProperty.Register("Tree", typeof(ChartGroupCollection), typeof(ChartsControl), new PropertyMetadata(OnTreeChanged));
public ChartGroupCollection Tree
{
get { return (ChartGroupCollection)GetValue(TreeProperty); }
set { SetValue(TreeProperty, value); }
}
private static void OnTreeChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var Treee = sender as ChartsControl;
if (e.OldValue != null)
{
var coll = (INotifyCollectionChanged)e.OldValue;
coll.CollectionChanged -= Tree_CollectionChanged;
}
if (e.NewValue != null)
{
var coll = (ObservableCollection<ChartGroup>)e.NewValue;
coll.CollectionChanged += Tree_CollectionChanged;
}
}
private static void Tree_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
(sender as ChartsControl).OnTreeChanged();
}
void OnTreeChanged()
{
MessageBox.Show("Do something..."); //RefreshCharts();
}
I seem to be getting to the OnTreeChanged event only at creation of the object, but once i do other work (adding to lists inside ChartGroup or changing properties of ChartGroup objects or even deleting elements of the observablecollection it never seems to trigger a refresh event. I have tried several other methods of getting the dependencyproperty found online but no solution worked for me. I wonder if it is down to the intrinsic nature of my dependencyproperty object or an error from my side
The sender argument in your Tree_CollectionChanged handler is not the ChartsControl instance, but the collection that raised the CollectionChanged event.
The Tree_CollectionChanged method should not be static
private void Tree_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
OnTreeChanged();
}
and it should be attached and removed like this:
private static void OnTreeChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var control = sender as ChartsControl;
var oldCollection = e.OldValue as INotifyCollectionChanged;
var newCollection = e.NewValue as INotifyCollectionChanged;
if (oldCollection != null)
{
oldCollection.CollectionChanged -= control.Tree_CollectionChanged;
}
if (newCollection != null)
{
newCollection.CollectionChanged += control.Tree_CollectionChanged;
}
}
Note also that adding a CollectionChanged handler does not care for subscribing to PropertyChanged events of the collection elements. You would also have to attach or remove a PropertyChanged handler whenever an element is added to or removed from the collection. Take a look at the NotifyCollectionChangedEventArgs.Action property.
I have a custom control that has a DependencyProperty of type ObservableCollection that is bound to an observableCollection:
<MyControl MyCollectionProperty = {Binding MyObservableCollection} ...
Problem is adding to MyObservableCollection does not update MyCollectionProperty.
I need to completly replace the MyObservableCollection to make it work e.g.
MyObservableCollection = null;
MyObservableCollection = new ObservableCollection(){...}
Is there a better way to deal with this?
EDIT:
public ObservableCollection<string> Columns
{
get { return (ObservableCollection<string>)GetValue(ColumnsProperty); }
set { SetValue(ColumnsProperty, value); }
}
public static readonly DependencyProperty ColumnsProperty =
DependencyProperty.Register("Columns", typeof(ObservableCollection<string>), typeof(MyControl),
new PropertyMetadata(new ObservableCollection<string>(), OnChanged));
In addition to what grantz has answered, I would suggest to declare the property with type IEnumerable<string> and check at runtime if the collection object implements the INotifyCollectionChanged interface. This provides greater flexibility as to which concrete collection implementation may be used as property value. A user may then decide to have their own specialized implementation of an observable collection.
Note also that in the ColumnsPropertyChanged callback the CollectionChanged event handler is attached to the new collection, but also removed from the old one.
public static readonly DependencyProperty ColumnsProperty =
DependencyProperty.Register(
"Columns", typeof(IEnumerable<string>), typeof(MyControl),
new PropertyMetadata(null, ColumnsPropertyChanged));
public IEnumerable<string> Columns
{
get { return (IEnumerable<string>)GetValue(ColumnsProperty); }
set { SetValue(ColumnsProperty, value); }
}
private static void ColumnsPropertyChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var control= (MyControl)obj;
var oldCollection = e.OldValue as INotifyCollectionChanged;
var newCollection = e.NewValue as INotifyCollectionChanged;
if (oldCollection != null)
{
oldCollection.CollectionChanged -= control.ColumnsCollectionChanged;
}
if (newCollection != null)
{
newCollection.CollectionChanged += control.ColumnsCollectionChanged;
}
control.UpdateColumns();
}
private void ColumnsCollectionChanged(
object sender, NotifyCollectionChangedEventArgs e)
{
// optionally take e.Action into account
UpdateColumns();
}
private void UpdateColumns()
{
...
}
Below is a working example that may help.
In this example, the method OnChanged is called immediately, when the Add button is clicked "Changed" is written to the console.
The Control
public class MyControl : Control
{
public ObservableCollection<string> ExtraColumns
{
get { return (ObservableCollection<string>)GetValue(ExtraColumnsProperty); }
set { SetValue(ExtraColumnsProperty, value); }
}
public static readonly DependencyProperty ExtraColumnsProperty =
DependencyProperty.Register("ExtraColumns", typeof(ObservableCollection<string>), typeof(MyControl),
new PropertyMetadata(new ObservableCollection<string>(), OnChanged));
static void OnChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
(sender as MyControl).OnChanged();
}
void OnChanged()
{
if ( ExtraColumns != null )
ExtraColumns.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(ExtraColumns_CollectionChanged);
}
void ExtraColumns_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
Console.WriteLine("Changed");
}
}
The Window
<Window x:Class="WpfApplication18.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication18"
Title="MainWindow" Height="350" Width="525">
<StackPanel>
<local:MyControl ExtraColumns="{Binding Extras}"/>
<Button Click="Button_Click">Add</Button>
</StackPanel>
</Window>
Window Code Behind
public partial class MainWindow : Window
{
private ObservableCollection<string> _extras = new ObservableCollection<string>( );
public ObservableCollection<string> Extras
{
get { return _extras; }
set
{
if (value != _extras)
{
_extras = value;
}
}
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
Extras.Add("Additional");
}
}
I created behavior for DataGrid to detect double-click:
public class DataGridDoubleClickBehavior : Behavior<DataGrid>
{
public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register(
"CommandParameter",
typeof(object),
typeof(DataGridDoubleClickBehavior),
new PropertyMetadata(null));
public object CommandParameter
{
get { return GetValue(CommandParameterProperty); }
set { SetValue(CommandParameterProperty, value); }
}
public static readonly DependencyProperty DoubleClickCommandProperty = DependencyProperty.Register(
"DoubleClickCommand",
typeof(ICommand),
typeof(DataGridDoubleClickBehavior),
new PropertyMetadata(null));
public ICommand DoubleClickCommand
{
get { return (ICommand)GetValue(DoubleClickCommandProperty); }
set { SetValue(DoubleClickCommandProperty, value); }
}
protected override void OnAttached()
{
this.AssociatedObject.LoadingRow += this.OnLoadingRow;
this.AssociatedObject.UnloadingRow += this.OnUnloadingRow;
base.OnAttached();
}
protected override void OnDetaching()
{
this.AssociatedObject.LoadingRow -= this.OnLoadingRow;
this.AssociatedObject.UnloadingRow -= this.OnUnloadingRow;
base.OnDetaching();
}
private void OnLoadingRow(object sender, DataGridRowEventArgs e)
{
e.Row.MouseLeftButtonUp += this.OnMouseLeftButtonUp;
}
private void OnUnloadingRow(object sender, DataGridRowEventArgs e)
{
e.Row.MouseLeftButtonUp -= this.OnMouseLeftButtonUp;
}
private void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
if (e.ClickCount < 2) return;
if (this.DoubleClickCommand != null) this.DoubleClickCommand.Execute(this.CommandParameter);
}
}
Everything seems to be fine except that it doesn't register multiple clicks. In OnMouseLeftButtonUp ClickCount always 1. Does anybody know why?
I found a very simple solution. Just replace the event handler registration syntax
myDataGrid.MouseLeftButtonDown += this.MyDataGrid_MouseLeftButtonDown;
with the AddHandler syntax
myDataGrid.AddHandler(DataGrid.MouseLeftButtonDownEvent,
new MouseButtonEventHandler(this.MyDataGrid_MouseLeftButtonDown),
handledEventsToo: true)
That way the magic boolean handledEventsToo argument can be specified.
This will, well, handle handled events too.
Well, the bigger trouble here is that when you click on the rows of a DataGrid, MouseLeftButtonDown is not raised, because this click is being handled at the row level.
I've long given up on dealing with some controls directly. I've my own derived version of the DataGrid, DataForm and etc. This only makes my solution easy to roll out, because I don't use the vanilla versions anyway.
I added a new event called ClickIncludingHandled, it's a bit wordy but it properly describes what's going on and will nicely appear right below Click with IntelliSense - if the control has a Click event to begin with.
Anyway, below is my implementation of it. You can then subscribe to this event and use ClickCount to determine the number of clicks you want to capture. I noticed it is a bit slow, but it works cleanly.
public partial class DataGridBase : DataGrid
{
public event MouseButtonEventHandler ClickIncludingHandled;
public DataGridBase() : base()
{
this.AddHandler(MouseLeftButtonDownEvent, new MouseButtonEventHandler(OnClickInclHandled), true);
}
private void OnClickInclHandled(object sender, MouseButtonEventArgs e)
{
if (ClickIncludingHandled != null)
{
ClickIncludingHandled(sender, e);
}
}
}
MouseButtonUp is broken with respect to capturing ClickCount in WPF and Silverlight (it's been recorded many times but Microsoft has opted not to fix it). You need to use MouseButtonDown events.
Not 100% sure this is the issue, but I notice that Pete Brown's sample uses MouseLeftButtonDown instead:
http://10rem.net/blog/2011/04/13/silverlight-5-supporting-double-and-even-triple-click-for-the-mouse
I ran into this exact same problem. I couldn't manage to resolve it in any sensible way, so worked around it like this:
private DateTime _lastClickTime;
private WeakReference _lastSender;
private void Row_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
var now = DateTime.Now;
if ((now - _lastClickTime).TotalMilliseconds < 200 && _lastSender != null && _lastSender.IsAlive && _lastSender.Target == sender)
{
if (Command != null)
{
Command.Execute(CommandParameter);
}
}
_lastClickTime = now;
_lastSender = new WeakReference(sender);
}
It's dirty but it works.