Thomas Bauer
Thomas Bauer

Reputation: 31

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?

Upvotes: 1

Views: 3815

Answers (2)

Mario Vernari
Mario Vernari

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

helb
helb

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

Related Questions