Binding to WPF ViewModel properties - wpf

I'm just playing around with WPF and MVVM, and I have made a simple app that displays a Rectangle that changes color whenever Network availability changes.
But when that happens, I get this error: Cannot use a DependencyObject that belongs to a different thread than its parent Freezable.
Code
XAML
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="400" Width="600">
<DockPanel LastChildFill="True">
<Rectangle x:Name="networkStatusRectangle" Width="200" Height="200" Fill="{Binding NetworkStatusColor}" />
</DockPanel>
</Window>
Code-behind
using System.Windows;
using WpfApplication1.ViewModels;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
DataContext = new NetworkViewModel();
}
}
}
ViewModel
using System.ComponentModel;
using System.Net.NetworkInformation;
using System.Windows.Media;
namespace WpfApplication1.ViewModels
{
public class NetworkViewModel : INotifyPropertyChanged
{
private Brush _NetworkStatusColor;
public Brush NetworkStatusColor
{
get { return _NetworkStatusColor; }
set
{
_NetworkStatusColor = value;
NotifyOfPropertyChange("NetworkStatusColor");
}
}
public NetworkViewModel()
{
NetworkChange.NetworkAvailabilityChanged += new NetworkAvailabilityChangedEventHandler(NetworkChange_NetworkAvailabilityChanged);
}
protected void NetworkChange_NetworkAvailabilityChanged(object sender, NetworkAvailabilityEventArgs e)
{
if (e.IsAvailable)
{
this.NetworkStatusColor = new SolidColorBrush(Colors.Green);
}
else
{
this.NetworkStatusColor = new SolidColorBrush(Colors.Red);
}
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public void NotifyOfPropertyChange(string propertyName)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I assume that I should change the NetworkStatusColor property by invoking something?

You assume correctly. It's the Dispatcher class and the .Invoke method you want to take a look at.
Something a bit like this:
if (this.Dispatcher.Thread != Thread.CurrentThread)
{
this.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(...your method...), any, params, here);
return
}
There's an MSDN article here with some more info.

With MVVM you have a couple of options when dealing with dispatching. Either you can send some kind of message to your view to have it invoke the operation for you, or you can create some kind of abstract dispatcher service that you are able to easily mock.
Take a look at the MVVM Light toolkit, as it includes a simple dispatcher-service you can use/copy.

Related

wpf Button always disabled (with CommandBinding, CanExecute=True and IsEnabled= True)

Revised: I apologize for missing some important descriptions in the first version, now the problem should be well-defined:
so I'm making a toy CAD program with following views:
MainWindow.xaml
CustomizedUserControl.xaml
CustomizedUserControl is a Tab within MainWindow, and its DataContext is defined in MainWindow.xaml as:
<Window.Resources>
<DataTemplate DataType="{x:Type local:CustomizedTabClass}">
<local:UserControl1/>
</DataTemplate>
</Window.Resources>
And CustomizedUserControl.xaml provides a canvas and a button, so when the button is pressed the user should be able to draw on the canvas. As the following code shows, the content of Canvas is prepared by the dataContext, "tabs:CustomizedTabClass".
CustomizedUserControl.xaml
<CustomizedUserControl x:Name="Views.CustomizedUserControl11"
...
>
<Button ToolTip="Lines (L)" BorderThickness="2"
Command="{Binding ElementName=CustomizedUserControl11,
Path=DrawingCommands.LinesChainCommand}"
IsEnabled="True"
Content = "{Binding ElementName=CustomizedUserControl11,
Path=DrawingCommands.Button1Name}">
</Button>
...
<canvas x:Name="CADCanvas"
Drawing="{Binding Drawing ,Mode=TwoWay}" >
</canvas>
It is also notable that I used an external library, Fody/PropertyChanged, in all classes so property notifications would be injected without further programming.
CustomizedUserControl.xaml.cs
using PropertyChanged;
using System.ComponentModel;
using System.Windows.Controls;
[AddINotifyPropertyChangedInterface]
public partial class CustomizedUserControl: Usercontrol, INotifyPropertyChanged{
public CADDrawingCommands DrawingCommands { get; set; }
public CustomizedUserControl()
{
InitializeComponent();
DrawingCommands = new CADDrawingCommands(this);
DrawingCommands.Button1Name = "yeahjojo"; //For testing data binding
}
public event PropertyChangedEventHandler PropertyChanged = (sender, e) => { };
}
CADDrawingCommands.cs
using PropertyChanged;
using System.ComponentModel;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows;
[AddINotifyPropertyChangedInterface]
public class CADDrawingCommands : INotifyPropertyChanged{
UserControl _drawableTab;
public string Button1Name { get; set; } = "TestForDataBinding";
public RoutedCommand LinesChainCommand { get; set; } = new RoutedCommand();
public CADDrawingCommands(UserControl dTab){
_drawableTab = dTab;
CommandBinding lineCommandBinding = new CommandBinding(LinesChainCommand,
(object sender, ExecutedRoutedEventArgs e) =>
{
MessageBox.Show("Test");
//Draw on canvas inside CustomizedUserControl (modify Drawing property in CustomizedTabClass)
}, (object sender, CanExecuteRoutedEventArgs e) => { e.CanExecute = true; });
_drawableTab.CommandBindings.Add(lineCommandBinding);
}
public event PropertyChangedEventHandler PropertyChanged = (sender, e) => { };
}
The Content of Button is set correctly, as I can read the string defined in Button1Name:
Therefore I suppose the Data Binding for Command is also ok. IsEnabled has been set to true and CanExecute of the CommandBinding would only return true.
Why is my button still greyed out and not clickable?
If I define the button inside a Window instead of UserControl (and set the datacontext of the Window to its own code behind, the button will be clickable! Why?
Thank you for your time! Hopefully would somebody help me cuz I've run out of ideas and references.
Made the simplest example.
Everything works as it should.
BaseInpc is my simple INotifyPropertyChanged implementation from here: BaseInpc
using Simplified;
using System.Windows;
using System.Windows.Input;
namespace CustomizedUserControlRoutedCommand
{
public class CADDrawingCommands : BaseInpc
{
UIElement _drawableTab;
private string _button1Name = "TestForDataBinding";
public string Button1Name { get => _button1Name; set => Set(ref _button1Name, value); }
public static RoutedCommand LinesChainCommand { get; } = new RoutedCommand();
public CADDrawingCommands(UIElement dTab)
{
_drawableTab = dTab;
CommandBinding lineCommandBinding = new CommandBinding(LinesChainCommand,
(object sender, ExecutedRoutedEventArgs e) =>
{
MessageBox.Show("Test");
//Draw on canvas inside CustomizedUserControl (modify Drawing property in CustomizedTabClass)
}, (object sender, CanExecuteRoutedEventArgs e) => { e.CanExecute = true; });
_drawableTab.CommandBindings.Add(lineCommandBinding);
}
}
}
<UserControl x:Name="CustomizedUserControl11" x:Class="CustomizedUserControlRoutedCommand.CustomizedUserControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:CustomizedUserControlRoutedCommand"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<Button ToolTip="Lines (L)" BorderThickness="2"
Command="{x:Static local:CADDrawingCommands.LinesChainCommand}"
IsEnabled="True"
Content = "{Binding ElementName=CustomizedUserControl11,
Path=DrawingCommands.Button1Name}">
</Button>
</Grid>
</UserControl>
using System.Windows.Controls;
namespace CustomizedUserControlRoutedCommand
{
public partial class CustomizedUserControl : UserControl
{
public CADDrawingCommands DrawingCommands { get; }
public CustomizedUserControl()
{
DrawingCommands = new CADDrawingCommands(this);
InitializeComponent();
DrawingCommands.Button1Name = "yeahjojo"; //For testing data binding
}
}
}
<Window x:Class="CustomizedUserControlRoutedCommand.TestCustomizedUserControlWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:CustomizedUserControlRoutedCommand"
mc:Ignorable="d"
Title="TestCustomizedUserControlWindow" Height="450" Width="800">
<Grid>
<local:CustomizedUserControl/>
</Grid>
</Window>
If you showed your code in full, then I see the following problems in it:
You are setting the value incorrectly for the DrawingCommands property.
In this property, you do not raise PropertyChanged.
The binding in the Button is initialized in the InitializeComponent() method. At this point, the property is empty, and when you set a value to it, the binding cannot find out.
There are two ways to fix this:
Raise PropertyChanged in the property;
If you set the property value once in the constructor, then set it immediately in the initializer. Make the property "Read Only". This way, in my opinion, is better.
public CADDrawingCommands DrawingCommands { get; }
public FileEditTabUserControl()
{
DrawingCommands = new CADDrawingCommands(this);
InitializeComponent();
DrawingCommands.Button1Name = "yeahjojo"; //For testing data binding
}
You have a button bound to a command in the DrawingCommands.LinesChainCommand property.
But to this property, you assign an empty instance of the = new RoutedCommand () routing command.
This looks pointless enough.
If you need a routable command, create it in the "Read Only" static property.
This will make it much easier to use in XAML:
public static RoutedCommand LinesChainCommand { get; } = new RoutedCommand();
<Button ToolTip="Lines (L)" BorderThickness="2"
Command="{x:Static local:DrawingCommands.LinesChainCommand}"
IsEnabled="True"
Content = "{Binding ElementName=CustomizedUserControl11,
Path=DrawingCommands.Button1Name}">
</Button>
Raising PropertyChanged in CADDrawingCommands properties is also not visible in your code.
If it really does not exist, then the binding is also unaware of changing property values.

Data binding doesn't work on Custom controls inside collection

WPF Data binding doesnt work for custom controls that are defined inside a xaml collection tag. I just want to define a collection of custom widgets inside a custom control and bind some widgets properties against viewmodel properties. Like so.
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid>
<local:MyCustomControl>
<local:MyCustomControl.Widgets>
<local:MyCustomWidget ImportantToggle="{Binding SomeToggle}"/>
</local:MyCustomControl.Widgets>
</local:MyCustomControl>
</Grid>
</Window>
That is my custom control. I use an obseravblecollection for the widgets and call SetValue in the constructor to get propertychanged callback later (right now not used in example)
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Windows;
namespace WpfApp1
{
public class MyCustomControl : FrameworkElement
{
public ObservableCollection<MyCustomWidget> Widgets
{
get { return (ObservableCollection<MyCustomWidget>)this.GetValue(WidgetsProperty); }
set { this.SetValue(WidgetsProperty, value); }
}
public static DependencyProperty WidgetsProperty = DependencyProperty.Register("Widgets", typeof(ObservableCollection<MyCustomWidget>), typeof(MyCustomControl), new PropertyMetadata(null, (e, args) => ((MyCustomControl)e).WidgetsChanged(args)));
public void WidgetsChanged(DependencyPropertyChangedEventArgs e)
{
Debug.WriteLine("widgets collection object changed inside my custom control!");
}
public MyCustomControl()
{
this.SetValue(WidgetsProperty, new ObservableCollection<MyCustomWidget>());
}
}
}
and that is my custom widget:
namespace WpfApp1
{
public class MyCustomWidget : FrameworkContentElement
{
public bool ImportantToggle
{
get { return (bool)this.GetValue(ImportantToggleProperty); }
set { this.SetValue(ImportantToggleProperty, value); }
}
public static DependencyProperty ImportantToggleProperty = DependencyProperty.Register("ImportantToggle", typeof(bool), typeof(MyCustomWidget), new PropertyMetadata(false, (e, args) => ((MyCustomWidget)e).ImportantToggleChanged(args)));
public void ImportantToggleChanged(DependencyPropertyChangedEventArgs e)
{
Debug.WriteLine("my toggle changed inside my custom widget!");
}
}
}
And finally my simplistic ViewModel:
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace WpfApp1
{
public class MainViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private bool _someToggle;
public bool SomeToggle
{
get { return this._someToggle; }
set
{
this._someToggle = value;
this.NotifyPropertyChanged();
}
}
public MainViewModel()
{
this.SomeToggle = !this.SomeToggle;
}
}
}
Thats the output I get from Debug.Writeline: widgets collection object changed inside my custom control!
Observation: I cant bind against properties of MyCustomWidget. I understand that the binding might fail in this scenario because the observablecollection is created inside of the constructor of mycustomcontrol, but I dont know how to fix it to get the binding working inside mycustomwidget.
For that binding to work, your local:MyCustomWidget needs to have the same DataContext as the main window. WPF elements inherit their logical parent's DataContext. MyCustomWidget doesn't, because it's not in the logical tree. It's just sitting there. You're not adding it to any kind of normal child collection of its parent, just to a random ObservableCollection that the framework doesn't know about.
The code below is probably a crude hack. I haven't investigated this corner of WPF. I urge you with the utmost sincerity to find out the right way of doing this. But with this addition to your code, I hit the propertychanged event in MyCustomWidget when the binding is initialized.
public MyCustomControl()
{
this.SetValue(WidgetsProperty, new ObservableCollection<MyCustomWidget>());
Widgets.CollectionChanged += Widgets_CollectionChanged;
}
private void Widgets_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems is System.Collections.IEnumerable)
{
foreach (MyCustomWidget widget in e.NewItems)
{
AddLogicalChild(widget);
}
}
}
By the way, you can save the trouble of toggling the toggle in the MainViewModel constructor. That happens long before the binding exists. I added a checkbox instead:
<StackPanel>
<CheckBox IsChecked="{Binding SomeToggle}">Test Toggle</CheckBox>
<local:MyCustomControl>
<local:MyCustomControl.Widgets>
<local:MyCustomWidget
ImportantToggle="{Binding SomeToggle}"
/>
</local:MyCustomControl.Widgets>
</local:MyCustomControl>
</StackPanel>
Update:
This omits your Widgets collection entirely, and the binding works without any effort on our part. The child widgets will be in MyCustomControl.Children. Importantly that we aren't limiting the child type to MyCustomWidget any more. That's a significant design change, and may not fit your requirements. You could examine the Panel class closely, and write a class that works the same way, but accepts only one type of child (that would mean writing an analog of UIElementCollection, which will be mostly a big pile of tedious boilerplate).
MyCustomControl.cs
[ContentProperty("Children")]
public class MyCustomControl : Panel
{
}
MyCustomWidget.cs
public class MyCustomWidget : Control
{
public bool ImportantToggle
{
get { return (bool)this.GetValue(ImportantToggleProperty); }
set { this.SetValue(ImportantToggleProperty, value); }
}
public static DependencyProperty ImportantToggleProperty =
DependencyProperty.Register("ImportantToggle", typeof(bool), typeof(MyCustomWidget),
new PropertyMetadata(false, (e, args) => ((MyCustomWidget)e).ImportantToggleChanged(args)));
public void ImportantToggleChanged(DependencyPropertyChangedEventArgs e)
{
Debug.WriteLine("my toggle changed inside my custom widget!");
}
}
MainWindow.xaml
<local:MyCustomControl>
<local:MyCustomWidget
ImportantToggle="{Binding SomeToggle}"
/>
</local:MyCustomControl>

DataContext in Window_Loaded

I'm very new to WPF. I'm currently doing a code to detect joints coordinate using the Kinect SDK and displaying on a simple textbox in WPF. The code to detect joints are in a private void Window_Loaded(object sender, RoutedEventArgs e) method. To display the coordinates, I used DataContext. Without further ado, let's just see the XAML code:
<Window x:Class="Prototype.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="480" Width="640">
<Grid>
<TextBox x:Name="coordinateText" Width="150" Height="20" Margin="441,409,27,12" Text="{Binding Path=xInfo}"/>
</Grid>
And this is my C# code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Microsoft.Research.Kinect.Nui;
using Coding4Fun.Kinect.Wpf;
namespace Prototype
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
//this.DataContext = new Coordinate { xInfo = "5" };
}
Runtime nui = new Runtime();
private void Window_Loaded(object sender, RoutedEventArgs e)
{
this.DataContext = new Coordinate { xInfo = "5" };
nui.Initialize(RuntimeOptions.UseSkeletalTracking); //code for detecting joints
//some code for detecting joints
}
public class Coordinate
{
public string xInfo { get; set; }
public string yInfo { get; set; }
public string zInfo { get; set; }
}
}
}
The thing is the information will not be loaded in the textbox if this.DataContext = new Coordinate { xInfo = "5" }; is not placed in the MainWindow. I have to put it in the Window_Loaded method. Any solutions?
As Coder323 said When window is loaded you need tell WPF TextBox that the Value of the variable xInfo is changed so you should use INotifyPropertyChanged in your Model Class
then where ever you change theValue of your Object it will pick up the changed Value... also
Just Set the DataContext=myCordinate in the Window Constructor then, make my cordinate a variable in the window class.
public class Coordinate : INotifyPropertyChanged
{
private string xInfo;
public string XInfo {
get{retun value};
set{
xInfo=Value;
FirePropertyChanged("XInfo")
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void FirePropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Do this for other properties and now you can set the value of myCordinate.XInfo="what ever you like" in any event it will notify to your view that the respective property has changed..
I am putting my complete solution here
My Coordinate class
public class Coordinates : INotifyPropertyChanged
{
private string xInfo;
#region Implementation of INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
public string XInfo
{
get { return xInfo; }
set
{
xInfo = value;
InvokePropertyChanged(new PropertyChangedEventArgs("XInfo"));
}
}
public void InvokePropertyChanged(PropertyChangedEventArgs e)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, e);
}
#endregion
}
My Xaml
<Window x:Class="TestApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded">
<Grid>
<TextBox Text="{Binding Path=XInfo}" Height="30" Widht="100"></TextBox>
</Grid>
My Xaml.cs
namespace TestApp
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private Coordinates myCoordinates;
public MainWindow()
{
myCoordinates=new Coordinates();
this.DataContext = myCoordinates;
InitializeComponent();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
myCoordinates.XInfo = "Acbd";
}
}
}
And yes this test Project i made... is working
This might Help :)

Does WPF databinding marshall changes to the UI Thread?

I just noticed that when changing bound properties in my ViewModel (MVVM) from a background worker thread I do not get any exceptions and the view is updated correctly. Does this mean I can safely rely on wpf databinding marshalling all changes in the ViewModel to the UI Thread? I think I have read somewhere that one should make sure (in the ViewModel) that INotifyPropertyChanged.PropertyChanged is fired on the UI thread. Has this changed in 3.5 or something?
Yes for scalars, no for collections. For collections, you'll need a specialized collection that marshals for you, or manually marshal to the UI thread yourself via the Dispatcher.
You may have read that INotifyCollectionChanged.CollectionChanged must fire on the UI thread, because it's simply not true of INotifyPropertyChanged.PropertyChanged. Below is a very simple example that proves WPF marshals property changes for you.
Window1.xaml.cs:
using System.ComponentModel;
using System.Threading;
using System.Windows;
namespace WpfApplication1
{
public partial class Window1 : Window
{
private CustomerViewModel _customerViewModel;
public Window1()
{
InitializeComponent();
_customerViewModel = new CustomerViewModel();
DataContext = _customerViewModel;
var thread = new Thread((ThreadStart)delegate
{
while (true)
{
Thread.Sleep(2000);
//look ma - no marshalling!
_customerViewModel.Name += "Appended";
_customerViewModel.Address.Line1 += "Appended";
}
});
thread.Start();
}
}
public abstract class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class CustomerViewModel : ViewModel
{
private string _name;
private AddressViewModel _address = new AddressViewModel();
public string Name
{
get { return _name; }
set
{
if (_name != value)
{
_name = value;
OnPropertyChanged("Name");
}
}
}
public AddressViewModel Address
{
get { return _address; }
}
}
public class AddressViewModel : ViewModel
{
private string _line1;
public string Line1
{
get { return _line1; }
set
{
if (_line1 != value)
{
_line1 = value;
OnPropertyChanged("Line1");
}
}
}
}
}
Window1.xaml:
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<StackPanel>
<TextBox Text="{Binding Name}"/>
<TextBox Text="{Binding Address.Line1}"/>
</StackPanel>
</Window>
I believe that with 2.0 and previous incarnations of .NET you would have received an InvalidOperationException due to thread affinity when executing the aforementioned example (link posted by bitbonk is dated 2006).
Now, with 3.5, WPF does appear to marshal background thread property changes onto the dispatcher for you.
So, in short, depends which version of .NET you're targetting. Hopefully that clears up any confusion.
One of my fellow Lab49'ers blogged about it here in 2007:
http://blog.lab49.com/archives/1166

INotifyPropertyChanged not working on ObservableCollection property

I have a class called IssuesView which implements INotifyPropertyChanged. This class holds an ObservableCollection<Issue> and exposes it as a DependencyProperty called Issues for consumption by Bindings. It is defined as below -
public class IssuesView : DependencyObject, INotifyPropertyChanged
{
public Issues Issues
{
get { return (Issues)GetValue(IssuesProperty); }
set
{
SetValue(IssuesProperty, value);
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Issues"));
}
}
}
// Using a DependencyProperty as the backing store for Issues. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IssuesProperty =
DependencyProperty.Register("Issues", typeof(Issues), typeof(IssuesView), new UIPropertyMetadata(null));
public IssuesView()
{
Refresh();
}
public void Refresh()
{
this.Issues = new Issues();
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
I have a test page declared like this -
<Page x:Class="Tracker.Pages.DEMO"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cont="clr-namespace:Tracker.Controls"
Title="DEMO">
<StackPanel>
<Button Click="Button_Click">Change</Button>
<cont:IssueTimeline IssuesForTimeline="{Binding Source={StaticResource issuesView},Path = Issues}"/>
</StackPanel>
The IssuesView class is defined in Application.Resources.
Now in the event hadnler for the button i have this code -
private void Button_Click(object sender, RoutedEventArgs e)
{
IssuesView iv = Application.Current.FindResource("issuesView") as IssuesView;
if (!once)
{
foreach (Issue i in iv.Issues)
{
i.DormantFor = new TimeSpan(30, 0, 0, 0);
i.AssignedUserID = 12;
i.Name = "MyName";
i.Priority = Issue.Priorities.Critical;
i.Status = Issue.Statuses.New;
i.Summary = "NewSummary";
}
once = true;
}
else
{
iv.Refresh();
}
once is a simple boolean to test mutation of the collection versus repopulation.
The first button click alters the collection's items and the UI is updated properly since the items implement INotifyPropertyChanged but the second click repopulates the collection but does not update the UI even though the event is not null and fires properly.
Why does the UI not update on the second click? How can i make it so that repopulating the collection will cause a UI update?
You really need to simplify your repro. I can see several things wrong with it, but cannot help to solve your problem without seeing all of it. Here is my simple repro, which works just fine.
Window1.xaml:
<Window x:Name="_root" x:Class="CollectionRepro.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<StackPanel DataContext="{Binding ElementName=_root}">
<ItemsControl ItemsSource="{Binding Issues}"/>
<Button x:Name="_addButton">Add</Button>
<Button x:Name="_resetButton">Reset</Button>
</StackPanel>
</Window>
Window1.xaml.cs:
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Windows;
namespace CollectionRepro
{
public partial class Window1 : Window
{
public static readonly DependencyProperty IssuesProperty = DependencyProperty.Register("Issues",
typeof(ICollection<string>),
typeof(Window1));
public ICollection<string> Issues
{
get { return (ICollection<string>)GetValue(IssuesProperty); }
set { SetValue(IssuesProperty, value); }
}
public Window1()
{
InitializeComponent();
Reset();
_addButton.Click +=new RoutedEventHandler(_addButton_Click);
_resetButton.Click += new RoutedEventHandler(_resetButton_Click);
}
void _resetButton_Click(object sender, RoutedEventArgs e)
{
Reset();
}
void _addButton_Click(object sender, RoutedEventArgs e)
{
Issues.Add("Another issue");
}
private void Reset()
{
Issues = new ObservableCollection<string>();
}
}
}
First: there's no reason to implement INotifyProperty changed for DependencyProperties. DependencyProperties know when they change.
Second: I don't see an ObservableCollection in your code.
Third: it's not entirely clear to me (from the code you posted) where the issues you modify in the first click come from. I assume from another action, not posted here.
Am I correct if I assume that you want to clear the issues list with the second click (since I don't know what the Issues constructor does)?

Resources