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>
Related
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.
I've seen this question posted (and answered) a number of times, and I still can't seem to figure out what I'm missing...
I have a window with a list of checkboxes, and I want the ability to have checkboxes in the list enabled/disabled dynamically from code-behind. To do that I've got couple of radio buttons that call a code-behind function to toggle the 'Enabled' property of the first entry in the VisibleFeatures collection. Ideally, this would cause the first checkbox + text to enable/disable, but no UI changes occur.
What am I doing wrong?
ViewModel:
public class MyFeature
{
private bool _supported;
private bool _enabled;
private bool _selected;
public string Name { get; set; }
public bool Supported
{
get { return _supported; }
set { _supported = value; NotifyPropertyChanged("Supported"); }
}
public bool Enabled
{
get { return _enabled; }
set { _visible = value; NotifyPropertyChanged("Enabled"); }
}
public bool Selected
{
get { return _selected; }
set { _selected = value; NotifyPropertyChanged("Selected"); }
}
public MyFeature(string name)
{
Name = name;
_supported = false;
_enabled = false;
_selected = false;
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public ObservableCollection<MyFeature> VisibleFeatures { get; set; }
void VisibleFeatures_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
foreach (MyFeature item in e.NewItems)
item.PropertyChanged += MyFeature_PropertyChanged;
if (e.OldItems != null)
foreach (MyFeature item in e.OldItems)
item.PropertyChanged -= MyFeature_PropertyChanged;
}
void MyFeature_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
// NotifyPropertyChanged() defined again elsewhere in the class
NotifyPropertyChanged("VisibleFeatures");
}
public Init()
{
VisibleFeatures = new ObservableCollection<MyFeature>();
VisibleFeatures.CollectionChanged += VisibleFeatures_CollectionChanged;
VisibleFeatures.Add(new MyFeature("Feature1"));
VisibleFeatures.Add(new MyFeature("Feature2"));
...
}
XAML:
<StackPanel>
<ListView ItemsSource="{Binding VisibleFeatures}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel IsEnabled="{Binding Enabled, Mode=TwoWay}">
<CheckBox IsChecked="{Binding Selected, Mode=TwoWay}">
<TextBlock Text="{Binding Name}" />
</CheckBox>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListView>
</StackPanel>
Your class MyFeature needs to declare that it implements interface INotifyPropertyChanged. Otherwise, there will be no listener generated from XAML to listen to your property change notification.
Beside, from your example, I see no use of notifying VisibleFeatures change.
Derive your class "MyFeature" from INotifyPropertyChanged interface.
Inorder to reflect your runtime changes made in your observable collection in view, it is mandatory to derive your viewmodel class (here MyFeature class) from INotifyPropertyChanged interface.
Also, it is advisable to use same instance of your binding property wherever it is used instead of creating a new instance.
I'm new to MVVM, just recently started my first project following the MVVM pattern. I have an issue trying to validate an ObservableCollection using the IDataErrorInfo Interface. My ObservableCollection looks like this:
ObservableCollection<Magazine> magazineRepository;
public ObservableCollection<Magazine> MagazineRepository
{
get { return magazineRepository; }
set
{
if (value != null)
{
bladRepository = value;
OnPropertyChanged("MagazineRepository");
}
}
}
And my XAML like this:
<ListBox x:Name="listMagazineRepository"
Grid.ColumnSpan="2"
ItemsSource="{Binding}"
DataContext="{Binding MagazineRepository}"
DisplayMemberPath="Navn"
SelectedItem="{Binding Path=SelectedItem}"/>
<TextBox x:Name="txtName" Grid.Row="1" Grid.Column="0"
Text="{Binding ElementName=listMagazineRepository, Path=SelectedItem.Navn, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
<TextBox x:Name="txtPrice" Grid.Row="2" Grid.Column="0"
Text="{Binding ElementName=listMagazineRepository, Path=SelectedItem.Pris, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
It's just a simple listBox containing objects, when you select an item, the selected objects properties is displayed in the textboxes, and is then bound to the listbox object.
My Problem is, that when I set my code up like this, the only way I can figure out how to validate my data is in the Domain Model, which really isn't a good practise, I'd like to validate in the ViewModel before it gets there. Basically I want to validate each property in the MagazineRepository, in the ViewModel, How would you go about doing this?
PS: I'm new to posting on this board (and programming boards in general) if my question is lacking information, please let me know and I will supply the needed details.
Thanks a lot.
If i understand correctly you want to validate the Magazine object. If that's the case, one way to do it is to wrap that class in a viewmodel, let's call it MagazineVM, that implements IDataErrorInfo and keep the magazine object updated. You then bind to the view a list of MagazineVM. As a very simple example:
public class MagazineVM : IDataErrorInfo, INotifyPropertyChanged
{
private Magazine _magazine;
public int FirstMagazineProperty
{
get { return _magazine.FirstMagazineProperty; }
set { _magazine.FirstMagazineProperty = value; RaisePropertyChanged("FirstMagazineProperty"); }
}
//INotifyPropertyChanged implementation
//IDataErrorInfo implementation
}
Firstly, as Dtex says, you should use a MagazineViewModel class rather than a Magazine class. E.G.
public class MagazineViewModel : INotifyPropertyChanged, IDataErrorInfo
{
private string navn;
private string pris;
private string error;
public string Navn
{
get { return navn; }
set
{
if (navn != value)
{
navn = value;
RaisePropertyChanged("Navn");
}
}
}
public string Pris
{
get { return pris; }
set
{
if (pris != value)
{
pris = value;
RaisePropertyChanged("Pris");
}
}
}
public string Error
{
get { return error; }
set
{
if (error != value)
{
error = value;
RaisePropertyChanged("Error");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public string this[string columnName]
{
get
{
var result = string.Empty;
switch (columnName)
{
case "Pris":
if (string.IsNullOrWhiteSpace(Pris))
{
result = "Pris is required";
}
break;
case "Navn":
if (string.IsNullOrWhiteSpace(Navn))
{
result = "Navn is required";
}
break;
}
return result;
}
}
private void RaisePropertyChanged(string PropertyName)
{
var e = PropertyChanged;
if (e != null)
{
e(this, new PropertyChangedEventArgs(PropertyName));
}
}
}
The important property to note is "public string this[string columnName]". ColumnName will be one of your bound properties and this is where you can do validation.
The next thing to consider is your MainViewModel (Your DataContext). E.G.
public class MainViewModel : INotifyPropertyChanged
{
//Use a readonly observable collection. If you need to reset it use the .Clear() method
private readonly ObservableCollection<MagazineViewModel> magazines = new ObservableCollection<MagazineViewModel>();
private MagazineViewModel selectedItem;
//Keep the item being edited separate to the selected item
private MagazineViewModel itemToEdit;
public ObservableCollection<MagazineViewModel> Magazines { get { return magazines; } }
public MagazineViewModel SelectedItem
{
get { return selectedItem; }
set
{
if (selectedItem != value)
{
selectedItem = value;
RaisePropertyChanged("SelectedItem");
//When the selected item changes. Copy it to the ItemToEdit
//This keeps the the copy you are editing separate meaning that invalid data isn't committed back to your original view model
//You will have to copy the changes back to your original view model at some stage)
ItemToEdit = Copy(SelectedItem);
}
}
}
public MagazineViewModel ItemToEdit
{
get { return itemToEdit; }
set
{
if (itemToEdit != value)
{
itemToEdit = value;
RaisePropertyChanged("ItemToEdit");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public MainViewModel()
{
//Ctor...
}
//Create a copy of a MagazineViewModel
private MagazineViewModel Copy(MagazineViewModel ToCopy)
{
var vm = new MagazineViewModel();
vm.Navn = ToCopy.Navn;
vm.Pris = ToCopy.Pris;
return vm;
}
private void RaisePropertyChanged(string PropertyName)
{
//...
}
}
The only thing missing here is how you copy the changes back to the original view model. You could do it before the selected item changes (if the ItemToEdit is valid) or have a Commit button that is only enabled when the ItemToEdit is valid. If you can allow your original view models to go into an invalid state you don't need to worry about the copying.
Finally the XAML
An implicit style to show the error tooltip
<Style
TargetType="{x:Type TextBox}">
<Setter
Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}" />
</Style>
And the controls and bindings
<ListBox
ItemsSource="{Binding Magazines}"
DisplayMemberPath="Navn"
SelectedItem="{Binding Path=SelectedItem, Mode=TwoWay}" />
<TextBox
Margin="5"
x:Name="txtName"
Grid.Row="1"
Grid.Column="0"
Text="{Binding ItemToEdit.Navn, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
<TextBox
Margin="5"
x:Name="txtPrice"
Grid.Row="2"
Grid.Column="0"
Text="{Binding ItemToEdit.Pris, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
The TextBoxes bind to ItemToEdit. ItemToEdit will be an in-sync copy of the SelectedItem.
I have a collection of bools associated to days of the week - Collection() { true, false, false, false, false, false,false}; So whatever the bool represents means that this collection applies for sunday (sunday being the first day of week here).
Now I have set the itemssource of my listbox, say, to be this collection.
<ListBox ItemsSource={Binding Path=Collection, Mode=TwoWay}>
<ListBox.ItemTemplate>
<ToggleButton IsChecked={Binding Path=BoolValue, Mode=TwoWay}/>
</ListBox.ItemTemplate>
</ListBox>
However my Collection never gets updated (my collection is a dependencyproperty on the window). Plus "MyBool" class is simply just a wrapper around a bool object, with NotifyPropertyChanged implemented.
Any ideas.....my actual code is majorly complex so the above situation is a brutally simplified version, so make assumptions etc if necessary Ill work around this given that I have provided my actual code.
Thanks greatly in advance,
U.
Try to set UpdateSourceTrigger=PropertyChanged in your binding
<ToggleButton IsChecked={Binding Path=BoolValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}/>
edit:
created a small example, seems fine.
The wrapper class
public class MyBool : INotifyPropertyChanged
{
private bool _value;
public bool Value
{
get { return _value; }
set
{
_value = value;
NotifyPropertyChanged("Value");
}
}
public event PropertyChangedEventHandler PropertyChanged;
void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
The XAML
<ListBox ItemsSource="{Binding Path=Users}" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<ToggleButton IsChecked="{Binding Path=Value, Mode=TwoWay}" Content="MyButton"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Code behind
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public ObservableCollection<MyBool> Users { get; set; }
public MainWindow()
{
InitializeComponent();
Users = new ObservableCollection<MyBool>();
DataContext = this;
Loaded += new RoutedEventHandler(MainWindow_Loaded);
}
void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
FillUsers();
}
private void FillUsers()
{
for (int i = 0; i < 20; i++)
{
if(i%2 == 0)
Users.Add(new MyBool { Value = true });
else
Users.Add(new MyBool { Value = false});
}
}
}
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.