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!");
}
Related
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;
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 have a form, I select some checkboxes, edit some text field, select from a combobox etc. then I click Exit. Based on the fact that "Data has been changed ??" I wish to perform actions. The problem is I can't get the event work :
private void DataChanged(object sender, EventArgs e)
{
MessageBox.Show("Data is changed", "debug");
isDataSaved = false;
}
When is this method called, how do I make it work? Is this supposed to get fired when the form's fields have some data i.e filL a text box ?
I dont really get the API: DataChanged event
Note: I'm following Mike Murach C# 5th edition chapter 10 example.
Edit (exact words from book):
Generate an event handler named DataChanged for the
SelectedIndexChanged event of the XXXX Name combo box. Then , wire
this event handler to the TextChanged event of the YYYYY Method label
and add the code to this event handler so it sets the isDataSaved
variable to false
When I double click on the commbo box the generated event handler it is not named DataChanged but cboNames_SelectedIndexChanged... (is this a book screw up or me total noob ? PS: There is no .. 'database' in the project)
Personally I mostly use databinding these days to get notified of changes in data.
A data holder class, which implements INotifyPropertyChanged. This interface gives you the possibility to get notified when the value of a property changes.
public class SomeData: INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
private void SetProperty<T>(ref T field, T value, [CallerMemberName] string name = "") {
if (!EqualityComparer<T>.Default.Equals(field, value)) {
field = value;
var handler = PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(name));
}
}
}
private boolean _someBoolean;
public int SomeBoolean {
get { return _someBoolean; }
set {
SetProperty(ref _someBoolean, value);
}
}
private string _someString;
public string SomeString {
get { return _someString; }
set {
SetProperty(ref _someString, value);
}
}
// etc
}
Now our form, which uses the data class and it's INotifyPropertyChanged implementation to get notified when a change in data occurs.
public partial class SomeForm: Form {
private SomeData _data;
private void LoadData() {
_data = new SomeData();
_data.PropertyChanged += Data_PropertyChanged;
}
private void SaveData() {
// TODO: Save data
}
private void AddDataBindings() {
checkbox1.DataBindings.Add("Checked", _data, "SomeBoolean");
textbox1.DataBindings.Add("Text", _data, "SomeString");
// add other
}
private void Data_PropertyChanged(object sender, PropertyChangedEventArgs e) {
// Here you can add actions that must be triggered when some data changes.
if (e.PropertyName == "SomeBoolean") {
// Do something when some-boolean property changes
}
// Set is-changed-boolean to true to 'remember' that something has changed.
_isChanged = true;
// Give message
MessageBox.Show(string.Format("Data changed, property {0}", e.PropertyName));
}
private bool _isChanged = false;
protected void Form_Closed(object sender, EventArgs e) {
// If data is changed, save it
if (_isChanged) {
SaveData();
}
}
}
Your problem is not known where the method DataChanged use and how. I have a suggestion for you that is use Focus Activated in properties.Add datachanged printing method Activated
good luck.
You must make properties this like
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.