Reputation: 31
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?
Upvotes: 1
Views: 3815
Reputation: 7304
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
}
Upvotes: 2
Reputation: 7773
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>
Upvotes: 0