Reputation: 373
I have an object editor view which displays simple data and also an interpretation of a value, which is templated using a TemplateSelector. If the raw value gets updated, so does the interpreted value and vice versa. In a simple DataTemplate it works well but I have a more complex scenario where the value (a ushort) represents a bitmap (field of flags). For this I use an ItemsControl with an ItemTemplate. This works OK in value->flags direction but not when the flags are clicked. I'm in the MVVM world so don't want to be reacting to events in xaml.cs files...
The View's Resources:
<DataTemplate x:Key="StandardTemplate">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<TextBox Margin="0" VerticalAlignment="Top" Width="50" Text="{Binding Formatted,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="BitmapTemplate" >
<ItemsControl ItemsSource="{Binding Flags,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" >
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel HorizontalAlignment="Center" Width="20">
<TextBlock Text="{Binding BitPosition,Converter={StaticResource intConverter},ConverterParameter=1}" HorizontalAlignment="Center" />
<CheckBox HorizontalAlignment="Center" HorizontalContentAlignment="Center" IsChecked="{Binding IsBitSet,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
<selectors:EditTemplateSelector x:Key="EditSelector" BitmapTemplate="{StaticResource BitmapTemplate}" StandardTemplate="{StaticResource StandardTemplate}" />
It gets on the view:
<ContentPresenter Content="{Binding Register}" Grid.Row="2" Grid.ColumnSpan="2" Margin="10" ContentTemplateSelector="{StaticResource EditSelector}" />
My Bitmap and BitmapBit classes are:
public class BitmapBit : ObservableObject
{
ushort _bitPosition = 0;
bool _isSet = false;
public BitmapBit(ushort bitPos, bool isSet)
{
_bitPosition = bitPos;
_isSet = isSet;
}
public ushort BitPosition
{
get { return _bitPosition; }
set
{
if (_bitPosition == value)
return;
_bitPosition = value;
RaisePropertyChanged("BitPosition");
}
}
public bool IsBitSet
{
get { return _isSet; }
set
{
if (_isSet == value)
return;
_isSet = value;
RaisePropertyChanged("IsBitSet");
}
}
}
/// <summary>
/// A collection of BitmapBits
/// </summary>
public class Bitmap : ObservableCollection<BitmapBit>
{
const ushort NUMBER_OF_BITS = sizeof(ushort) * 8; // 8 BITS_IN_A_BYTE
protected Bitmap(ushort value) : base()
{
for (ushort i = 0; i < NUMBER_OF_BITS; ++i )
Insert(0, new BitmapBit(bitPos: i, isSet: (value & (1 << i)) != 0));
}
public static Bitmap Create(ushort value)
{
return new Bitmap(value);
}
public static ushort Parse(Bitmap bits)
{
ushort generated = 0;
foreach (BitmapBit bit in bits)
generated += (ushort)(bit.IsBitSet ? 2 ^ bit.BitPosition : 0);
return generated;
}
}
The IsBitSet setter is called, but I think the problem is that the Bitmap isn't involved in the Notification that the Bit has changed - so the Flags setter never gets called. How do I update/notify the Flags property when a member of its ObservableCollection is changed?
Upvotes: 2
Views: 717
Reputation: 21261
This is because your checkboxes are updating the BitmapBit
objects that were created by your converter. They know nothing about the converter itself which was invoked separately by your ItemsControl.
If you want the original ushort to be updated then you really want to be exposing the BitmapBit
collection directly from your ViewModel rather than via a converter. That way you can update keep a VM reference in the BitmapBit
object and update the ushort in the BitmapBit
setter.
EDIT: Your edit cleared it up a bit, but the problem is still pretty much what I was getting at. The BitmapBits are unaware of the underlying Bitmap, so don't update it.
You don't show how your Flags
is defined so I can't show you how to update it, but basically all you need to do is watch for changes on each BitmapBit just like the View does, by subscribing to PropertyChanged:
protected Bitmap(ushort value) : base()
{
for (ushort i = 0; i < NUMBER_OF_BITS; ++i )
{
var bit = new BitmapBit(bitPos: i, isSet: (value & (1 << i)) != 0);
bit.PropertyChanged += (s, e) => UpdateFlags();
Insert(0, bit);
}
}
Upvotes: 1