I'am trying to create a dependency property that allows me to two way bind a datagrid it's selected items to an property in my viewmodel.
So far i got it to work one way, when the selected item is changed in the datagrid my collection is updated, but thats where i ran into a snag
public static class MultiSelectorHelper
{
public static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.RegisterAttached(
"SelectedItems",
typeof(IList),
typeof(MultiSelectorHelper),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnSelectedItemsChanged));
public static IList GetSelectedItems(DependencyObject element)
{
return (IList)element.GetValue(SelectedItemsProperty);
}
public static void SetSelectedItems(DependencyObject element, IList value)
{
element.SetValue(SelectedItemsProperty, value);
}
private static void OnSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var selector = (Selector)d;
var newList = e.NewValue as IList;
var obs = newList as INotifyCollectionChanged;
obs.CollectionChanged += OnCollectionChanged;
selector.SelectionChanged += OnSelectionChanged;
}
private static void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var dependency = (DependencyObject)sender;
var items = GetSelectedItems(dependency);
// items.Add/items.Remove
}
private static void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// Update current datagrid selected items .add/.remove
}
When the collection changes it throws me into the OnCollectionChanged method, but from there i got no way to update my datagrid.
I have tried a couple of things, the first thing i tried was to define the event directly in the OnSelectedItemsChanged by means of obs.CollectionChanged += (s, e2) => {all the code} which altho works due to the access of d it resulted in triggering when the datagrid made a change to the collection with no way to disable it.
Another method i tried was to add an extra parameter to my OnCollectionChanged by means of obs.CollectionChanged += (s, e2) => OnCollectionChanged(s, e2, d); But this also did not get me any further and basicly resulted in the same issues as the one above.
The last thing i tried/could find/could come up with was to use a PropertyChanged event in my OnCollectionChanged and fire it off for each item that had changed, this in the hopes to be able to trigger the UI to send out an event i could hook into and update from there (since it would contain a dependency object of the datagrid). This never worked, no events never triggered anything leaving me at a dead trail.
The question is as follows, How would i be able to set the selected items of a datagrid (or any MultiSelector/Listbox) when a change is made from for example a viewModel, it's not a simple 1 on 1 binding so it does require having an instance of the controller to change it.
For those wondering, the idea is that it doesnt work for just a datagrid, but an object that is a ListBox or of type MultiSelector, for that reason i'm not just extending but creating a completely separate dependency property.
You could use a local function that captures the selector:
private static void OnSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var selector = (Selector)d;
var obs = e.NewValue as INotifyCollectionChanged;
if (obs != null)
{
void CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
var dataGrid = selector;
//...
}
obs.CollectionChanged += CollectionChanged;
}
}
The local function feature was introduced in C# 7.0. In earlier versions, you could use an anonymous method:
NotifyCollectionChangedEventHandler handler = (sender, ee) =>
{
var dataGrid = selector;
//..
};
obs.CollectionChanged += handler;
Related
How to Override PropertyChangedCallback of a predefined Dependency Property ItemsSource in a WPF ItemsControl.
I developed a WPF Custom Control inherited from ItemsControl. In that I used the predefined Dependency Property ItemsSource. In that I need to monitor and check data once the Collection gets updated.
I searched a lot in google, but I can't able to find any related solution to fulfill my requirement.
https://msdn.microsoft.com/en-us/library/system.windows.controls.itemscontrol.itemssource(v=vs.110).aspx
Kindly assist me, whats the method name to Override ?...
Call OverrideMetadata in a static constructor of your derived ItemsSource class:
public class MyItemsControl : ItemsControl
{
static MyItemsControl()
{
ItemsSourceProperty.OverrideMetadata(
typeof(MyItemsControl),
new FrameworkPropertyMetadata(OnItemsSourcePropertyChanged));
}
private static void OnItemsSourcePropertyChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
((MyItemsControl)obj).OnItemsSourcePropertyChanged(e);
}
private void OnItemsSourcePropertyChanged(DependencyPropertyChangedEventArgs e)
{
var oldCollectionChanged = e.OldValue as INotifyCollectionChanged;
var newCollectionChanged = e.NewValue as INotifyCollectionChanged;
if (oldCollectionChanged != null)
{
oldCollectionChanged.CollectionChanged -= OnItemsSourceCollectionChanged;
}
if (newCollectionChanged != null)
{
newCollectionChanged.CollectionChanged += OnItemsSourceCollectionChanged;
// in addition to adding a CollectionChanged handler
// any already existing collection elements should be processed here
}
}
private void OnItemsSourceCollectionChanged(
object sender, NotifyCollectionChangedEventArgs e)
{
// handle collection changes here
}
}
I am adding a Custom DataGridTextColumn that will allow me to fire an event when when ever the content is changed in this cell.
Please note:
I do not want to use a DataGridTemplateColumn with this as I know that. I want to create my own text column as there are a lot of features that come with text column that we use.
So I decided to just simply add an event to a custom control - simple enough. not so much. well it seams that there isn't an AddHandler or RemoveHandler methods.
Please explain where I am going wrong.
Code:
public static readonly RoutedEvent TextChangedEvent =
EventManager.RegisterRoutedEvent("TextChanged", RoutingStrategy.Bubble,
typeof (RoutedEventHandler),
typeof (DataGridTextChangedEventColumn));
public event RoutedEventHandler TextChanged
{
add { AddHandler(TextChangedEvent, value); }
remove { RemoveHandler(TextChangedEvent, value); }
}
private void AddHandler(RoutedEvent textChangedEvent, RoutedEventHandler value)
{
this.TextChanged += (s, e) => textChangedEvent;
}
Thank you.
If you want to create "Your" customized DatagridTextColumn, you could create a CustomControl that inherits from DataGridTextColumn.
Doing this, you can override the method "GenerateEditingElement" that returns the control that is associated with the editing look of the grid (generally it is a TextBox).
While you are overriding this method, you can attach an event handler to Your TextChanged event.
public class YourCustomDataGridTextColumn : DataGridTextColumn
{
public delegate void ColumnTextChangedHandler(object sender,TextChangedEventArgs e);
public event ColumnTextChangedHandler ColumnTextChanged;
#region "Methods"
protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem)
{
var textBox = (TextBox)base.GenerateEditingElement(cell, dataItem);
textBox.TextChanged += OnTextChanged;
return textBox;
}
private void OnTextChanged(object sender, TextChangedEventArgs e)
{
//Your event handling
if (ColumnTextChanged != null) {
ColumnTextChanged(sender, e);
}
}
#endregion
}
In a previous post I asked how to register a property as DependencyProperty. I got an answer and it works fine.
But now I want to add some Items to this DependencyProperty on a Click. This doesn't work. My code to register the DependencyProperty is:
public static readonly DependencyProperty ChartEntriesProperty = DependencyProperty.Register(
"ChartEntries", typeof(ObservableCollection<ChartEntry>), typeof(ChartView),
new FrameworkPropertyMetadata(OnChartEntriesChanged));
private static void OnChartEntriesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
The OnChartEntriesChanged-Event is called at the moment I do the Binding from my XAML to my c#-code. But if I add a ChartEntry afterwards (on button-click) the event is not fired.
Does anyone know why?
When you add an item to the ChartEntries collection, you do not actually change that property, so the PropertyChangedCallback isn't called. In order to get notified about changes in the collection, you need to register an additional CollectionChanged event handler:
private static void OnChartEntriesChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var chartView = (ChartView)obj;
var oldCollection = e.OldValue as INotifyCollectionChanged;
var newCollection = e.NewValue as INotifyCollectionChanged;
if (oldCollection != null)
{
oldCollection.CollectionChanged -= chartView.OnChartEntriesCollectionChanged;
}
if (newCollection != null)
{
newCollection.CollectionChanged += chartView.OnChartEntriesCollectionChanged;
}
}
private void OnChartEntriesCollectionChanged(
object sender, NotifyCollectionChangedEventArgs e)
{
...
}
It would also make sense not to use ObservableCollection<ChartEntry> for the property type, but simply ICollection or IEnumerable instead. This would allow for other implementations of INotifyCollectionChanged in the concrete collection type. See here and here for more information.
OnChartEntriesChanged callback will be called when you will set the new instance of the ObservableCollection. You will have to listen to collection changed as below:
private static void OnChartEntriesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((ObservableCollection<ChartView>)e.OldValue).CollectionChanged -= new System.Collections.Specialized.NotifyCollectionChangedEventHandler(ChartView_CollectionChanged);
((ObservableCollection<ChartView>)e.NewValue).CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(ChartView_CollectionChanged);
}
static void ChartView_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
}
Sorry but this won't work as you've detected yourself. The DependencyProperty changed handler does fire only, if the value of the property changes, but in your case it doesn't, as the object reference is still the same. You have to register on the CollectionChanged eventhandler of the provided collection. (this you can do in the propertychanged handler from the dependencyproperty)
The answer from Clemens looks good and helped me a lot. However, in many cases you will want your CollectionChanged event handler to get called also when the entire collection is replaced with another collection. To do this, simply call the CollectionChanged event handler explicitly and directly from the PropertyChanged event handler. The full code would look as follows.
private static void OnChartEntriesChanged(
DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var chartView = (ChartView)obj;
var oldCollection = e.OldValue as INotifyCollectionChanged;
var newCollection = e.NewValue as INotifyCollectionChanged;
if (oldCollection != null)
{
oldCollection.CollectionChanged -= chartView.OnChartEntriesCollectionChanged;
}
if (newCollection != null)
{
newCollection.CollectionChanged += chartView.OnChartEntriesCollectionChanged;
}
// The first parameter below can also be null
chartView.OnChartEntriesCollectionChanged(newCollection, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
private void OnChartEntriesCollectionChanged(
object sender, NotifyCollectionChangedEventArgs e)
{
...
}
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.
I have next dependency property:
public static DependencyProperty RequestObjectProperty = DependencyProperty.Register("RequestObject", typeof(RegistrationCardSearch), typeof(RegCardSearchForm),new UIPropertyMetadata(Changed));
private static void Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
MessageBox.Show("Property Changed!!!");
}
public RegistrationCardSearch RequestObject
{
get
{
return (RegistrationCardSearch)GetValue(RequestObjectProperty);
}
set
{
SetValue(RequestObjectProperty, value);
}
}
and "Changed" Method which have to fire when my dependency property changes. My property type is RegistrashionCardSearch (class) . When I change property values of class in dependency property, Property changed call back not fired. Why?? My RegistrashionCardSearch class implement INotifePropertyChanged interface
The changed event is fired only when the property itself changes, not when you change values inside this property. To given an example that will cause the changed event to fire:
var requestObject = myObject.RequestObject;
myObject.RequestObject = new RegistrationCardSearch() { ... };
A changed event will fire for the last line of this example because the property itself changes to another value.
However, when you do something like this:
myObject.RequestObject.SomeProperty = newPropertyValue;
the changed event will not fire because you haven't changed the RequestObject property itself, only some value inside the property.
Ronald already nicely explained why your approach doesn't work. To get it to work, you need to subscribe to the PropertyChanged event of your RequestObject:
private static void Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var form = (RegCardSearchForm)d;
if (e.OldValue != null)
((RegistrationCardSearch)e.OldValue).PropertyChanged -= form.RequestObject_PropertyChanged;
if (e.NewValue != null)
((RegistrationCardSearch)e.NewValue).PropertyChanged += form.RequestObject_PropertyChanged;
}
private void RequestObject_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
MessageBox.Show("Property " + e.PropertyName + " changed!");
}