I have seen some answers related to this issue but neither quite resembled this scenario I have. I have a settings class and when one of the properties is updated I want another class to be notified. The event is being fired but however its handler is always null.
Can somebody help me understand what is wrong?
class AppSettings : INotifyPropertyChanged
{
// Our settings
ApplicationDataContainer settings ;
// The key names of our settings
const string CheckBoxSettingKeyName = "CheckBoxSetting";
const string ComboBoxSettingKeyName = "ComboBoxSetting";
// The default value of our settings
const bool CheckBoxSettingDefault = true;
private int ComboBoxSettingDefault = 0;
public event PropertyChangedEventHandler PropertyChanged;
public AppSettings()
{
settings = ApplicationData.Current.LocalSettings;
}
public bool AddOrUpdateValue(string Key, Object value)
{
bool valueChanged = false;
// If the key exists
if (settings.Values.ContainsKey(Key))
{
// If the value has changed
if (settings.Values[Key] != value)
{
// Store the new value
settings.Values[Key] = value;
valueChanged = true;
}
}
// Otherwise create the key.
else
{
settings.Values.Add(Key, value);
valueChanged = true;
}
return valueChanged;
}
/// <summary>
/// Get the current value of the setting, or if it is not found, set the
/// setting to the default setting.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="Key"></param>
/// <param name="defaultValue"></param>
/// <returns></returns>
public T GetValueOrDefault<T>(string Key, T defaultValue)
{
T value;
// If the key exists, retrieve the value.
if (settings.Values.ContainsKey(Key))
{
value = (T)settings.Values[Key];
}
// Otherwise, use the default value.
else
{
value = defaultValue;
}
return value;
}
/// Property to get and set a ComboBox Setting Key.
public int ComboBoxSettings
{
get
{
return GetValueOrDefault<int>(ComboBoxSettingKeyName, ComboBoxSettingDefault);
}
set
{
AddOrUpdateValue(ComboBoxSettingKeyName, value);
NotifyPropertyChanged();
}
}
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
I removed some of your code and created a new console application. Then I changed to Program class to:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
using System.ComponentModel;
using System.Runtime.CompilerServices;
class Program
{
static void Main(string[] args)
{
// create instance
var settings = new AppSettings();
// subscribe for the event as soon as you can
settings.PropertyChanged += (s, e) => Console.WriteLine("Property {0} has changed", e.PropertyName);
Console.WriteLine("Press any key to start test");
Console.ReadKey();
// change the value
settings.ComboBoxSettings = 10;
Console.ReadKey();
}
}
class AppSettings : INotifyPropertyChanged
{
private int _comboBoxSettings;
public event PropertyChangedEventHandler PropertyChanged;
public int ComboBoxSettings
{
get
{
return _comboBoxSettings;
}
set
{
_comboBoxSettings = value;
NotifyPropertyChanged();
}
}
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
}
If you past this into a fresh console application it works.
Related
I have a CheckBoxComboBox where I want to get fields of checked items so I do something like:
foreach (var currentChecked in cboDesignStatus.CheckBoxItems.Where(x => x.Checked))
{
var a = currentChecked.ComboBoxItem;
}
So var a result is :
My question is, how can I access this fields ? I want to get property Name so I try
var a = currentChecked.ComboBoxItem.Name;
But I can't it only let this:
How can I access this field
Note: variable on foreach "currentChecked" throws ComboBoxItem as ObjectSelectionWrapper
ObjectSelectionWrapper Class:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Reflection;
using System.Data;
namespace MyProject.Utility
{
/// <summary>
/// Used together with the ListSelectionWrapper in order to wrap data sources for a CheckBoxComboBox.
/// It helps to ensure you don't add an extra "Selected" property to a class that don't really need or want that information.
/// </summary>
public class ObjectSelectionWrapper<T> : INotifyPropertyChanged
{
public ObjectSelectionWrapper(T item, ListSelectionWrapper<T> container)
: base()
{
_Container = container;
_Item = item;
}
#region PRIVATE PROPERTIES
/// <summary>
/// Used as a count indicator for the item. Not necessarily displayed.
/// </summary>
private int _Count = 0;
/// <summary>
/// Is this item selected.
/// </summary>
private bool _Selected = false;
/// <summary>
/// A reference to the wrapped item.
/// </summary>
private T _Item;
/// <summary>
/// The containing list for these selections.
/// </summary>
private ListSelectionWrapper<T> _Container;
#endregion
#region PUBLIC PROPERTIES
/// <summary>
/// An indicator of how many items with the specified status is available for the current filter level.
/// Thaught this would make the app a bit more user-friendly and help not to miss items in Statusses
/// that are not often used.
/// </summary>
public int Count
{
get { return _Count; }
set { _Count = value; }
}
/// <summary>
/// A reference to the item wrapped.
/// </summary>
public T Item
{
get { return _Item; }
set { _Item = value; }
}
/// <summary>
/// The item display value. If ShowCount is true, it displays the "Name [Count]".
/// </summary>
public string Name
{
get
{
string Name = null;
if (string.IsNullOrEmpty(_Container.DisplayNameProperty))
Name = Item.ToString();
else if (Item is DataRow) // A specific implementation for DataRow
Name = ((DataRow)((Object)Item))[_Container.DisplayNameProperty].ToString();
else
{
PropertyDescriptorCollection PDs = TypeDescriptor.GetProperties(Item);
foreach (PropertyDescriptor PD in PDs)
if (PD.Name.CompareTo(_Container.DisplayNameProperty) == 0)
{
Name = (string)PD.GetValue(Item).ToString();
break;
}
if (string.IsNullOrEmpty(Name))
{
PropertyInfo PI = Item.GetType().GetProperty(_Container.DisplayNameProperty);
if (PI == null)
throw new Exception(String.Format(
"Property {0} cannot be found on {1}.",
_Container.DisplayNameProperty,
Item.GetType()));
Name = PI.GetValue(Item, null).ToString();
}
}
return _Container.ShowCounts ? String.Format("{0} [{1}]", Name, Count) : Name;
}
}
/// <summary>
/// The textbox display value. The names concatenated.
/// </summary>
public string NameConcatenated
{
get { return _Container.SelectedNames; }
}
/// <summary>
/// Indicates whether the item is selected.
/// </summary>
public bool Selected
{
get { return _Selected; }
set
{
if (_Selected != value)
{
_Selected = value;
OnPropertyChanged("Selected");
OnPropertyChanged("NameConcatenated");
}
}
}
#endregion
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
}
It looks like ComboBoxItem is an Object. As Nat said earlier, if you cast the object to your expected class, you can then reference the properties.
var itemName = ((Utility.ObjectSelectionWrapper<DataRow>)currentChecked.ComboBoxItem).Name;
This question already has answers here:
WPF MVVM command canexecute enable/disable button
(4 answers)
Disable button in WPF?
(5 answers)
How does one "disable" a button in WPF using the MVVM pattern?
(5 answers)
Command source disabling and enabling
(1 answer)
Closed 5 years ago.
I have the command
public class RelayActionCommand : ICommand
{
/// <summary>
/// The Action Delegate representing a method with input parameter
/// </summary>
public Action<object> ExecuteAction { get; set; }
/// <summary>
/// The Delegate, used to represent the method which defines criteria for the execution
/// </summary>
public Predicate<object> CanExecuteAction { get; set; }
public bool CanExecute(object parameter)
{
if (CanExecuteAction != null)
{
return CanExecuteAction(parameter);
}
return true;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void Execute(object parameter)
{
if (ExecuteAction != null)
{
ExecuteAction(parameter);
}
}
}
To use it,
public RelayActionCommand SearchPersonCommnad { get; set; }
DataAccess objds;
public PersonViewModel()
{
Persons = new ObservableCollection<PersonInfo>();
objds = new DataAccess();
Persons = new ObservableCollection<PersonInfo>(objds.GetPersonData());
var defaultView = CollectionViewSource.GetDefaultView(Persons);
//based upon the data entered in the TextBox
SearchPersonCommnad = new RelayActionCommand()
{
CanExecuteAction = n=> !String.IsNullOrEmpty(Name),
ExecuteAction = n => defaultView.Filter = name => ((PersonInfo)name).FirstName.StartsWith(Name)
|| ((PersonInfo)name).LastName.StartsWith(Name)
|| ((PersonInfo)name).City==Name
};
At the beginning, the button is disabled. But in running time, it changes by different situations. My question is how to set up the button's IsEnabled property with it? Which means, when ExecuteAction I have to set up the property correctly.
UPDATE:
I use ICommand not DelegateCommand.
You can use the CanExecute method, but it is good practice is actually to avoid this, and bind the button's enabled state to a separate boolean property of the view model. Most other solutions will have unexpected effects, or be suboptimal. Why?
CanExecute is a method. This means that it needs to be polled for the button state to change. You can force the control that's using the command to re-poll on a status change, but the code is much cleaner and more straightforward if you just use a property on the view model. This is because as a method, you can't use INotifyPropertyChanged to notify for changes, whereas with a property you can.
The danger in using CanExecute is that the user will manage to click the button after the method would return false, but before the button's enablement has changed.
Edit: Code to do what you want:
public class ViewModel : INotifyPropertyChanged
{
private int someValue;
private bool isEnabled;
public ViewModel()
{
MyCommand = new RelayActionCommand(Click);
}
private void Click(object obj)
{
//Do something.
}
/// <summary>
/// Bind this to the IsEnabled property of the button, and
/// also the background using a convertor or see ButtonBackground.
/// </summary>
public bool IsEnabled => SomeValue < 5;
/// <summary>
/// Option 2 - use this property to bind to the background of the button.
/// </summary>
public Brush ButtonBackground => IsEnabled ? Brushes.SeaShell : Brushes.AntiqueWhite;
public int SomeValue
{
get { return someValue; }
set
{
if (value == someValue) return;
someValue = value;
OnPropertyChanged();
OnPropertyChanged(nameof(IsEnabled));
OnPropertyChanged(nameof(ButtonBackground));
}
}
/// <summary>
/// Bind this to the command of the button.
/// </summary>
public RelayActionCommand MyCommand { get; }
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged
([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Relay command fixed up a bit to avoid using CanExecute:
public class RelayActionCommand : ICommand
{
public RelayActionCommand(Action<object> executeAction)
{
ExecuteAction = executeAction;
}
/// <summary>
/// The Action Delegate representing a method with input parameter
/// </summary>
public Action<object> ExecuteAction { get; }
/// <summary>
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
ExecuteAction?.Invoke(parameter);
}
//Deliberately empty.
public event EventHandler CanExecuteChanged
{
add { }
remove { }
}
}
EDIT 2: Code to do what you want using a DelegateCommand
Note, this does not use InvalidateRequerySuggested - mainly because it refreshes all buttons when any CanExecute changes, which is a poor solution. As you can see, this is less immediately straightforward than putting the code in the view model directly, but whatever floats your boat I guess.
public sealed class ViewModel : INotifyPropertyChanged
{
private int calls;
public ViewModel()
{
SafeOnceCommand = new RelayCommand(DoItOnce, HasDoneIt);
}
private bool HasDoneIt()
{
return Calls == 0;
}
private void DoItOnce()
{
if (Calls > 0) throw new InvalidOperationException();
Calls++;
}
public int Calls
{
get { return calls; }
set
{
if (value == calls) return;
calls = value;
OnPropertyChanged();
SafeOnceCommand.RaiseCanExecuteChanged();
}
}
public RelayCommand SafeOnceCommand { get; }
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public sealed class RelayCommand : ICommand
{
private readonly Action execute;
private readonly Func<bool> canExecute;
private readonly List<EventHandler> invocationList = new List<EventHandler>();
public RelayCommand(Action execute, Func<bool> canExecute)
{
this.execute = execute;
this.canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return canExecute();
}
public void Execute(object parameter)
{
execute();
}
/// <summary>
/// Method to raise CanExecuteChanged event
/// </summary>
public void RaiseCanExecuteChanged()
{
foreach (var elem in invocationList)
{
elem(null, EventArgs.Empty);
}
}
public event EventHandler CanExecuteChanged
{
add { invocationList.Add(value); }
remove { invocationList.Remove(value); }
}
}
I have the following classes gist with the classes.
I want to bind Item.Visible to Items.ItemsVisible - is it possible?, if so - how?
Item.cs:
using System;
using System.ComponentModel;
namespace WpfApplication85
{
/// <summary>
/// Item Object.
/// </summary>
public class Item : INotifyPropertyChanged
{
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged; //Event to notify when Property changed.
/// <summary>
/// Notify that Property has Changed.
/// </summary>
/// <param name="propertyName">The name of the Property</param>
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
#region Private Variables
private bool _Visible; //Bool to determine if the Item is visible or not
#endregion
#region Public Properties
//Return the value of Visible / Set the value of Visible and Notify.
public bool Visible
{
get { return _Visible; }
set
{
_Visible = value;
NotifyPropertyChanged("Visible");
}
}
#endregion
#region Constructor
/// <summary>
/// Item Constructor
/// </summary>
public Item()
{
_Visible = true;
}
#endregion
}
}
Items.cs:
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace WpfApplication85
{
/// <summary>
/// Items Object.
/// </summary>
public class Items : INotifyPropertyChanged
{
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged; //Event to notify when Property changed.
/// <summary>
/// Notify that Property has Changed.
/// </summary>
/// <param name="propertyName">The name of the Property</param>
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
#region Private Variables
private bool _itemsVisible; //Bool to determine if the Items are visible or not
private ObservableCollection<Item> _itemsCollection; //Collection of Items.
#endregion
#region Public Properties
//Return the value of ItemsVisible / Set the value of ItemsVisible and Notify.
public bool ItemsVisible
{
get { return _itemsVisible; }
set
{
_itemsVisible = value;
NotifyPropertyChanged("ItemsVisible");
}
}
//Return the Items Collection / Set the Items Collection and Notify.
public ObservableCollection<Item> ItemsCollection
{
get
{
return _itemsCollection;
}
set
{
_itemsCollection = value;
NotifyPropertyChanged("ItemsCollection");
}
}
#endregion
#region Constructor
/// <summary>
/// Items Constructor
/// </summary>
public Items()
{
_itemsVisible = true;
_itemsCollection = new ObservableCollection<Item>();
}
#endregion
#region Methods
/// <summary>
/// Add Item to the ItemsCollection.
/// </summary>
/// <param name="item">Item Object</param>
public void AddItem(Item item)
{
//Bind item.Visible to this.ItemsVisible
_itemsCollection.Add(item);
}
#endregion
}
}
Setting data binding within Items and Item properties is nothing but listening PropertyChanged or CollectionChanged event from proper interfaces.
You can use either += clause for the subscription, or WeakEventListener pattern, using the PropertyChangedEventManager and CollectionChangedEventManager
I prefer the last one, because:
Listening for events can lead to memory leaks.
So, your Items class should implement IWeakEventListener interface:
public class Items : INotifyPropertyChanged, IWeakEventListener
{
#region IWeakEventListener
public bool ReceiveWeakEvent(Type managerType, Object sender, EventArgs e)
{
if (sender == this._itemsCollection && managerType == typeof(CollectionChangedEventManager))
{
// Your collection has changed, you should add/remove
// subscription for PropertyChanged event
UpdateSubscriptions((NotifyCollectionChangedEventArgs)e);
return true;
}
if (sender is Item && managerType == typeof(PropertyChangedEventManager))
{
// The Visible property of an Item object has changed
// You should handle it properly here, for example, like this:
this.ItemsVisible = this._itemsCollection.All(i => i.Visible);
return true;
}
return false;
}
private void UpdateSubscriptions(NotifyCollectionChangedEventArgs e)
{
switch(e.Action)
{
case NotifyCollectionChangedAction.Add:
foreach (Item item in e.NewItems)
{
PropertyChangedEventManager.AddListener(item, this, "Visible");
}
break;
case NotifyCollectionChangedAction.Remove:
foreach (Item item in e.OldItems)
{
PropertyChangedEventManager.RemoveListener(item, this, "Visible");
}
break;
case NotifyCollectionChangedAction.Reset:
foreach (Item item in this._itemsCollection)
{
PropertyChangedEventManager.RemoveListener(item, this, "Visible");
PropertyChangedEventManager.AddListener(item, this, "Visible");
}
break;
default:
break;
}
}
...
public Items()
{
_itemsVisible = true;
_itemsCollection = new ObservableCollection<Item>();
CollectionChangedEventManager.AddListener(_itemsCollection, this);
}
}
Binding in the WPF sense only works on DependencyProperties, and does not apply between two standard properties (even when using INotifyPropertyChanged).
That said, if you are using these classes as View Models and are binding them to controls you could use a MultiConverter to set the visibility of the Control to collapsed when both the ItemsVisible and Visible property are true (for example).
Alternatively, you could add a Parent property to the Item class and set it to the parent Items class which would allow you to have the Item.Visible property return the parent's ItemsVisible property (or again whatever logic makes sense in your application).
hELLO !
Here i have a simple class example with three fields of type class B and some other stuff.
As you can see im listening on every child object change.
Since i could need alot of properties of type class B i wonder if there is a way of shrinking the code. Creating a listener + a method for each seems like i will have ALOT of code. How would i fix this ... using a dictionary or something similar? I have been told that IoC could fix this, but im not sure where to start.
public class A : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public int _id;
public int Id
{
get { return _id; }
set
{
if (_id == value)
{
return;
}
_id = value;
OnPropertyChanged("Id");
}
}
public string _name;
public string Name
{
get { return _name; }
set
{
if (_name == value)
{
return;
}
_name = value;
OnPropertyChanged("Name");
}
}
public B _firstB;
public B FirstB
{
get { return _firstB; }
set
{
if (_firstB == value)
{
return;
}
if (_firstB != null)
{
FirstB.PropertyChanged -= firstObjectB_Listener;
}
_firstB = value;
if (_firstB != null)
FirstB.PropertyChanged += new PropertyChangedEventHandler(firstObjectB_Listener);
OnPropertyChanged("FirstB");
}
}
public B _secondB;
public B SecondB
{
get { return _secondB; }
set
{
if (_secondB == value)
{
return;
}
if (_secondB != null)
{
FirstB.PropertyChanged -= secondObjectB_Listener;
}
_secondB = value;
if (_secondB != null)
SecondB.PropertyChanged += new PropertyChangedEventHandler(secondObjectB_Listener);
OnPropertyChanged("FirstB");
}
}
public B _thirdB;
public B ThirdB
{
get { return _thirdB; }
set
{
if (_thirdB == value)
{
return;
}
if (_thirdB != null)
{
ThirdB.PropertyChanged -= thirdObjectB_Listener;
}
_thirdB = value;
if (_thirdB != null)
ThirdB.PropertyChanged += new PropertyChangedEventHandler(thirdObjectB_Listener);
OnPropertyChanged("ThirdB");
}
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
void firstObjectB_Listener(object sender, PropertyChangedEventArgs e)
{
Console.WriteLine("Object A has found a change of " + e.PropertyName + " on first object B");
}
void secondObjectB_Listener(object sender, PropertyChangedEventArgs e)
{
Console.WriteLine("Object A has found a change of " + e.PropertyName + " on second object B");
}
void thirdObjectB_Listener(object sender, PropertyChangedEventArgs e)
{
Console.WriteLine("Object A has found a change of " + e.PropertyName + " on third object B");
}
}
The most elegant way I know of is to use Aspect Oriented Programming (AOP) with a tool such as PostSharp. I found INotifyPropertyChanged implementation examples here and here. These allow you to decorate your properties with an attribute and PostSharp then implements INotifyPropertyChanged for you when the code is built.
It looks like you are setting up a dependency chain. None of the AOP or static analysis solutions are going to handle this properly. Check out Update Controls, which uses dependency tracking to discover dependency chains at runtime.
Here's what your example becomes:
public class B
{
private Independent<string> _someProperty = new Independent<string>();
public string SomeProperty
{
get { return _someProperty; }
set { _someProperty.Value = value; }
}
}
public class A
{
private Dependent<string> _dependentProperty;
public A()
{
_dependentProperty = new Dependent<string>(() =>
FirstB.SomeProperty + ", " + SecondB.SomeProperty + ", " + ThirdB.SomeProperty);
}
public string DependentProperty
{
get { return _dependentProperty; }
}
private Independent<int> _id = new Independent<int>();
public int Id
{
get { return _id; }
set { _id.Value = value; }
}
private Independent<string> _name = new Independent<string>();
public string Name
{
get { return _name; }
set { _name.Value = value; }
}
private Independent<B> _firstB = new Independent<B>();
public B FirstB
{
get { return _firstB; }
set { _firstB.Value = value; }
}
private Independent<B> _secondB = new Independent<B>();
public B SecondB
{
get { return _secondB; }
set { _secondB.Value = value; }
}
private Independent<B> _thirdB = new Independent<B>();
public B ThirdB
{
get { return _thirdB; }
set { _thirdB.Value = value; }
}
}
One nice way to simplify the setup of your properties can be found here.
Regarding to your cascading notifications: I'd guess that you could use the approach outlined above to handle the (un-)subscription of events for properties implementing INotifyPropertyChanged there.
To simplify at little bit you can do the following two things.
First, in the handler of the PropertyChanged the first parameter, sender, is the object that fired the event, at least if you have implmented the OnPropertyChanged in class B the same way as in class A. This means you only need one handler for all the B properties.
private void BValueListener(object sender, PropertyChangedEventArgs e)
{
Console.WriteLine("Found change of {0} on object {1}", e.PropertyName, sender);
}
If you need to do know exactly which of the B properties did the sending you could do checks in the BValueListener method.
if (sender == FirstB) { /* Do special stuff here */ }
Having the same listener for all the B properties we can then move on to write the property setter like:
private B _thirdB;
public B ThirdB
{
get { return _thirdB; }
set {
if (UpdateBValue(ref _thirdB, value)) {
OnPropertyChanged("ThirdB");
}
}
}
private bool UpdateBValue(ref B value, B newValue)
{
if (value == newValue)
{
return false;
}
if (value != null)
{
value.PropertyChanged -= BValueListener;
}
value = newValue;
if (value != null)
{
value.PropertyChanged += BValueListener;
}
return true;
}
If you really need different handlers for each property you can modify the code above to something like
private B _thirdB;
public B ThirdB
{
get { return _thirdB; }
set
{
if (UpdateBValue(ref _thirdB, value, BValueListener))
{
OnPropertyChanged("ThirdB");
}
}
}
private bool UpdateBValue(ref B value, B newValue, PropertyChangedEventHandler eventHandler)
{
if (value == newValue)
{
return false;
}
if (value != null)
{
value.PropertyChanged -= eventHandler;
}
value = newValue;
if (value != null)
{
value.PropertyChanged += eventHandler;
}
return true;
}
where you can send in the listener method you would like to use in each case.
A tool you could consider is T4 (T4 is shipped with VS2010 so no additional dependencies needed).
T4 is a code-generation tool which can help reduce the maintainance of "tedious" code. Think ASP/PHP for code. It's also similar to XML/XSLT.
For an excellent introduction to T4 please see Oleg Sychs blog.
The benefits of code-generation in a case like this is that even though the generated code is redundant the code that you maintain (the T4 template) isn't or at least is less redundant.
So thinking about the sample you provided I wrote this T4 template:
(If you would like to try this template in Visual Studio click Add New Item, chose class template but change the extension from .cs to .tt, paste the following source in the .tt file and save. After save the result should be in the corresponding .cs file)
// ReSharper disable InconsistentNaming
// ReSharper disable PartialMethodWithSinglePart
// ReSharper disable PartialTypeWithSinglePart
<#
// This is the "model" that is "what" we would like to generate
var classDefs = new []
{
new ClassDefinition
{
Name = "A",
Properties = new []
{
P ("int" , "Id" ),
P ("string" , "Name" ),
P ("B" , "FirstB" , listenToChanges:true ),
P ("B" , "SecondB" , listenToChanges:true ),
P ("B" , "ThirdB" , listenToChanges:true ),
},
},
new ClassDefinition
{
Name = "B",
Properties = new []
{
P ("int" , "Id" ),
P ("string" , "Name" ),
},
},
};
#>
namespace SO
{
using System;
using System.ComponentModel;
<#
// This part is the template ie "how" the model will be transformed into code
foreach (var classDef in classDefs)
{
#>
// ------------------------------------------------------------------------
/// <summary>
/// class <#=classDef.Name#> (implements INotifyPropertyChanged)
/// </summary>
public partial class <#=classDef.Name#> : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged (string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler (this, new PropertyChangedEventArgs (name));
}
}
<#
foreach (var propertyDef in classDef.Properties)
{
#>
// --------------------------------------------------------------------
/// <summary>
/// Gets or sets property <#=propertyDef.Name#> (<#=propertyDef.Type#>)
/// </summary>
public <#=propertyDef.Type#> <#=propertyDef.Name#>
{
get { return <#=propertyDef.FieldName#>; }
set
{
if (<#=propertyDef.FieldName#> == value)
{
return;
}
<#
if (propertyDef.ListenToChanges)
{
#>
if (<#=propertyDef.FieldName#> != null)
{
<#=propertyDef.FieldName#>.PropertyChanged -= <#=propertyDef.ListenerName#>;
}
<#=propertyDef.FieldName#> = value;
if (<#=propertyDef.FieldName#> != null)
{
<#=propertyDef.FieldName#>.PropertyChanged += <#=propertyDef.ListenerName#>;
}
<#
}
else
{
#>
<#=propertyDef.FieldName#> = value;
<#
}
#>
<#=propertyDef.EventName#> ();
OnPropertyChanged("<#=propertyDef.Name#>");
}
}
// --------------------------------------------------------------------
<#=propertyDef.Type#> <#=propertyDef.FieldName#>;
// --------------------------------------------------------------------
partial void <#=propertyDef.EventName#> ();
// --------------------------------------------------------------------
<#
if (propertyDef.ListenToChanges)
{
#>
void <#=propertyDef.ListenerName#> (object sender, PropertyChangedEventArgs e)
{
Console.WriteLine (
"Instance of <#=classDef.Name#> detected a change of <#=propertyDef.Name#>.{0}",
e.PropertyName
);
<#=propertyDef.EventName#> ();
}
// --------------------------------------------------------------------
<#
}
}
#>
}
// ------------------------------------------------------------------------
<#
}
#>
}
<#+
class ClassDefinition
{
public string Name;
public PropertyDefinition[] Properties;
}
class PropertyDefinition
{
public string Type;
public string Name;
public bool ListenToChanges;
public string FieldName
{
get
{
return "_" + Name;
}
}
public string ListenerName
{
get
{
return Name + "_Listener";
}
}
public string EventName
{
get
{
return "Change_" + Name;
}
}
}
PropertyDefinition P (string type, string name, bool listenToChanges = false)
{
return new PropertyDefinition
{
Type = type ?? "<NO_TYPE>",
Name = name ?? "<NO_NAME>",
ListenToChanges = listenToChanges,
};
}
#>
Finally this produces this output:
// ReSharper disable InconsistentNaming
// ReSharper disable PartialMethodWithSinglePart
// ReSharper disable PartialTypeWithSinglePart
namespace SO
{
using System;
using System.ComponentModel;
// ------------------------------------------------------------------------
/// <summary>
/// class A (implements INotifyPropertyChanged)
/// </summary>
public partial class A : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged (string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler (this, new PropertyChangedEventArgs (name));
}
}
// --------------------------------------------------------------------
/// <summary>
/// Gets or sets property Id (int)
/// </summary>
public int Id
{
get { return _Id; }
set
{
if (_Id == value)
{
return;
}
_Id = value;
Change_Id ();
OnPropertyChanged("Id");
}
}
// --------------------------------------------------------------------
int _Id;
// --------------------------------------------------------------------
partial void Change_Id ();
// --------------------------------------------------------------------
// --------------------------------------------------------------------
/// <summary>
/// Gets or sets property Name (string)
/// </summary>
public string Name
{
get { return _Name; }
set
{
if (_Name == value)
{
return;
}
_Name = value;
Change_Name ();
OnPropertyChanged("Name");
}
}
// --------------------------------------------------------------------
string _Name;
// --------------------------------------------------------------------
partial void Change_Name ();
// --------------------------------------------------------------------
// --------------------------------------------------------------------
/// <summary>
/// Gets or sets property FirstB (B)
/// </summary>
public B FirstB
{
get { return _FirstB; }
set
{
if (_FirstB == value)
{
return;
}
if (_FirstB != null)
{
_FirstB.PropertyChanged -= FirstB_Listener;
}
_FirstB = value;
if (_FirstB != null)
{
_FirstB.PropertyChanged += FirstB_Listener;
}
Change_FirstB ();
OnPropertyChanged("FirstB");
}
}
// --------------------------------------------------------------------
B _FirstB;
// --------------------------------------------------------------------
partial void Change_FirstB ();
// --------------------------------------------------------------------
void FirstB_Listener (object sender, PropertyChangedEventArgs e)
{
Console.WriteLine (
"Instance of A detected a change of FirstB.{0}",
e.PropertyName
);
Change_FirstB ();
}
// --------------------------------------------------------------------
// --------------------------------------------------------------------
/// <summary>
/// Gets or sets property SecondB (B)
/// </summary>
public B SecondB
{
get { return _SecondB; }
set
{
if (_SecondB == value)
{
return;
}
if (_SecondB != null)
{
_SecondB.PropertyChanged -= SecondB_Listener;
}
_SecondB = value;
if (_SecondB != null)
{
_SecondB.PropertyChanged += SecondB_Listener;
}
Change_SecondB ();
OnPropertyChanged("SecondB");
}
}
// --------------------------------------------------------------------
B _SecondB;
// --------------------------------------------------------------------
partial void Change_SecondB ();
// --------------------------------------------------------------------
void SecondB_Listener (object sender, PropertyChangedEventArgs e)
{
Console.WriteLine (
"Instance of A detected a change of SecondB.{0}",
e.PropertyName
);
Change_SecondB ();
}
// --------------------------------------------------------------------
// --------------------------------------------------------------------
/// <summary>
/// Gets or sets property ThirdB (B)
/// </summary>
public B ThirdB
{
get { return _ThirdB; }
set
{
if (_ThirdB == value)
{
return;
}
if (_ThirdB != null)
{
_ThirdB.PropertyChanged -= ThirdB_Listener;
}
_ThirdB = value;
if (_ThirdB != null)
{
_ThirdB.PropertyChanged += ThirdB_Listener;
}
Change_ThirdB ();
OnPropertyChanged("ThirdB");
}
}
// --------------------------------------------------------------------
B _ThirdB;
// --------------------------------------------------------------------
partial void Change_ThirdB ();
// --------------------------------------------------------------------
void ThirdB_Listener (object sender, PropertyChangedEventArgs e)
{
Console.WriteLine (
"Instance of A detected a change of ThirdB.{0}",
e.PropertyName
);
Change_ThirdB ();
}
// --------------------------------------------------------------------
}
// ------------------------------------------------------------------------
// ------------------------------------------------------------------------
/// <summary>
/// class B (implements INotifyPropertyChanged)
/// </summary>
public partial class B : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged (string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler (this, new PropertyChangedEventArgs (name));
}
}
// --------------------------------------------------------------------
/// <summary>
/// Gets or sets property Id (int)
/// </summary>
public int Id
{
get { return _Id; }
set
{
if (_Id == value)
{
return;
}
_Id = value;
Change_Id ();
OnPropertyChanged("Id");
}
}
// --------------------------------------------------------------------
int _Id;
// --------------------------------------------------------------------
partial void Change_Id ();
// --------------------------------------------------------------------
// --------------------------------------------------------------------
/// <summary>
/// Gets or sets property Name (string)
/// </summary>
public string Name
{
get { return _Name; }
set
{
if (_Name == value)
{
return;
}
_Name = value;
Change_Name ();
OnPropertyChanged("Name");
}
}
// --------------------------------------------------------------------
string _Name;
// --------------------------------------------------------------------
partial void Change_Name ();
// --------------------------------------------------------------------
}
// ------------------------------------------------------------------------
}
How do you perform databinding against the MonthCalendar.SelectionRange property? Given the property is of type 'SelectionRange' which is a class I am not sure how to go about it. Any examples would be much appreciated.
Well, there don't seem to be any obvious events for this either on the MonthCalendar or the SelectionRange, and neither implements INotifyPropertyChanged, so it looks like data-binding might not be possible here.
Update: It does, however, raise the DateChanged, so you could hook some stuff together manually, or (more usefully) by subclassing the control to expose the values and events in a fashion suitable for binding. Note the Actual(...) are useful because the end (otherwise) is just before midnight, rather than midnight itself...
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Windows.Forms;
class Foo : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
Debug.WriteLine(ToString());
}
private void SetField<T>(ref T field, T value, string propertyName)
{
if (!EqualityComparer<T>.Default.Equals(field, value))
{
field = value;
OnPropertyChanged(propertyName);
}
}
private DateTime start, end;
public DateTime Start { get { return start; } set { SetField(ref start, value, "Start"); } }
public DateTime End { get { return end; } set { SetField(ref end, value, "End"); } }
}
class BindableCalendar : MonthCalendar
{
public DateTime ActualSelectionStart
{
get { return SelectionRange.Start; }
set { if (ActualSelectionStart != value) { SetSelectionRange(value, ActualSelectionEnd); } }
}
public DateTime ActualSelectionEnd
{
get { return SelectionRange.End; }
set { if (ActualSelectionEnd != value) { SetSelectionRange(ActualSelectionStart, value); } }
}
// should really use EventHandlerList here...
public event EventHandler ActualSelectionStartChanged, ActualSelectionEndChanged;
DateTime lastKnownStart, lastKnownEnd;
protected override void OnDateChanged(DateRangeEventArgs drevent)
{
base.OnDateChanged(drevent);
if (lastKnownStart != drevent.Start)
{
if (ActualSelectionStartChanged != null) ActualSelectionStartChanged(this, EventArgs.Empty);
lastKnownStart = drevent.Start;
}
if (lastKnownEnd != drevent.End)
{
if (ActualSelectionEndChanged != null) ActualSelectionEndChanged(this, EventArgs.Empty);
lastKnownEnd = drevent.End;
}
}
}
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
MonthCalendar cal;
Button btn;
using (Form form = new Form
{
Controls = {
(cal = new BindableCalendar { Dock = DockStyle.Fill, MaxSelectionCount = 10 }),
(btn = new Button { Dock = DockStyle.Bottom, Text = "thwack"})
}
})
{
Foo foo = new Foo { Start = DateTime.Today, End = DateTime.Today.AddDays(1) };
cal.DataBindings.Add("ActualSelectionStart", foo, "Start").DataSourceUpdateMode = DataSourceUpdateMode.OnPropertyChanged;
cal.DataBindings.Add("ActualSelectionEnd", foo, "End").DataSourceUpdateMode = DataSourceUpdateMode.OnPropertyChanged;
btn.Click += delegate
{
foo.Start = foo.Start.AddDays(1);
foo.End = foo.End.AddDays(1);
};
Application.Run(form);
}
}
}
For me it seems to be very simple. I just bound SelectionStart and SelectionEnd properties of the MonthCalendar component.
this.Calendar1.DataBindings.Add(new System.Windows.Forms.Binding("SelectionStart",
bindingSource, "DateField", true));
this.Calendar1.DataBindings.Add(new System.Windows.Forms.Binding("SelectionEnd",
bindingSource, "DateField", true));