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>
我想创建一个允许操作字节中特定位的 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>