2

I want to create a WPF control that allows to manipulate specific bits in a Byte (the markup is attached to the bottom of this post).

It should be used as follows

<ns:BitManipulator BitByte={Binding Path=...} />

but I cannot figure out how to organise (Multi-)Bindings to keep the following three values the same: a) Byte-Value in the Model that BitByte will be bound to b) Byte-Value of BitByte which either should update its value if either the model's value or the BitVector's value changes c) Bit-Representation of BitByte in the TextBoxes named order_i

Any hint appreciated

XAML

<UserControl x:Class="WpfLib.StackOverflow.BitManipulator"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>

        <TextBox Name="order_0" Grid.Column="0" />
        <TextBox Name="order_1" Grid.Column="1" />
        <TextBox Name="order_2" Grid.Column="2" />
        <TextBox Name="order_3" Grid.Column="3" />
        <TextBox Name="order_4" Grid.Column="4" />
        <TextBox Name="order_5" Grid.Column="5" />
        <TextBox Name="order_6" Grid.Column="6" />
        <TextBox Name="order_8" Grid.Column="7" />
    </Grid>
</UserControl>

C# Code behind

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Markup;

namespace WpfLib.StackOverflow
{
    [ContentProperty("BitByte")]
    public partial class BitManipulator : UserControl
    {
        public static DependencyProperty BitByteProperty = 
            DependencyProperty.Register
            (
                "BitByte", 
                typeof(Byte), 
                typeof(BitManipulator), 
                new PropertyMetadata(null)
            );

        public BitManipulator()
        {
            InitializeComponent();
        }

        public Byte BitByte
        {
            get { return (Byte)GetValue(BitByteProperty); }
            set { SetValue(BitByteProperty, value); }
        }
    }
}
Benj
  • 889
  • 1
  • 14
  • 31

4 Answers4

1

Sorry for quite time. But it seems that there is mistake on MSDN at CollectionChanged event and it's not firing for individual items changes. For sketch of your case I'll just leave some sample. XAML for bindable collection of textboxes

<ItemsControl ItemsSource="{Binding Bits}">
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <TextBox Text="{Binding Path=Value, UpdateSourceTrigger=PropertyChanged}"/>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <StackPanel IsItemsHost="True" Orientation="Horizontal"/>
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
</ItemsControl>

BindableBit class for individual items to fire changes to the upper level.

public class BindableBit : INotifyPropertyChanged {
        private int bit;
        private readonly int index;
        public Action<int, int> ChangedAction;

        public int Value {
            get { return bit; }
            set {
                bit = value;
                OnPropertyChanged();
                if (ChangedAction != null) ChangedAction(index, bit);
            }
        }

        public BindableBit(int index) {
            this.index = index;
        }

        ...PropertyChangedImplementation
    }

MainCode where we create base representation in bits and then change myByte value on every change from inside the collection.

public partial class MyCustomBitsControl: UserControl, INotifyPropertyChanged {
    private byte myByte;

    private readonly List<BindableBit> collection;
    public List<BindableBit> Bits {
        get { return collection; }
    }

    public MyCustomBitsControl() {
        const byte defaultValue = 7;
        myByte = defaultValue;
        var index = 0;
        collection = new BitArray(new[] { myByte }).Cast<bool>()
            .Select(b => new BindableBit(index++) { Value = (b ? 1 : 0), ChangedAction = ChangedAction }).Reverse().ToList();

        DataContext = this;
    }

    private void ChangedAction(int index, int value) {
        var bit = (byte)Math.Pow(2, index);
        if (value == 0) {
            myByte &= (byte)(byte.MaxValue - bit);
        }
        else {
            myByte |= bit;
        }
    }

    ...PropertyChangedImplementation
    }

After implementing dependency property you can get needed values from both the myByte field, or collection. Just reinitialize the collection on setValues like we've done in curent constructor. All changes will appear at UI(don't forget to notify for that change or modify its type to observableCollection).

1

Here is the solution that uses binding to indexer that is defined on the control itself:

User control code-behind

Here the UserControl itself implements indexer property and INotifyPropertyChanged interface (to allow notification about the changes in the indexer property).

[ContentProperty("BitByte")]
public partial class BitManipulator : UserControl,
    INotifyPropertyChanged
{
    public BitManipulator()
    {
        InitializeComponent();

        this.grid_Items.DataContext = this;
    }


    #region Indexer and related

    public Boolean this[Int32 index]
    {
        get
        {
            return BitConverterExt.BitAt(this.BitByte, index);
        }
        set
        {
            this.BitByte = BitConverterExt.WithSetBitAt(this.BitByte, index, value);
        }
    }

    #endregion


    #region Dependency properties

    public static DependencyProperty BitByteProperty =
        DependencyProperty.Register
        (
            "BitByte",
            typeof(Byte),
            typeof(BitManipulator),
            new PropertyMetadata((sender, args) =>
                {
                    if (args.Property != BitByteProperty)
                        return;

                    if (args.NewValue == args.OldValue)
                        return;

                    var This = (BitManipulator)sender;

                    This.OnPropertyChanged("Item[]");
                })
        );

    public Byte BitByte
    {
        get { return (Byte)GetValue(BitByteProperty); }
        set { SetValue(BitByteProperty, value); }
    }

    #endregion


    #region INotifyPropertyChanged implementation

    public event PropertyChangedEventHandler PropertyChanged;

    private void OnPropertyChanged([CallerMemberName] String propertyName = null)
    {
        if (this.PropertyChanged == null)
            return;

        this.PropertyChanged(this,
            new PropertyChangedEventArgs(propertyName));
    }

    #endregion
}

User control xaml

To allow more easier editing I've changed TextBoxes to CheckBoxes:

<Grid x:Name="grid_Items">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="*" />
    </Grid.ColumnDefinitions>
    <CheckBox Name="order_0" Grid.Column="0" IsChecked="{Binding [0]}"/>
    <CheckBox Name="order_1" Grid.Column="1" IsChecked="{Binding [1]}"/>
    <CheckBox Name="order_2" Grid.Column="2" IsChecked="{Binding [2]}"/>
    <CheckBox Name="order_3" Grid.Column="3" IsChecked="{Binding [3]}"/>
    <CheckBox Name="order_4" Grid.Column="4" IsChecked="{Binding [4]}"/>
    <CheckBox Name="order_5" Grid.Column="5" IsChecked="{Binding [5]}"/>
    <CheckBox Name="order_6" Grid.Column="6" IsChecked="{Binding [6]}"/>
    <CheckBox Name="order_8" Grid.Column="7" IsChecked="{Binding [7]}"/>
</Grid>

Bit manipulation helper

public static class BitConverterExt
{
    public static Boolean BitAt(Byte byteValue, Int32 index)
    {
        if ((index < 0) || (index > 7))
            throw new ArgumentNullException();

        Byte mask = (Byte)(0x1 << index);

        return (byteValue & mask) != 0;
    }

    public static Byte WithSetBitAt(Byte byteValue, Int32 index, Boolean value)
    {
        if ((index < 0) || (index > 7))
            throw new ArgumentNullException();

        Byte mask = (Byte)(0x1 << index);

        if (value)
        {
            return (Byte)(byteValue | mask);
        }

        return (Byte)(byteValue & (~mask));
    }
}

Main Window code-behind (for demonstration)

Here is the main window code-behind that allows to demonstrate that any changes to either bits, user control byte or model byte property are all properly reflected.

public class ByteModel : INotifyPropertyChanged
{
    private Byte m_ValueByte = 0xAA;

    public Byte ValueByte
    {
        get
        {
            return this.m_ValueByte;
        }
        set
        {
            if (this.m_ValueByte == value)
                return;

            this.m_ValueByte = value;

            if (this.PropertyChanged != null)
                this.PropertyChanged(this,
                    new PropertyChangedEventArgs("ValueByte"));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        this.DataContext = new ByteModel();
    }
}

Main window xaml

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="auto"/>
        <ColumnDefinition/>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition/>
        <RowDefinition/>
        <RowDefinition/>
    </Grid.RowDefinitions>
    <view:BitManipulator x:Name="bitManipulator" Grid.ColumnSpan="2" BitByte="{Binding ValueByte, Mode=TwoWay}" />

    <Label Grid.Row="1" Grid.Column="0"
           Content="Change model BitByte:"/>
    <TextBox Grid.Row="1" Grid.Column="1"
             Text="{Binding ValueByte, Mode=TwoWay}"/>

    <Label Grid.Row="2" Grid.Column="0"
           Content="Change user control BitByte:"/>
    <TextBox Grid.Row="2" Grid.Column="1"
             DataContext="{Binding ElementName=bitManipulator}"
             Text="{Binding BitByte, Mode=TwoWay}"/>
</Grid>
Community
  • 1
  • 1
Eugene Podskal
  • 10,270
  • 5
  • 31
  • 53
0

You may need a multibinding to follow 3 values

 <ns:BitManipulator >
  <ns:BitManipulator.BitByte>
     <MultiBinding Converter="{StaticResource TitleSectionConverter}">
        <Binding Path="PathA" />
        <Binding Path="PathB" />
        <Binding Path="PathC" />
      </MultiBinding>
   </ns:BitManipulator.BitByte>
</ns:BitManipulator>

and use a converter to handle changes

  public class BitByteConverter : IMultiValueConverter
  {
       public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
       {
          // implement your logic to return expected value
       }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
          throw new NotImplementedException();
        }
  }

The converter will be invoked after one of 3 bindings updates it source.

Hope this helps

HungDL
  • 493
  • 2
  • 11
  • 2
    I am not sure that it is something that OP needs. As I understand he needs some adapter collection (`INotifyCollectionChanged` wrapper over the byte property) that will allow to project changes in the underlying byte to the bit controls and vice versa. – Eugene Podskal Apr 07 '15 at 09:14
  • 2
    All three properties have the same source, so multibinding is not necessary. Actually there is only one value that will affect all mentioned properties. – Vladimir Mezentsev Apr 07 '15 at 09:21
  • But one-one binding can only handle 1 property at one time. In that time, you must resolve others for calculation excepted value (example). With multibinding, you always have them whenever you want. – HungDL Apr 07 '15 at 10:42
0

Inside the BitManipulator control, I wouldn't use bindings. Instead, I would use simple event handlers to sync the textboxes and the bound byte:

<TextBox Name="order_0" Grid.Column="0" TextChanged="OnBitChanged" />
<TextBox Name="order_1" Grid.Column="1" TextChanged="OnBitChanged" />
<TextBox Name="order_2" Grid.Column="2" TextChanged="OnBitChanged" />
...

..and:

public static DependencyProperty BitByteProperty = 
    DependencyProperty.Register
    (
        ...
        //Listen for changes in the model:
        new PropertyMetadata(OnByteChanged)
    );

Implementation:

private void OnBitChanged(object sender, TextChangedEventArgs e)
{
    //Collect the bits from the textboxes, and create the new byte:
    byte newByte = ...

    //Update the bound model:
    this.BitByte = newByte;
}

private static void OnByteChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
    var manipulator = (BitManipulator)sender;
    var newByte = manipulator.BitByte;

    //Split the byte into bits and populate the textboxes:
    var bits = ...

    manipulator.order_0.Text = bits[0];
    manipulator.order_1.Text = bits[1];
    ...
}
Sphinxxx
  • 12,484
  • 4
  • 54
  • 84