WPF控件的组成

Composition of WPF Controls

我想创建一个允许操作字节中特定位的 WPF 控件(标记附加到此 post 的底部)。

应该如下使用

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

但我不知道如何组织(多)绑定来保持以下三个值相同: a) BitByte 将绑定到的模型中的字节值 b) BitByte 的字节值,如果模型的值或 BitVector 的值发生变化,则应更新其值 c) BitByte 在名为 order_i

的文本框中的位表示

感谢任何提示

XAML

<UserControl x:Class="WpfLib.Whosebug.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# 代码隐藏

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

namespace WpfLib.Whosebug
{
    [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); }
        }
    }
}

您可能需要多重绑定才能遵循 3 个值

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

并使用转换器来处理变化

  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();
        }
  }

转换器将在 3 个绑定之一更新其源后被调用。

希望对您有所帮助

BitManipulator 控件中,我不会使用绑定。相反,我会使用简单的事件处理程序来同步文本框和绑定字节:

<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" />
...

..和:

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

实施:

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];
    ...
}

很抱歉。但是 MSDN 上的 CollectionChanged 事件似乎有错误,并且它不会针对个别项目的更改而触发。 对于您的案例草图,我只留下一些样本。 XAML 用于可绑定的文本框集合

<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 用于触发上层更改的单个项目。

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,我们在其中创建以位为单位的基本表示,然后在 collection.

内部的每次更改时更改 myByte 值]
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
    }

实施依赖项 属性 后,您可以从 myByte 字段或 collection 中获取所需的值。只需在 setValues 上重新初始化 collection,就像我们在当前 constructor 中所做的那样。所有更改都将出现在UI(不要忘记通知该更改或将其类型修改为observableCollection)。

这是使用在控件本身上定义的 binding to indexer 的解决方案:

用户控制代码隐藏

这里 UserControl 本身实现了索引器 属性 和 INotifyPropertyChanged 接口(允许 notification about the changes 在索引器 属性 中)。

[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
}

用户控制xaml

为了让编辑更容易,我将 TextBoxes 更改为 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>

位操作助手

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));
    }
}

主要Window代码隐藏(用于演示)

这是主要的 window 代码隐藏,可以证明对位、用户控制字节或模型字节 属性 的任何更改都已正确反映。

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();
    }
}

主要 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>