Ste*_*der 18 c# data-binding wpf enums bit-manipulation
对于喜欢WPF绑定挑战的人:
我有一个近乎功能的例子,双向绑定一个复选框到一个标志枚举的单个位(感谢Ian Oakes,原始的MSDN帖子).但问题是绑定的行为就好像它是一种方式(UI到DataContext,反之亦然).因此,复选框不会初始化,但如果切换,则数据源会正确更新.Attached是定义一些附加依赖项属性的类,以启用基于位的绑定.我注意到,即使我强制DataContext更改,也永远不会调用ValueChanged.
我尝试过的:更改属性定义的顺序,使用标签和文本框确认DataContext正在冒泡更新,任何合理的FrameworkMetadataPropertyOptions(AffectsRender,BindsTwoWayByDefault),显式设置绑定模式= TwoWay,敲打墙头,改变如果发生冲突,请将ValueProperty添加到EnumValueProperty.
任何建议或想法将非常感谢,感谢您提供的任何东西!
枚举:
[Flags]
public enum Department : byte
{
None = 0x00,
A = 0x01,
B = 0x02,
C = 0x04,
D = 0x08
} // end enum Department
Run Code Online (Sandbox Code Playgroud)
XAML用法:
CheckBox Name="studentIsInDeptACheckBox"
ctrl:CheckBoxFlagsBehaviour.Mask="{x:Static c:Department.A}"
ctrl:CheckBoxFlagsBehaviour.IsChecked="{Binding Path=IsChecked, RelativeSource={RelativeSource Self}}"
ctrl:CheckBoxFlagsBehaviour.Value="{Binding Department}"
Run Code Online (Sandbox Code Playgroud)
班级:
///
/// A helper class for providing bit-wise binding.
///
public class CheckBoxFlagsBehaviour
{
private static bool isValueChanging;
public static Enum GetMask(DependencyObject obj)
{
return (Enum)obj.GetValue(MaskProperty);
} // end GetMask
public static void SetMask(DependencyObject obj, Enum value)
{
obj.SetValue(MaskProperty, value);
} // end SetMask
public static readonly DependencyProperty MaskProperty =
DependencyProperty.RegisterAttached("Mask", typeof(Enum),
typeof(CheckBoxFlagsBehaviour), new UIPropertyMetadata(null));
public static Enum GetValue(DependencyObject obj)
{
return (Enum)obj.GetValue(ValueProperty);
} // end GetValue
public static void SetValue(DependencyObject obj, Enum value)
{
obj.SetValue(ValueProperty, value);
} // end SetValue
public static readonly DependencyProperty ValueProperty =
DependencyProperty.RegisterAttached("Value", typeof(Enum),
typeof(CheckBoxFlagsBehaviour), new UIPropertyMetadata(null, ValueChanged));
private static void ValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
isValueChanging = true;
byte mask = Convert.ToByte(GetMask(d));
byte value = Convert.ToByte(e.NewValue);
BindingExpression exp = BindingOperations.GetBindingExpression(d, IsCheckedProperty);
object dataItem = GetUnderlyingDataItem(exp.DataItem);
PropertyInfo pi = dataItem.GetType().GetProperty(exp.ParentBinding.Path.Path);
pi.SetValue(dataItem, (value & mask) != 0, null);
((CheckBox)d).IsChecked = (value & mask) != 0;
isValueChanging = false;
} // end ValueChanged
public static bool? GetIsChecked(DependencyObject obj)
{
return (bool?)obj.GetValue(IsCheckedProperty);
} // end GetIsChecked
public static void SetIsChecked(DependencyObject obj, bool? value)
{
obj.SetValue(IsCheckedProperty, value);
} // end SetIsChecked
public static readonly DependencyProperty IsCheckedProperty =
DependencyProperty.RegisterAttached("IsChecked", typeof(bool?),
typeof(CheckBoxFlagsBehaviour), new UIPropertyMetadata(false, IsCheckedChanged));
private static void IsCheckedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (isValueChanging) return;
bool? isChecked = (bool?)e.NewValue;
if (isChecked != null)
{
BindingExpression exp = BindingOperations.GetBindingExpression(d, ValueProperty);
object dataItem = GetUnderlyingDataItem(exp.DataItem);
PropertyInfo pi = dataItem.GetType().GetProperty(exp.ParentBinding.Path.Path);
byte mask = Convert.ToByte(GetMask(d));
byte value = Convert.ToByte(pi.GetValue(dataItem, null));
if (isChecked.Value)
{
if ((value & mask) == 0)
{
value = (byte)(value + mask);
}
}
else
{
if ((value & mask) != 0)
{
value = (byte)(value - mask);
}
}
pi.SetValue(dataItem, value, null);
}
} // end IsCheckedChanged
///
/// Gets the underlying data item from an object.
///
/// The object to examine.
/// The underlying data item if appropriate, or the object passed in.
private static object GetUnderlyingDataItem(object o)
{
return o is DataRowView ? ((DataRowView)o).Row : o;
} // end GetUnderlyingDataItem
} // end class CheckBoxFlagsBehaviour
Run Code Online (Sandbox Code Playgroud)
Pau*_*ulJ 45
您可以使用值转换器.这是目标枚举的一个非常具体的实现,但不难看出如何使转换器更通用:
[Flags]
public enum Department
{
None = 0,
A = 1,
B = 2,
C = 4,
D = 8
}
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
this.DepartmentsPanel.DataContext = new DataObject
{
Department = Department.A | Department.C
};
}
}
public class DataObject
{
public DataObject()
{
}
public Department Department { get; set; }
}
public class DepartmentValueConverter : IValueConverter
{
private Department target;
public DepartmentValueConverter()
{
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Department mask = (Department)parameter;
this.target = (Department)value;
return ((mask & this.target) != 0);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
this.target ^= (Department)parameter;
return this.target;
}
}
Run Code Online (Sandbox Code Playgroud)
然后在XAML中使用转换器:
<Window.Resources>
<l:DepartmentValueConverter x:Key="DeptConverter" />
</Window.Resources>
<StackPanel x:Name="DepartmentsPanel">
<CheckBox Content="A"
IsChecked="{Binding
Path=Department,
Converter={StaticResource DeptConverter},
ConverterParameter={x:Static l:Department.A}}"/>
<!-- more -->
</StackPanel>
Run Code Online (Sandbox Code Playgroud)
编辑:我没有足够的"代表"(尚未!)在下面评论所以我必须更新我自己的帖子:(
在最后一条评论中,demwiz.myopenid.com说"但是当谈到双向绑定时,ConvertBack会崩溃",我已经更新了上面的示例代码来处理ConvertBack场景; 我还在这里发布了一个示例工作应用程序(编辑:请注意,示例代码下载还包括转换器的通用版本).
我个人认为这更简单,我希望这会有所帮助.
这是我想出的一些东西,可以让 View 变得干净整洁(不需要静态资源,不需要填写新的附加属性,绑定中不需要转换器或转换器参数),并且使 ViewModel 保持干净(不需要绑定额外的属性) )
视图看起来像这样:
<CheckBox Content="A" IsChecked="{Binding Department[A]}"/>
<CheckBox Content="B" IsChecked="{Binding Department[B]}"/>
<CheckBox Content="C" IsChecked="{Binding Department[C]}"/>
<CheckBox Content="D" IsChecked="{Binding Department[D]}"/>
Run Code Online (Sandbox Code Playgroud)
ViewModel 看起来像这样:
public class ViewModel : ViewModelBase
{
private Department department;
public ViewModel()
{
Department = new EnumFlags<Department>(department);
}
public Department Department { get; private set; }
}
Run Code Online (Sandbox Code Playgroud)
如果您打算为 Department 属性分配新值,请不要这样做。别管部门。将新值写入 Department.Value。
这就是神奇的地方(这个泛型类可以重用于任何标志枚举)
public class EnumFlags<T> : INotifyPropertyChanged where T : struct, IComparable, IFormattable, IConvertible
{
private T value;
public EnumFlags(T t)
{
if (!typeof(T).IsEnum) throw new ArgumentException($"{nameof(T)} must be an enum type"); // I really wish they would just let me add Enum to the generic type constraints
value = t;
}
public T Value
{
get { return value; }
set
{
if (this.value.Equals(value)) return;
this.value = value;
OnPropertyChanged("Item[]");
}
}
[IndexerName("Item")]
public bool this[T key]
{
get
{
// .net does not allow us to specify that T is an enum, so it thinks we can't cast T to int.
// to get around this, cast it to object then cast that to int.
return (((int)(object)value & (int)(object)key) == (int)(object)key);
}
set
{
if ((((int)(object)this.value & (int)(object)key) == (int)(object)key) == value) return;
this.value = (T)(object)((int)(object)this.value ^ (int)(object)key);
OnPropertyChanged("Item[]");
}
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string memberName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(memberName));
}
#endregion
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
20136 次 |
| 最近记录: |