Trigger PropertyChanged of the certain element in collection - c#

Having two collections in the ViewModel, one is working as a source of rows for the DataGrid (in this case have only values 1,2,3,..), and other object that represents sequencer for one column in the DataGrid (based on ID expose some value in property).
In the example below I have used default ObservableObject and FullyObservableCollection behaviour, but it was also tried with other kinds of collections with no success. WPF behavior was tested on both DataGridComboBoxColumn and DataGridTemplateColumn with ComboBox.
ViewModel:
public class ViewModel : ObservableObject
{
private FullyObservableCollection<Seq> mSequencer;
public FullyObservableCollection<Seq> Sequencer
{
get { return mSequencer; }
set { SetProperty(ref mSequencer, value); }
}
private FullyObservableCollection<RowSrc> mRowSource;
public FullyObservableCollection<RowSrc> RowSource
{
get { return mRowSource; }
set { SetProperty(ref mRowSource, value); }
}
}
Class definitions:
public class Seq : ObservableObject
{
private int mId;
public int Id
{
get { return mId; }
set { SetProperty(ref mId, value); }
}
private string mName;
public string Name
{
get { return mName; }
set { SetProperty(ref mName, value); }
}
}
public class RowSrc : ObservableObject
{
private int mValue;
public int Value
{
get { return this.mValue; }
set { SetProperty(ref mValue, value); }
}
}
View - XAML:
<CollectionViewSource x:Key="Proxy" Source="{Binding Sequencer}"/>
<DataGrid ItemsSource="{Binding RowSource}">
<DataGrid.Columns>
<DataGridComboBoxColumn
ItemsSource="{Binding Source={StaticResource Proxy}"
SelectedValueBinding="{Binding Value}"
SelectedValuePath="Id"
DisplayMemberPath="Name"/>
</DataGridComboBoxColumn>
</DataGrid.Columns>
</DataGrid>
From the above, datagrid is initiliazed with the values of RowSource, that will go throught binding tunnel to the Proxy, which will lead to Sequencer. Based on the Value of each row, object with same Id will be returned from the Sequencer collection.
As is, on the first start everything works well. Problem starts, when we want to empty the Sequencer and fill it again.
Sequencer.Clear();
Sequencer.AddRange(...);
Now the items in the Sequencer refresh, however the binding between Row[n].Combobox and the items in Sequencer DO NOT REFRESH. The reason behind this is that the properties themselves has not changed, only items in collection.
Workaround - hack:
public class RowSrc
{
...
public void InvokeChange()
{
OnPropertyChanged(nameof(Value));
}
}
public class ViewModel
{
Sequencer.Clear();
Sequencer = GenerateNewCollection();
//after update of Sequencer
foreach (var nRowSrc in RowSrc) nRowSrc.InvokeChange();
}
However this means that we would need to add this specific method to each of our Model classes, and then remember to call them on each of Sequencer change.
Q: How to invoke property change automatically, when the collection changed OR how to handle this binding correctly?

For the problem you posted, is very simply, just use an ObsevrableCollection instead of a List. In fact, ObsevrableCollection dose not only report Add and Remove, it report all kind of movement that support by INotifyCollectionChanged.CollectionChanged event. So if you use indexer of ObsevrableCollection to set an element, a CollectionChanged of type NotifyCollectionChangedAction.Replace will be raised, and the binding engine will handle it fine from that. But I am a little confused after read you comment, and can not be sure if this is really what you want.

You can raise the PropertyChanged event for your Property Prop1 through calling MyList[0].OnPropertyChanged(nameof(Foo.Prop1));.
There is no need to create a TriggerPropertyChanged method.
This works, because in your example OnPropertyChanged is public and optionally accepts a string, that describes the property name.

Related

Data-binding ObservableCollection<T> with ComboBox doesn't work with Dependency Property

I have a list of objects (ObservableCollection subjectlist) and want to display them in a Combobox via data-binding and dependency property.
WPF Data Binding to a Combo Box
I searched on stackoverflow and tried to implement the solution of Craig Suchanec in the link above. (tried the whole day now and I just don't get what's wrong with my code)
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public static readonly DependencyProperty SubjectListProperty =
DependencyProperty.Register("SubjectList",
typeof(ObservableCollection<Subject>),
typeof(MainWindow));
private ObservableCollection<Subject> subjectList = new ObservableCollection<Subject>();
Initialization init1;
public ObservableCollection<Subject> SubjectList
{
get { return (ObservableCollection<Subject>)GetValue(SubjectListProperty); }
// get { return subjectList; }
}
public MainWindow()
{
init1 = new Initialization();
subjectList = init1.createMenuSubject();
InitializeComponent();
//this.comboBox.DataContext = SubjectList;
}
}
MainWindow.xaml
<Grid>
<ComboBox x:Name="comboBox" HorizontalAlignment="Left"VerticalAlignment="Top" Width="120" Margin="321,10,0,0"
ItemsSource="{Binding ElementName=mainWindow, Path=SubjectList}" DisplayMemberPath="Name"/>
</Grid>
It DOES work if I just set the DataContext and work without dependency property, but as soon as I try to use the dependency property for data-binding it does NOT and I don't see the significant difference between my implementation and the solution given in the link.
It would be much appreciated, if somebody could help me with this problem.
I can't see anywhere in your code where you are actually setting the value of the SubjectList property.
You are however setting the value of subjectList, but you're binding to SubjectList. Note the casing difference.
You should write:
public ObservableCollection<Subject> SubjectList
{
set { base.SetValue(SubjectListProperty, value); }
get { return (ObservableCollection<Subject>)base.GetValue(SubjectListProperty); }
}
instead of
public ObservableCollection<Subject> SubjectList
{
set { base.SetValue(SubjectListProperty, value); }
get { return subjectList; }
}
or any other ad hoc format. You are setting subjectList in your constructor MainWindow(), however, it will not set the value of SubjectList (with Capital S) and a property change event is never raised. Remove subjectList.
If you are wondering why the DataContext approach works, you should note it will work even if you do not use a DepenedencyProperty. However, if you implement INotifyPropertyChange, it will work with setting ElementName too.

WPF: Binding is lost when bindings are updated

I have a WPF-Application for controlling a WCF-RESTful service, i.e. for starting, initializing and stopping it. Therefore I have a MainWindow UI which contains a UserControl to configure settings. When I initialize my service, some data is loaded into DependencyProperties and ObservableCollections to display it in the GUI. Here is the part of the method where I update these settings:
public partial class MainWindow : Window {
private void InitializeService (bool reInitialize = false) {
var restService = (RestService)this.ServiceHost.SingletonInstance;
var settings = restService.GetSettings();
//UCSettings is the "x:name" of the embedded UserControl "UserControlSettings" in this window
this.UCSettings.ExecutionTimes.Clear();
settings.ExecutionTimes.ForEach(x => this.UCSettings.ExecutionTimes.Add(x));
this.UCSettings.TableConfigurationLoader = settings.Timer.Find(x => x.Name == "TableConfigLoader");
}
}
public partial class UserControlSettings : UserControl {
public ObservableCollection<ExecutionTime> ExecutionTimes { get; set; }
public static readonly DependencyProperty TableConfigurationLoaderProperty = DependencyProperty.Register("TableConfigurationLoader", typeof(Setting), typeof(UserControlSettings), new FrameworkPropertyMetadata(default(Setting)));
public Setting TableConfigurationLoader {
get { return (Setting)this.GetValue(TableConfigurationLoaderProperty); }
set { this.SetValue(TableConfigurationLoaderProperty, value); }
}
}
public class Setting {
public string Name { get; set; }
public bool IsEnabled { get; set; }
public int ExecutionTimeId { get; set; }
}
public class ExecutionTime {
public int Id { get; set; }
public string Value { get; set; }
}
In the Code-Designer (UserControlSettings.xaml.cs) these properties are used in some bindings for a ComboBox:
<UserControl x:Class="InsightTool.Gui.UserControlSettings" x:Name="UCSettings">
<ComboBox x:Name="CbConfigLoadingExecutionTime" ItemsSource="{Binding ElementName=UCSettings, Path=ExecutionTimes}" DisplayMemberPath="Value" SelectedValue="{Binding ElementName=UCSettings, Path=TableConfigurationLoader.ExecutionTimeId}" SelectedValuePath="Id"/>
</UserControl>
When I first load in the data with the InitializeService method, everything works fine. The ComboBox is filled with the data of the ObservableCollection and the matching value is selected automatically by the ExecutionTimeId.
When I try to "reinitialize" the service, I call the same method again, but the SelectedValue binding does not work anymore. I checked the values of these properties in the debugger, but they are set correctly in this method again. What am I doing wrong here? Some samples:
Correct display first load:
Incorrect display seconds load:
TableConfigurationLoader is a dependency property. That means a lot of things, but one of them is that when you change the value of TableConfigurationLoader to a different instance of Setting, an event is raised, and this Binding handles that event and updates SelectedValue on the combo box:
SelectedValue="{Binding ElementName=UCSettings, Path=TableConfigurationLoader.ExecutionTimeId}"
However, Setting.ExecutionTimeId isn't a dependency property. It's a regular .NET CLR property, which doesn't notify anybody of anything when its value changes. So if you change the ExecutionTimeId property of the same old Setting that's already in TableConfigurationLoader, nobody knows and nothing happens.
Since Setting is not a control class, you don't particularly need or want its properties to be dependency properties. Instead, you can treat it as a viewmodel. In implementation terms, all a viewmodel really is, is any class that implements INotifyPropertyChanged. With changes to Setting shown below, I think the binding should work as you expect, if I correctly understand your problem. I've changed IsEnabled so it will raise PropertyChanged as well; you may not actually need that, but it's illustrative.
You may need to do the same with your ExecutionTime class.
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] String propName = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
}
public class Setting : ViewModelBase
{
public string Name { get; set; }
#region IsEnabled Property
private bool _isEnabled = false;
public bool IsEnabled
{
get { return _isEnabled; }
set
{
if (value != _isEnabled)
{
_isEnabled = value;
OnPropertyChanged();
}
}
}
#endregion IsEnabled Property
#region ExecutionTimeId
private int _executionTimeId = 0;
public int ExecutionTimeId
{
get { return _executionTimeId; }
set
{
if (value != _executionTimeId)
{
_executionTimeId = value;
OnPropertyChanged();
}
}
}
#endregion ExecutionTimeId
}
There are three (ish) mechanisms in WPF for notifying things that properties have changed, and you need to be using one or another somehow if you want things to update correctly:
Dependency properties of dependency objects: For properties of controls
INotifyPropertyChanged: For properties of viewmodels
INotifyCollectionChanged: For collections.
A collection property should also raise INotifyPropertyChanged.PropertyChanged when you assign a new collection instance to it. A given instance of the collection will handle raising its own events when its contents change.
ObservableCollection<T> and ReadOnlyObservableCollection<T> implement INotifyCollectionChanged so you don't have to; it's a big hassle to implement that one properly so you really don't want to go there.
Creating a new instance of Setting before referring to the actual object solved my problem. It seems that the reference to the specific property of Setting is lost, if I just "override" the existing instance of this property:
var settings = restService.GetSettings();
this.UCSettings.ExecutionTimes.Clear();
settings.ExecutionTimes.ForEach(x => this.UCSettings.ExecutionTimes.Add(x));
this.UCSettings.TableConfigurationLoader = new Setting();
this.UCSettings.TableConfigurationLoader = settings.Timer.Find(x => x.Name == "TableConfigLoader");
Try adding UpdateSourceTrigger=PropertyChanged to the binding like this
<UserControl x:Class="InsightTool.Gui.UserControlSettings" x:Name="UCSettings">
<ComboBox x:Name="CbConfigLoadingExecutionTime" ItemsSource="{Binding ElementName=UCSettings, Path=ExecutionTimes}" DisplayMemberPath="Value" SelectedValue="{Binding ElementName=UCSettings, UpdateSourceTrigger=PropertyChanged,Path=TableConfigurationLoader.ExecutionTimeId}" SelectedValuePath="Id"/>

How does WPF know to use INotifyPropertyChanged when I bind to IEnumerable?

In a view model(SomeViewModel below), a Data property returns IEnumerable<IData> where both interfaces do not implement INotifyPropertyChanged.
However, the underlying Data field is ObservableCollection<ObservableData> and both classes implement the INotifyPropertyChanged.
Finally in XAML, `Data is bound to a DataGrid.
<DataGrid ItemsSource="{Binding Data}" AutoGenerateColumns="True"/>
I thought this binding could introduce the binding memory leak described in KB938416, but to my surprise it does not.
When the method ChangeData is called, I can see DataGrid is updated and the OnPropertyChanged called a handler.
My question is: How does WPF know to use INotifyPropertyChanged when the bound data returns IEnumerable<IData> (that both do not implement INotifyPropertyChanged)??
public interface IData
{
string Name { get; }
}
// In addition to IData, implements INotifyPropertyChanged
public class ObservableData : IData, INotifyPropertyChanged
{
private string _name;
public string Name
{
get { return this._name; }
set
{
if (_name == value) { return; }
_name = value;
OnPropertyChanged("Name");
}
}
// 'OnPropertyChanged' omitted for brevity
}
// here is some ViewModel
public class SomeViewModel
{
private ObservableCollection<ObservableData> _data = new ObservableCollection<ObservableData>();
// In XAML, a DataGrid's ItemsSource is bound to this.
public IEnumerable<IData> Data { get { return _data; } }
public void ChangeData()
{
// test OC's notification
_data.Add(new ObservableData {Name = "new" });
// test ObservableData's notification
_data[0].Name += " and changed";
}
}
In your case INotifyPropertyChanged is not needed for the Data property.
Data is of type ObservableCollection which internally implements INotifyCollectionChanged.
Due to this the view gets notified whenever you add or remove items.
Even though your Data property is returned with the type of IEnumerable<IData>, the object itself is still a ObservableCollection<ObservableData>. WPF can just use the is or as operators to test whether any particular object implements INotifyPropertyChanged, regardless of the handle provided.
IEnumerable<IData> test = Data;
if (test is INotifyPropertyChanged) {
//This if block enters because test is really ObservableCollection<ObservableData>
INotifyPropertyChanged test2 = (INotifyPropertyChanged)test;
}

Setter not called when a control bound to Array element changes the value

I am new to WPF and bindings and I wanted to see if there is a way to do a two way binding between an array element and a control:
My ViewModel has a property which looks like following:
public ObservableCollection<MeaseurementValue> MeasurementValues
{
get
{
return Config.MeasurementValues;
}
set
{
if (value == null) return;
Config.MeasurementValues = value;
OnPropertyChanged("MeasurementValues");
QpatConfig.SerializeConfigFile(Config,
Path.GetPathRoot(Environment.GetFolderPath(Environment.SpecialFolder.System)) + "//Qualcomm//QPAT//qpat.config");
}
}
Where MeasurementValues is defined as following:
public class MeaseurementValue
{
public string TestName { get; set; }
public int value { get;set; }
}
Xaml looks like following:
<TextBox HorizontalAlignment="Left" Grid.Column="1" Grid.Row="1" Height="23" Margin="14,20,0,0" TextWrapping="Wrap" Text="{Binding MeasurementValues[0].value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top" Width="124" />
So the element in the xaml is bound to an Arrayelement in ViewModel. But when I change that property in text control it does not call the setter of the ViewModel.
I also changed my code that evey element in the array is also notifiable so it looks like following:
public class MeaseurementValue : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string TestName { get; set; }
private int _value;
public int value
{
get { return _value; }
set { _value = value; OnPropertyChanged("value"); }
}
void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
But that also did not work
Is that anything different that we have to do with Arrays as oppose to primitive types?
The setter gets called, just not the one you expect. In two-way bindings, only the last property in the path gets updated (in the target->source direction).
Think of it like this - if you wrote it in C#, what would you expect to happen?
MeasurementValues[0].value = 1;
What happens here is that first the MeasurementValues getter is called, then the array indexer's getter is called, but the setter is of the value property. So there's no reason for the array's setter to ever be called. You're two property accessors away from the MeasurementValues setter. Even if you write
MeasurementValues[0] = new MeasurementValue();
the MeasurementValues setter won't be called. Only
MeasurementValues = new ObservableCollection<MeaseurementValue>();
would cause the setter you're referring to be called. It's the exact same thing with bindings. The last item in the property path is the only setter used in a two-way binding. In your case it's the value property.
If you want to save the configuration each time the value property is set, you can:
Create a ValueChanged event in MeaseurementValue and hook it up when you create each item
Use some pub/sub mechanism (such as Prism's Event Aggregator) to publish that the value has changed

MVVM Properties in ViewModel?

I have a view which binds to a ViewModel
DataContext="{Binding MyViewModel, Source={StaticResource Locator}}">
the textboxes, ... are binding to the propertys IN the ViewModel:
<TextBox Text="{Binding MyValue, Mode=TwoWay}"/>
<TextBlock Text="{Binding DisplayValue}"/>
Property in ViewModel:
public MyViewModel()
{
DisplayValue = "0€";
MyValue = "0";
}
private string _myvalue;
public string MyValue
{
get
{
return _myvalue;
}
set
{
_myvalue = value;
ChangeValue();
RaisePropertyChanged(() => MyValue);
}
}
private string _displayvalue;
public string DisplayValue
{
get
{
return _displayvalue;
}
set
{
_displayvalue = value;
RaisePropertyChanged(() => DisplayValue);
}
}
private void ChangeValue()
{
//do something here and change the values of the property, e.g.:
DisplayValue = MyValue + "€";
}
This is just a snipped. I normally have ~50 properties IN THE VIEWMODEL and all the methods are also in the ViewModel (means RelayCommands AND methods, which will be called in the setter of ~50% of the properties).
As you can see, I'm not using any Model(s). Is this a normal way of using MVVM or should I create a new class and put all the properties/methods in the new class (Model)?... But how am I supposed to bind the elements in the view with the Properties in the Model, when the views DataContext is binded to the ViewModel?
Edit: To make it clear.
I have a TextBox and the TextBox is binded to a property in the ViewModel. Is this the correct way of using MVVM? Should I use a Model-Class only when I have a List (e.g. ComboBox) or also when I have several TextBox (which would be in my eyes kinda stupid and unnecessary)?
I hope I understand what you are trying to do. My solution comprises of the DependencyProperty that I use in the MVVM pattern, not the INotifyPropertyChanged.
Lets say, you have a model, that contains a property:
public class SymbolStatsModel
{
public string Symbol
{
get
{
return this._symbol;
}
set
{
this._symbol = value;
}
}
}
Then the corresponding ViewModel is going to be like this. Declare a property and a dependency property:
public string Symbol
{
get
{
return (string)GetValue(SymbolProperty);
}
set
{
SetValue(SymbolProperty, value);
}
}
public static readonly DependencyProperty SymbolProperty =
DependencyProperty.Register
(
"Symbol",
typeof(string),
typeof(SymbolStatsViewModel),
new FrameworkPropertyMetadata
(
string.Empty
)
);
And also create a property of the Model class(SymStatsModel) in the ViewModel:
public SymbolStatsModel SymbolStatsModel
{
get
{
return new SymbolStatsModel(Symbol);
}
set
{
this.Symbol = value.Symbol;
}
}
In that way, the values that you assign to the ViewModel Symbol Property are going to be assigned to the Model Property. Also, you can directly access the Model's properties from the View by accessing the property of the Model present in the ViewModel.
This may be a little hard to grasp, but this sure is the way to make the view communicate with the Model. On another thought, you can specify the property of the Model just like I have mentioned in my solution, while using the INotifyPropertyChanged. A little immature I guess, but you gave give it a thought.
"Divide and Conquer" should help you in maintainability
Try and find boundaries in your UI, logical groups, repeating parts.
Factor them out
You can always reset the DataContext, or use a more complex Binding Path like MyVm.SubVm.Property

Categories

Resources