Refreshing UI with databind in WPF - c#

I have a 3 layer deep treeview,
-MAIN
->:SUB1
>:SUB2
>:SUB2
-X:SUB1
X:SUB2
SUB1
SUB1
where, > and X represent graphics denoting the status of that specific item (determined from backend).
I'm using an Observable Dictionary to bind to this tree (and it has an ICollectionChanged event). The structure is like this:
ObservableDictionary<string,CustomClass> mainitems;
public class CustomClass{
ObservableDictionary<string, InnerClass> sub1item;
// Bunch of properties and methods in this class
// INotify not implemented
}
public class InnerClass{
// Bunch of properties and methods in this class
// INotify not implemented
public SomeEnum Status{
get{ return this.status; }
}
}
The graphics, mentioned above, are binded using a custom converter which converts the Status enum to a path so that it can be binded (ie. <img source="{Binding Path=something, Converter={StaticResource someconverter}, Mode=OneWay" /> ).
QUESTION:
My problem is, when I update the CustomClass's sub1item dictionary with new statuses, it doesn't update it in the UI. I think implementing INotify stuff might work but I don't know where I need to update it and exactly how to do so.
Edit:
My XAML template for the treeview is as follows:
<TreeView Name="tvInstance" ItemsSource="{Binding}" TreeViewItem.Selected="tviSelected" IsTextSearchEnabled="True">
<TreeView.ItemContainerStyle>
<Style>
<Setter Property="TreeViewItem.IsExpanded" Value="{Binding Path=Value.Expanded, Mode=TwoWay}" />
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Value.CustomClass}" ItemContainerStyle="{x:Null}">
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Path=Key}"/>
</StackPanel>
<HierarchicalDataTemplate.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Value.AnotherClass}">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Path=Value.Status, Converter={StaticResource convertstatus} }"
Width="10" Height="10"/>
<Label Content="{Binding Path=Key}" />
</StackPanel>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Image Source="{Binding Path=Value, Converter={StaticResource convertstatus} }"
Width="10" Height="10"/>
<Label Content="{Binding Path=Key}" />
</StackPanel>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
EDIT: After adding all INotifyProperty events in my mainclass, my CustomClass, and my InnerClass, it still doesn't work. I'm using the Dr. WPF version of ObservableDictionary (and using a dictionary is crucial to my application since I need to do lots of lookups). Help!
Epilogue
The answers in this page are correct in that INotifyPropertyChanged needs to be implemented on properties I want updated in the UI.
I found that binding the dictionary was too much trouble so I kept both an ObservableCollection and a Dictionary. I used the dictionary for lookup and the collection for binding (since both use the same reference to the object, removing was easy with the collection and the only O(n) operation).
With regards to updating in the UI, please refer to the other posts on this page.

This may be a little long, here would be my best guess:
public class CustomClass : INotifyPropertyChanged
{
public CustomClass()
{
sub1item = new ObservableDictionary<string, InnerClass>();
// This next line may not be necessary... Changes might propogate up.
sub1item.CollectionChanged += () => NotifyPropertyChange("Sub1Item");
}
private ObservableDictionary<string, InnerClass> sub1item;
public ObservableDictionary<string, InnerClass> Sub1Item
{
get { return sub1item; }
private set { sub1item = value; NotifyPropertyChange("Sub1Item"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
public class InnerClass : INotifyPropertyChanged
{
public SomeEnum Status
{
get { return this.status; }
private set { this.status = value; NotifyPropertyChange("Status"); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
Just make sure you update your status by calling Status = something, and not directly through this.status
Edit: If you're just looking to ONLY update the single object that got the updated status, I'm not sure that this will do it. I suspect this will signal that Sub1Item changed, but mainitems will likely not know about the individual object. It depends on your implementation.
If you created a DataTemplate for CustomClass, which had a binding to Sub1Item, then your binding will properly update for only the updated status
<DataTemplate DataType="{x:Type myClrNamespace:InnerClass}">
<Grid>
<TextBlock Text={Binding Path=Status}/>
</Grid>
</DataTemplate>
...
<ListBox x:Name="listStatus"/>
Then in the C# somewhere, you could have: listStatus = mainlist[0].Sub1Item; After seeing your example of your TreeView ItemTemplate though, I'm not sure anymore.

Observable collections implement INofityCollectionChanged which is used by WPF to refresh the collection of view items.
However, for the status to be updated you need your data to implement INotifyPropertyChanged.
Each class you want to appear within the view must implement it, so WPF will know when its properties change and which of its properties has changed.
The implementation is simple...
// Should implement INotifyPropertyChanged if the dictionary itself
// can be changed and not only its items
public class CustomClass {
ObservableDictionary sub1item;
// Bunch of properties and methods in this class
// INotify not implemented
}
public class InnerClass : INotifyProperyChanged {
// Bunch of properties and methods in this class
// INotify not implemented
public SomeEnum Status{
get{ return this.status; }
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if(PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
// where ever this.status is changed directly,
// call NotifyPropertyChanged("Status")
// (at end of that method)
//
// if this.status is changed from outside class (if public),
// then add a public method NotifyStatusChanged() which calls
// NotifyPropertyChanged("Status")
//
// If Status property has a set{} then if new value != this.status,
// call NotifyPropertyChanged("Status") at end of setter
}

You need to use an event, have your class implement INotifyPropertyChanged, it would look something like this:
public class InnerClass: INotifyPropertyChanged
{
private string _propertyName;
//Implemented from INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
public string PropertyName
{
get { return _propertyName; }
set
{
_propertyName = value;
OnPropertyChanged("Name or Property Data");
}
}
//Just using string as an example, send whatever data you'd like
protected void PropertyChanged(string name)
{
//Check to make sure the event is wired.
if(PropertyChanged != null)
{
//Fire event
PropertyChanged(this, name);
}
}
}
Basically, have these events fire for your sub items and pass up to your CustomClass object. Then, if need be, have the CustomClass handle these events, and fire another event up to your main object telling it to update the UI.

ObservableDictionary(Of TKey, TValue) - VB.NET
General feature list:
ObservableDictionary(Of TKey, TValue)
AddRange getting notified only once.
Generic EventArgs(Of TKey, TValue)
NotifyDictionaryChanging(Of TKey, TValue) - a subclass of CancelEventArgs that allows cancelling operation.

working example for class of type "Task"
public class Task: INotifyPropertyChanged
{
//Implemented from INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
private string text;
public string Text
{
get { return text; }
set {
text = value;
NotifyPropertyChanged("Text");
}
}
}
On a side note its worth remembering that you need to use an ObservableCollection rathan than List to get a dynamically updating ItemSource when databinding to a collection of types. List does not notify.

Related

WPF ItemsControl doesn't refresh when I use PropertyChanged

I have list of strings and a property for it, only with get that returns List<string>. So, when I add something to my list and call OnPropertyChanged("NameOfProperty") it does not refresh my ItemsControl in the view, but when I add something in the constructor it works.
MainWindow
public MainWindow()
{
InitializeComponent();
DataContext = new MainWindowModel();
}
MainWindowModel C#
private static List<string> messages = new List<string>();
public List<string> Messages
{
get
{
return messages;
}
}
// ...
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
// ...
public void foo()
{
messages.Add("Hi");
OnPropertyChanged("Messages");
}
MainWindow XAML
<ScrollViewer Grid.Row="0" Grid.Column="1" Margin="10, 0, 0, 0" Grid.ColumnSpan="2">
<ItemsControl ItemsSource="{Binding Messages, Mode=OneWay}"/>
</ScrollViewer>
The expression
messages.Add("Hi");
does not change the value of the Messages property, and unless the property value has not actually changed, the PropertyChanged event is ignored.
In order to update the UI when a collection is modified (i.e. elements are added, moved or removed), the collection needs to implement the INotifyCollectionChanged interface. The framework provides the ObservableCollection<T> class that implements this interface.
public ObservableCollection<string> Messages { get; }
= new ObservableCollection<string>();
and just
Messages.Add("Hi");
Collection Properties which should notify the framework, that items were added or deleted must implement the interface INotifyCollectionChanged.
A default implementation is found in the ObservableCollection.
So you could change your Messages Property Type to ObservableCollection instead of List.
Changing lists to ObservableCollection should be enough.

TextBox inside a ListView bound to an object, two way binding dosen't work

Edit:
Ok after finally playing around numerous times without no luck, I have created a very small Wpf application. You can directly copy this code. Notice when you change values in the TextBox and press the Test button, the values never get updated. I don't understand why the two way binding dosen't work. Please help.
Here is the xaml:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListView Grid.Row="0"
ItemsSource="{Binding Path=Demo.CurrentParameterValue,Mode=TwoWay}"
HorizontalAlignment="Center" VerticalAlignment="Center">
<ListView.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=.,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Width="100"></TextBox>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button Grid.Row="1" Click="Button_Click">TEST</Button>
</Grid>
Here is the xaml.cs:
namespace WpfApp9
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window, INotifyPropertyChanged
{
private VmServiceMethodsViewDataGridModel _demo;
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
public VmServiceMethodsViewDataGridModel Demo
{
get => _demo;
set
{
_demo = value;
OnPropertyChanged("Demo");
}
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
Demo = new VmServiceMethodsViewDataGridModel();
Demo.CurrentParameterValue.Add(1);
Demo.CurrentParameterValue.Add(2);
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var collection = Demo.CurrentParameterValue;
MessageBox.Show(string.Format("Values are {0}, {1}", collection[0], collection[1]));
}
}
public class VmServiceMethodsViewDataGridModel : INotifyPropertyChanged
{
private List<object> _currentParameterValue;
public List<object> CurrentParameterValue
{
get => _currentParameterValue;
set
{
_currentParameterValue = value;
OnPropertyChanged("CurrentParameterValue");
}
}
public VmServiceMethodsViewDataGridModel()
{
CurrentParameterValue = new List<object>();
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
The problem with your binding is that you are trying to bind to an object. This is perfectly fine in a OneWay/OneTime scenario. But not when using binding TwoWay. You can change the value of a property e.g. in your view model, but you can't change the object instance itself. In your specific case, the binding would have to send the new long input to the view model's value collection and replace the old value. Of course this will never happen as Binding is not designed to work this way.
The technical reason is that changing the instance would mean to change the Binding.Source. Once the binding is active (controlled by a BindingExpression) it becomes immutable. Changing the source is not allowed. That's also the reason why {Binding Source={DynamicResource ...}} won't work. The BindingSource can only be static (or StaticResource - not changing resource).
You usually bind to properties. In a TwoWay binding scenario Binding can simply update the property's value. So the solution to your problem is to wrap the long values into a class and bind the TextBox to a property of this class to retrieve/modify the actual value.
In this context your code looks too complicated.
Your object structure is too complex or unnatural.
You don't need to apply the DataTemplate to a ContentControl (in XAML).
And of course as this is a UWP application, use x:Bind where possible as it will improve performance. The converter is redundant as Binding and x:Bind allow a nested PropertyPath e.g.
<ListView ItemsSource="{Binding CurrentParameterValue.ListParameterValues}">
ItemsControl.ItemsSource doesn't need a TwoWay binding. The ItemsControl will never update/replace the source collection. If you don plan to replace the source collection in the view model (e.g., AtlasMethodParameterList = new ObservableCollection<>()), then you can even set the binding mode to OneTime (which would be the default for x:Bind).
I recommend to use OneTime and if you need to replace the collection, rather call Clear() on the collection and add the new items. This will improve the performance.
Never use async void in a method signature except for event handlers.
Always use async Task, when the return type is void or when returning a value async Task<TResult>. Otherwise you will experience unexpected side effects, especially when encountering exceptions:
// An async void method must return Task
private async Task GetParameterList(string obj)
Also async methods should always be awaited. This means the method calling and awaiting an async method must itself return Task or Task<T> to be awaitable. A method returning type void cannot be awaited.
All DependencyProperty of every control, have their Binding.UpdateSourceTrigger set to UpdateSourceTrigger.PropertyChanged by default.
Exceptions are properties that are likely to raise too much consecutive property changes like a TextBox would do on each input/key press. TextBox.Text has the default set to UpdateSourceTrigger.LostFocus.
You should remove all redundant UpdateSourceTrigger.PropertyChanged from the bindings to improve readability.
Consider to use out instead of ref if you don't intend to read the variable. If you only set the value prefer to use out to hint your intent to any reader. Use in if don't intent to modify the reference (read-only reference).
Your Set method should look something like this:
protected virtual void Set<TValue>(out TValue valueTarget, TValue value, [CallerMemberName] string propertyName = null)
{
if (value != valueTarget)
{
valueTarget = value;
OnPropertyChanged(propertyName);
}
}
I refactored your complete code trying to improve it:
Parameter.cs
// The type that wraps the actual parameter value.
// Consider to use dedicated types e.g., LongParameter instead, to allow a strongly typed Value property instead of a basic property of type object.
// This prevents implicit boxing/unboxing in order to convert from object/reference type to primitive/value type and vice versa. This will improve performance.
// (Only needed because we are dealing with primitive/value types like long, double, etc)
// You would then have to define a DataTemplate for each type. Don't forget to set x:DataType on each DataTemplate.
public class Parameter : BindableBase
{
protected Parameter(object value)
{
this.Value = value;
}
private object value;
public object Value
{
get => this.value;
set => Set(out this.value, value);
}
}
VmServiceModel.cs
public class VmServiceModel : BindableBase
{
public VmServiceModel()
{
this.Parameters = new List<Parameter>();
}
private List<Parameter> _parameters;
public List<Parameter> Parameters
{
get => this._parameters;
set => Set(out this._parameters, value);
}
}
ViewModel.cs
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
this.AtlasMethodParameterList = new ObservableCollection<VmServiceModel>();
}
private ObservableCollection<VmServiceModel> _atlasMethodParameterList;
public ObservableCollection<VmServiceModel> AtlasMethodParameterList
{
get => _atlasMethodParameterList;
set => Set(out _atlasMethodParameterList, value);
}
private async Task GetParameterList(string obj)
{
foreach (var item in this.ParametersCollection)
{
var vmServiceModel = new VmServiceModel();
vmServiceModel.Parameters
.AddRange(item.Value.Cast<long>().Select(innerItem => new Parameter(innerItem)));
this.AtlasMethodParameterList.Add(vmServiceModel);
}
}
}
MainPage.xaml.cs
public sealed partial class MainPage : Page
{
public ViewModel ViewModel { get; set; }
public MainPage()
{
this.InitializeComponent();
this.ViewModel = new ViewModel();
}
}
MainPage.xaml
<Page>
<Page.Resources>
<DataTemplate x:Key="ListIntTemplate" x:DataType="local:VmServiceModel">
<ListView ItemsSource="{x:Bind Parameters}"
HorizontalAlignment="Center"
SelectionMode="None" Background="Transparent">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<controls:WrapPanel VerticalAlignment="Top"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:Parameter">
<TextBox Text="{Binding Value Mode=TwoWay}" Height="36" Width="65"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</DataTemplate>
</Page.Resources>
<Grid>
<ListView ItemsSource="{x:Bind ViewModel.AtlasMethodParameterList}"
ItemTemplate="{StaticResource ListIntTemplate}">
</ListView>
</Grid>
</Page>
But when I change the values in the TextBox it dosen't update back the source that is the CurrentParameterValue property.
Binding in ListView doesn't know how to update the Property of type object because it's ItemsSource and it can update only ICollection such as you can't interact with object like List in C#. for example:
object MyList = new object();
MyList.Add("something"); // Compile error
And in my viewmodel the object which can be a list of long, list of double etc comes from an external API.
You need this solution then.
public class VmServiceMethodsViewDataGridModel : BindableBaseThreadSafe
{
private List<object> _currentParameterValue; // or ObservableCollection
public List<object> CurrentParameterValue
{
get => _currentParameterValue;
set => Set(ref _currentParameterValue, value);
}
}
Additionally
I have no idea what do you want to achieve or solve with this syntax
<ListView ItemsSource="{x:Bind ViewModel.AtlasMethodParameterList,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}">
Everything must work with this
<ListView ItemsSource="{Binding AtlasMethodParameterList}">
Mode=TwoWay is default Mode, you may not include it here explicitly.
UpdateSourceTrigger=PropertyChanged (Default is LostFocus) is needed in UI->VM direction, not in a back way. So, it's useless here. You may apply it to the TextBox in template instead.
EDIT
Because Two-way Binding requires explicit Path and the target must be a Property which contains Setter.
The workaround with your Demo app
<ListView Grid.Row="0"
ItemsSource="{Binding Demo.CurrentParameterValue}"
HorizontalAlignment="Center" VerticalAlignment="Center">
<ListView.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Value, UpdateSourceTrigger=PropertyChanged}" Width="100"></TextBox>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
public partial class MainWindow : Window, INotifyPropertyChanged
{
private VmServiceMethodsViewDataGridModel _demo;
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
public VmServiceMethodsViewDataGridModel Demo
{
get => _demo;
set
{
_demo = value;
OnPropertyChanged("Demo");
}
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
Demo = new VmServiceMethodsViewDataGridModel();
Demo.CurrentParameterValue.Add(new MyItem { Value = 1 });
Demo.CurrentParameterValue.Add(new MyItem { Value = 2 });
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var collection = Demo.CurrentParameterValue;
MessageBox.Show(string.Format("Values are {0}, {1}", collection[0].Value, collection[1].Value));
}
}
// here it is
public class MyItem
{
public object Value { get; set; }
}
public class VmServiceMethodsViewDataGridModel : INotifyPropertyChanged
{
private List<MyItem> _currentParameterValue;
public List<MyItem> CurrentParameterValue
{
get => _currentParameterValue;
set
{
_currentParameterValue = value;
OnPropertyChanged("CurrentParameterValue");
}
}
public VmServiceMethodsViewDataGridModel()
{
CurrentParameterValue = new List<MyItem>();
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
Additionally you may implement INPC for the Value regarding to your needs.

Using a TwoWay binding to an ItemsSource with an IValueConverter

In my ViewModel I have a model class Foo which contains a property Bar
class Foo
{
byte Bar { get; set; }
}
What I want is to show that property in my View as a list of checkboxes representing the bits of this value. I managed to do this by the use of an ItemsControl and and a binding converter.
<ItemsControl ItemsSource="{Binding Bar, Converter={StaticResource ValueToLed}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Style="{StaticResource LedCheckBox}"
IsChecked="{Binding Value,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
ToolTip="{Binding Name}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
My Converter uses BitArray to get an array of bool out of this value. I also wrap each bool value inside a wrapper class to provide more properties for that value like index and name (for toolip). You can say I build a ViewModel for such a CheckBox inside my IValueConverter.
This works well but in other direction not. When I change one of those bits I want my ConvertBack to store the updated value in my ViewModel/Model but my ConvertBack is never called. I have read some stuff related to this topic and my conclusion so far is, that is is not possible to do so because the ConvertBack would be only called when the ItemsSource itself changes and not an item inside it. So is this really true?
The only(?) alternative to this approach would be to do the conversion in my ViewModel and not in an IValueConverter, right?
The C# language (and also a C# developer) should be unaware (i.e. allergic) to the internal bit representation. Whereas you need any boolean variable, just use it.
If your program deal with some low-level interfaces (e.g. bit mask in a I/O stream ), just pack/unpack the actual byte-array at the closest possible level.
That said, WPF and bits are very allergic themselves, and a checkbox bound to a single bit requires an uselessly yet messy code. I won't suggest to follow this way.
Just expose your un/check variable in your MVVM pattern, as a part of each item being templated in the list. The checkbox binding would be straightforward and your code clean.
EDIT: I understand right now what's your problem.
Did you implement the INotifyPropertyChanged pattern in the bools' wrapper, specifically for the "Value" property?
EDIT2: This is working fine for me.
<ItemsControl ItemsSource="{Binding Path=Bar}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox
IsChecked="{Binding Value,Mode=TwoWay}"
ToolTip="{Binding Name}"
/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Here is the ViewModel:
class MyCollection
{
public IEnumerable<MyWrapper> Bar { get; set; }
}
class MyWrapper : INotifyPropertyChanged
{
#region PROP Value
private bool _value;
public bool Value
{
get { return this._value; }
set
{
if (this._value != value)
{
this._value = value;
this.OnPropertyChanged("Value");
Console.WriteLine("changed="+ this._value);
}
}
}
#endregion
#region EVT PropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(
this,
new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
When a checkbox is clicked, the related property is modified. The Console.Writeline prove it.
EDIT3: Correct the code as follows:
class MyCollection : INotifyPropertyChanged
{
#region PROP Bar
private byte _bar;
public byte Bar
{
get { return this._bar; }
set
{
if (this._bar != value)
{
this._bar = value;
this.OnPropertyChanged("Bar");
Console.WriteLine("bar="+ this._bar);
this.UpdateItems();
}
}
}
#endregion
public void UpdateItems()
{
//rebuild the children collection
var collection = new List<MyWrapper>();
for (int i = 0; i < 8; i++)
{
var item = new MyWrapper();
item.Value = ((this._bar >> i) & 1) != 0;
collection.Add(item);
}
this.Items = collection;
}
#region PROP Items
private IEnumerable<MyWrapper> _items;
public IEnumerable<MyWrapper> Items
{
get { return this._items; }
set
{
if (this._items != value)
{
this._items = value;
this.OnPropertyChanged("Items");
if (this._items != null)
{
foreach (var child in this._items)
{
child.PropertyChanged += child_PropertyChanged;
}
}
}
}
}
#endregion
void child_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
//rebuild the scalar value
int value = 0;
foreach (var item in this._items)
{
value = value >> 1;
if (item.Value) value |= 0x80;
}
this.Bar = (byte)value;
}
#region EVT PropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(
this,
new PropertyChangedEventArgs(propertyName));
}
}
#endregion
}
You should create a value converter to convert a single ubyte value to checked/unchecked for the Checkbox since the ConvertBack method of the Converter is not called for the collection if a value for an item in the collection changes.
Your XAML should look like this:
<ItemsControl ItemsSource="{Binding Bar">
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox Style="{StaticResource LedCheckBox}"
IsChecked="{Binding Value,
Mode=TwoWay,
Converter={StaticResource ValueToLedConverter},
UpdateSourceTrigger=PropertyChanged}"
ToolTip="{Binding Name}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

the right way for MVVM pattern implementation

I'm trying to implement the MVVM, so i dont know the following is correct.
It seems that ViewModel is some kind of model of the view, so associations in view shall be shown in ViewModel, in that case there shall be some associations between ViewModels. so by creating some templates for ViewModel Types, it seems the application can work, here is some example code:
ViewModels:
public class SomeVm : INotifyPropertyChanged
{
public SomeVm()
{
SomeOtherVm = new SomeOtherVm();
}
public INotifyPropertyChanged SomeOtherVm { set; get; }
private int _a;
public int A
{
set {
_a= value;
B = value;
}
get { return _a; }
}
private int _b;
public int B
{
set
{
_b = value;
OnPropertyChanged("B");
}
get { return _b; }
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public class SomeOtherVm : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int _c;
public int C
{
set
{
_c = value;
D = value;
}
get { return _c; }
}
private int _d;
public int D
{
set
{
_d = value;
OnPropertyChanged("D");
}
get { return _d; }
}
protected virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
And the View:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wpfApplication1="clr-namespace:WpfApplication1"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
x:Class="WpfApplication1.MainWindow"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<wpfApplication1:SomeVm x:Key="SomeVm"/>
<DataTemplate DataType="{x:Type wpfApplication1:SomeVm}">
<StackPanel d:DesignWidth="339" d:DesignHeight="54">
<TextBox HorizontalAlignment="Left" TextWrapping="Wrap" Text="{Binding A}" VerticalAlignment="Stretch"/>
<TextBlock HorizontalAlignment="Left" TextWrapping="Wrap" Text="{Binding B}" VerticalAlignment="Stretch"/>
<ContentPresenter Content="{Binding SomeOtherVm}"/>
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type wpfApplication1:SomeOtherVm}">
<StackPanel d:DesignWidth="339" d:DesignHeight="54">
<TextBox HorizontalAlignment="Left" TextWrapping="Wrap" Text="{Binding C}" VerticalAlignment="Stretch"/>
<TextBlock HorizontalAlignment="Left" TextWrapping="Wrap" Text="{Binding D}" VerticalAlignment="Stretch"/>
</StackPanel>
</DataTemplate>
</Window.Resources>
<Grid>
<ContentPresenter Content="{DynamicResource SomeVm}" />
</Grid>
</Window>
in this way all the views can be created in some Resource Dictionaries, so the question is: is it right to use MVVM like this? And if it is, what is the drawbacks?
Usually ViewModel is supposed to be the DataContext for the whole view i.e it should be the entity incharge of providing Data to view to render itself and to listen to UI command, events and property change to interact with the Business Layer (model).
The way you implemented it is you have your VM as a resource and set it as content not DataContext for one contentpresented and for the scenerio you have mentioned it might work well. But you should set VM as the DataContext for the whole view so that all the elements in the view can bind to the properties in the VM to render their state.
In your scenerio, if you have to add one more UI element in you view apart from the ContentPresenter then again you will have to access your resource VM.
So if you set you VM instance as DataContext (like this.DataContext = new ViewModel()) and bind your contentpresenter Content to DataContext of view like Content={Binding} that will be more correct and will help you if you ever want to extend your view. Here is a nice msdn article regarding mvvm implementation http://msdn.microsoft.com/en-us/library/gg405484(v=pandp.40).aspx
Thanks
Speaking in terms of ViewModel nesting, this code looks correct at a glance. Bindings you set up in XAML are also correct.
Concerning drawbacks, I would refrain from creating wpfApplication1:SomeVm in window resources. Usually DataContext of the Window is set to an instance of a WindowViewModel, which would in turn hold a reference to SomeVm. Imagine a class like this:
public class WindowViewModel
{
public SomeVM SomeVM{get; set;}
public string Title {get; set;} //other data to bind by window
//...
}
Then, while the window is initialized, DataContext must be set to a ViewModel instance, e.g:
MainWindow.DataContext = new WindowViewModel();
In XAML you'd use bindings again:
<Grid>
<ContentPresenter Content="{Binding SomeVm}" />
</Grid>
I'd also recommend putting your implicit DataTemplates in generic.xaml dictionary, rather then within a window. This way you can reuse these templates in your whole app.
Moreover, it is far better to use a ViewModelBase class implementing common event handling, so that you don't need to reimplement INotifyPropertyChanged. Also try to avoid "magic strings" in property change notification. Be better off using lambda based approach or the new Caller Info Attributes. I'm aware of that your example code is probably simplified, but I'm commenting on it as it is.

Bind a control to a single value in a collection/array in WPF

In WPF I have a collection of bool? values and I want to bind each of these to a separate checkbox programmatically. I want the bindings to be TwoWay so that changing the value of the individual item in the collection in code updates the check box and vice versa.
I have spent ages trying to figure out how to do this and I am completely stuck. With the following code the checkbox only gets the right value when the window is loaded and that's it. Changing the check box doesn't even update the value in the collection. (UPDATE: this appears to be a bug in .NET4 as the collection does get updated in an identical .NET3.5 project. UPDATE: Microsoft have confirmed the bug and that it will be fixed in the .NET4 release.)
Many thanks in advance for your help!
C#:
namespace MyNamespace
{
public partial class MyWindow : Window, INotifyPropertyChanged
{
public MyWindow()
{
InitializeComponent();
DataContext = this;
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public List<bool?> myCollection = new List<bool?>
{ true, false, true, false, true, false };
public List<bool?> MyCollection
{
get { return myCollection; }
set { myCollection = value; }
}
}
}
XAML:
<CheckBox IsChecked="{Binding Path=MyCollection[0], Mode=TwoWay}">
There are a few things that need changing here to get this to work. Firstly you'll need to wrap your boolean value in an object that implements the INotifyPropertyChanged interface in order to get the change notification that you are looking for. Currently you are binding to boolean values in your collection which do not implement the interface. To do this you could create a wrapper class like so :
public class Wrapper: INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private bool val = false;
public bool Val
{
get { return val; }
set
{
val = value;
this.OnPropertyChanged("Val");
}
}
public Wrapper(bool val)
{
this.val = val;
}
}
You'll then want to create these objects in your form instead of a list of booleans. You may also want to use an observable collection instead of a list so that notification of items being added and removed are sent. This is shown below:
public Window1()
{
InitializeComponent();
this.DataContext = this;
}
private ObservableCollection<Wrapper> myCollection = new ObservableCollection<Wrapper>()
{new Wrapper(true), new Wrapper(false), new Wrapper(true)};
public ObservableCollection<Wrapper> MyCollection
{
get { return myCollection; }
}
The next thing to do is to display a list of check boxes in your ui. To do this WPF provides itemscontrols. ListBox is an itemscontrol so we can use this as a starting point. Set the itemssource of a listbox to be MyCollection. We then need to define how each Wrapper object is going to be displayed in the list box and this can be done with a datatemplate which is created in the windows resources. This is shown below :
<Window.Resources>
<DataTemplate x:Key="myCollectionItems">
<CheckBox IsChecked="{Binding Path=Val, Mode=TwoWay}"></CheckBox>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox ItemsSource="{Binding Path=MyCollection}" ItemTemplate="{StaticResource myCollectionItems}"></ListBox>
</Grid>
This should get you up and running with a simple demo of checkboxes that have values bound to a list of booleans.
What makes you think it's not working? It's working for me :)
Here's my test XAML:
<UniformGrid>
<CheckBox IsChecked="{Binding Path=MyCollection[0], Mode=TwoWay}"/>
<ListBox ItemsSource="{Binding MyCollection}"/>
<Button Content="Test" Click="Button_Click"/>
</UniformGrid>
Here's my code behind:
private void Button_Click(object sender, RoutedEventArgs e)
{
}
(the rest is the same as yours)
I placed a breakpoint on Button_Click and checked MyCollection[0] it was updated according to the IsChecked value of the CheckBox.
Try changing your collection type from List<bool?> to ObservableCollection<bool?> perhaps that is the reason you think it's not working for you (the fact that changes to the collection are not reflected anywhere else in your view).
Change your List<bool?> to an ObservableCollection<bool?>. A List does not raise the change notifications that WPF needs to update the UI. An ObservableCollection does. This handles the case where the list entry is changed and the CheckBox needs to update accordingly.
In the other direction, it works for me even with a List<bool?> -- i.e. toggling the checkbox modifies the value in the collection. Your binding syntax is certainly correct.

Categories

Resources