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.
Related
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){}
}
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 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 have a base DependencyObject class where I have a method that takes an object, gets the properties, and for each property that is a type that implements INotifyPropertyChanged, I add a new PropertyChangedEventHandler. Now in the handler method, it gets the parameters of an object "sender" and the PropertyChangedEventArgs "e". My question is, does anyone know how to dynamically get the property name if sender is a property of a type that implements the INotifyPropertyChanged.
Here is what I'm working with:
public class BaseDependencyObject : DependencyObject, INotifyPropertyChanged
{
public BaseDependencyObject()
{
}
protected void SetValues(Object thisObject, Object entity)
{
try
{
PropertyInfo[] properties = entity.GetType().GetProperties();
foreach (PropertyInfo property in properties)
{
var value = property.GetValue(entity, null);
var valueIsEntity = value is System.ServiceModel.DomainServices.Client.Entity;
var thisObjectsProperty = thisObject.GetType().GetProperty(property.Name);
if (thisObjectsProperty != null && value != null)
{
if (valueIsEntity)
{
if (thisObjectsProperty.PropertyType.GetInterface("INotifyPropertyChanged", true) != null)
{
var propertyInstance = Activator.CreateInstance(thisObjectsProperty.PropertyType);
((INotifyPropertyChanged)propertyInstance).PropertyChanged += new PropertyChangedEventHandler(Object_PropertyChanged);
}
SetValues(thisObjectsProperty, value);
}
else if (thisObjectsProperty.PropertyType.GetInterface("ICollection", true) != null
&& thisObjectsProperty.PropertyType.GetGenericArguments().Count() > 0)
{
Type genericType = thisObjectsProperty.PropertyType.GetGenericArguments()[0];
var observableCollection = Activator.CreateInstance(thisObjectsProperty.PropertyType) as IList;
if (observableCollection is INotifyCollectionChanged)
((INotifyCollectionChanged)observableCollection).CollectionChanged += this.Object_CollectionChanged;
if (observableCollection is INotifyPropertyChanged)
((INotifyPropertyChanged)observableCollection).PropertyChanged += new PropertyChangedEventHandler(Object_PropertyChanged);
foreach (var item in (IEnumerable)value)
{
var newItem = Activator.CreateInstance(genericType);
if (newItem != null)
{
SetValues(newItem, item);
observableCollection.Add(newItem);
}
}
}
else
{
thisObjectsProperty.SetValue(thisObject, value, null);
}
}
}
}
catch (Exception ex)
{
throw ex;
}
}
protected void Object_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
foreach (var item in e.NewItems)
{
if (item is INotifyPropertyChanged)
{
((INotifyPropertyChanged)item).PropertyChanged += new PropertyChangedEventHandler(Object_PropertyChanged);
}
}
break;
case NotifyCollectionChangedAction.Remove:
foreach (var item in e.OldItems)
{
if (item is INotifyPropertyChanged)
{
((INotifyPropertyChanged)item).PropertyChanged -= this.Object_PropertyChanged;
}
}
break;
}
}
protected void Object_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
this.NotifyPropertyChanged(e.PropertyName);
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
The SetValues method's first param is the DependencyObject type that will be used in the view model. The second param is the entity that is being returned from the DomainService's Context.LoadOperation.
What my issue boils down to is when the INotifyCollectionChanged.CollectionChanged fires I'm needing to be able to raise the PropertyChanged event with the collection's property name. So if anyone has any advise I would greatly appreciate it. Thanks in advance.
Edit
Figured out how to get the properties name that is firing the event. Here is an edited version of my PropertyChangedEventHandler.
protected void Object_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
var properties = this.GetType().GetProperties().Where(x => x.PropertyType == sender.GetType()).ToArray();
foreach (var property in properties)
{
this.NotifyPropertyChanged(property.Name);
}
//this.NotifyPropertyChanged(e.PropertyName);
}
Basically this does what I was looking for, but aparentyly I am still not doing something right. The UIElement is still not updating when the ObservableCollection that is a property of another type is being added to.
Here is an example of my DependencyObjects and ViewModel:
public class LOB : DependencyObject
{
public Int32 ID
{
get { return (Int32)GetValue(IDProperty); }
set
{
SetValue(IDProperty, value);
NotifyPropertyChanged("ID");
}
}
public static readonly DependencyProperty IDProperty =
DependencyProperty.Register("ID", typeof(Int32), typeof(LOB), null);
public ObservableCollection<Group> Groups
{
get { return (ObservableCollection<Group>)GetValue(GroupsProperty); }
set
{
SetValue(GroupsProperty, value);
NotifyPropertyChanged("Groups");
}
}
public static readonly DependencyProperty GroupsProperty =
DependencyProperty.Register("Groups", typeof(ObservableCollection<Group>), typeof(LOB), new PropertyMetadata(null, OnGroupsPropertyChanged));
static void OnGroupsPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue != null)
{
((INotifyCollectionChanged)e.NewValue).CollectionChanged += new NotifyCollectionChangedEventHandler(((LOB)obj).Object_CollectionChanged);
((INotifyPropertyChanged)e.NewValue).PropertyChanged += new PropertyChangedEventHandler(((LOB)obj).Object_PropertyChanged);
}
if (e.OldValue != null)
{
((INotifyCollectionChanged)e.OldValue).CollectionChanged -= ((LOB)obj).Object_CollectionChanged;
((INotifyPropertyChanged)e.OldValue).PropertyChanged -= ((LOB)obj).Object_PropertyChanged;
}
}
}
public class Group : DependencyObject
{
public Int32 ID
{
get { return (Int32)GetValue(IDProperty); }
set
{
SetValue(IDProperty, value);
NotifyPropertyChanged("ID");
}
}
public static readonly DependencyProperty IDProperty =
DependencyProperty.Register("ID", typeof(Int32), typeof(Group), null);
public String GroupName
{
get { return (String)GetValue(GroupNameProperty); }
set
{
SetValue(GroupNameProperty, value);
NotifyPropertyChanged("GroupName");
}
}
public static readonly DependencyProperty GroupNameProperty =
DependencyProperty.Register("GroupName", typeof(String), typeof(Group), null);
}
public class MyViewModel : DependencyObject
{
public static readonly DependencyProperty LobCollectionProperty =
DependencyProperty.Register("LobCollection",
typeof(ObservableCollection<LOB>),
typeof(MyViewModel),
new PropertyMetadata(null, LobCollectionPropertyChanged));
public ObservableCollection<LOB> LobCollection
{
get { return (ObservableCollection<MainBusinessLine>)GetValue(LobCollectionPropertyChanged); }
set
{
SetValue(MainBusinessLineCollectionProperty, value);
NotifyPropertyChanged("LobCollection");
}
}
static void LobCollectionPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var viewModel = obj as MyViewModel;
if (viewModel == null)
return;
if (e.OldValue != null)
{
((INotifyCollectionChanged)e.OldValue).CollectionChanged -= viewModel.LobCollection_Changed;
}
if (e.NewValue != null)
{
((INotifyCollectionChanged)e.NewValue).CollectionChanged += viewModel.LobCollection_Changed;
}
}
void LobCollection_Changed(object sender, NotifyCollectionChangedEventArgs e)
{
NotifyPropertyChanged("LobCollection");
}
}
After our conversation above, this is rather moot, but I thought about how I'd implement a base class that fired PropertyChanged events when a collection changed in a property that was defined by the subclass. As I said, it's a bit non-standard, but here's how I'd do it.
class FancyCollectionAndPropertyChangedBase : INotifyPropertyChanged
{
private Dictionary<ICollectionChanged, String> collectionNameLookup = new Dictionary<ICollectionChanged, String>();
protected FancyCollectionAndPropertyChangedBase()
{
this.PropertyChanged += MyPropertyChanged;
}
private void MyPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if(this.collectionNameLookup.ContainsValue(e.PropertyName)
{
KeyValuePair<INotifyCollectionChanged, String> oldValue = this.collectionNameLookup.First(kvp => kvp.Value == e.Name);
oldValue.Key -= MyCollectionChanged;
this.collecitonNameLookup.Remove(oldValue.Key);
INotifyCollectionChanged collection = this.GetType().GetProperty(e.PropertyName, BindingFlags.FlattenHierarchy).GetValue(this, null);
collection.CollectionChanged += MyCollectionChanged;
this.collectionNameLookup.Add(collection, e.Name);
}
else if(typeof(INotifyCollectionChanged).IsAssignableFrom(this.GetType().GetProperty(e.PropertyName, BindingFlags.FlattenHierarchy).PropertyType))
{
// Note: I may have gotten the IsAssignableFrom statement, above, backwards.
INotifyCollectionChanged collection = this.GetType().GetProperty(e.PropertyName, BindingFlags.FlattenHierarchy).GetValue(this, null);
collection.CollectionChanged += MyCollectionChanged;
this.collectionNameLookup.Add(collection, e.Name);
}
}
private void MyCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
this.NotifyPropertyChanged(this.collectionNameLookup[sender];
}
}