问题描述
我想在我的viewmodel上有一个Enum,在这种情况下,它代表用户可以选择的一组操作。目前,我使用IValueConverters来获取显示值并设置所选的Enum,从而对XAML中的每个单选按钮进行硬编码并绑定到各自的Enum成员。
这可以正常工作,但是,我希望它为Enum中的每个成员动态生成并(如果可能)对RadioButtons进行分组。理想情况下,我希望基于枚举的GroupName添加一个枚举成员的单选按钮并将其放置在它所属的组(扩展器)中。
我一直在寻找实现这一目标的方法,但是到目前为止,还没有找到适合我并且不难理解的东西。但是也许有人在此方面提供了一些帮助我的建议,或者是一个巧妙的解决方案。
枚举:
using System.ComponentModel.DataAnnotations;
namespace CommonLibrary.SystemSetup.Enums
{
public enum UtilityOperation
{
[display(Name="Reboot",GroupName ="Client Control")]
ClientReboot,[display(Name = "Shutdown",GroupName = "Client Control")]
ClientShutdown,[display(Name = "Remote Desktop",GroupName = "Client Control")]
ClientRDPControl,}
}
查看:
<UserControl.Resources>
<views:EnumToBoolConverter x:Key="EnumToBoolConverter" />
<views:EnumTodisplayNameConverter x:Key="EnumToNameConverter" />
</UserControl.Resources>
<Expander Foreground="White" VerticalAlignment="Center" Background="Transparent" Margin="10 10 5 5">
<!-- Sub-menu header. -->
<Expander.Header>
<TextBlock Text="Client Control"/>
</Expander.Header>
<StackPanel>
<RadioButton Margin="10 0 0 10"
Content="{Binding Source={x:Static enum:UtilityOperation.ClientReboot},Converter={StaticResource EnumToNameConverter}}"
IsChecked="{Binding Path=E,Converter={StaticResource EnumToBoolConverter},ConverterParameter={x:Static enum:UtilityOperation.ClientReboot}}" />
<RadioButton Margin="10 0 0 10"
Content="{Binding Source={x:Static enum:UtilityOperation.ClientShutdown},ConverterParameter={x:Static enum:UtilityOperation.ClientShutdown}}" />
<RadioButton Margin="10 0 0 10"
Content="{Binding Source={x:Static enum:UtilityOperation.ClientRDP},ConverterParameter={x:Static enum:UtilityOperation.ClientRDP}}" />
</StackPanel>
</Expander>
IValueConverters
public class EnumToBoolConverter : IValueConverter
{
public object Convert(object value,Type targettype,object parameter,System.Globalization.CultureInfo culture)
{
return parameter != null && parameter.Equals(value);
}
public object ConvertBack(object value,System.Globalization.CultureInfo culture)
{
return value != null && value.Equals(true) ? parameter : DependencyProperty.UnsetValue;
}
}
public class EnumTodisplayNameConverter : MarkupExtension,IValueConverter
{
public object Convert(object value,CultureInfo culture)
{
return ((Enum)value).GetAttributeOfType<displayAttribute>().Name;
}
public object ConvertBack(object value,CultureInfo culture)
{
throw new NotSupportedException();
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
}
解决方法
这是我做类似事情的一个例子。基本方法是重新模板/(.*)
并使用它动态创建单选按钮。视图模型包含要显示的值列表,这些值是基于枚举值创建的(一次初始化)。那些绑定到ListBox
。
此代码是从真实的应用程序中切出的,但是您可能需要对其进行调整才能编译/运行而不会出现错误。
其中某些当然是基于我在SO上学到的东西。
首先-代表每个单选按钮条目的类:
ListBox
我们用于将数据附加到枚举的自定义属性:
public class ListItem
{
public string Desc { get; set; }
public EnumType ID { get; set; }
public int SortOrder { get; set; }
}
用于读取属性值的代码:
[AttributeUsage(AttributeTargets.Field,Inherited = false,AllowMultiple = false)]
public class EnumMetadataAttribute : Attribute
{
public string Description { get; set; }
public int SortOrder { get; set; }
public EnumMetadataAttribute(string desc)
{
Description = desc;
SortOrder = 0;
}
public EnumMetadataAttribute(string desc,int sortOrder)
{
Description = desc;
SortOrder = sortOrder;
}
}
绝对有争议的是,使用静态对象列表来存储这种数据而不是属性枚举会更好;但这确实很好用,并且具有成为我们可以添加到现有枚举中的优点。
示例枚举:
// based on https://stackoverflow.com/a/19621488/3195477
public static class EnumerationExtensions
{
// This extension method is broken out so you can use a similar pattern with
// other MetaData elements in the future. This is your base method for each.
static T GetAttribute<T>(this Enum value) where T : Attribute
{
var type = value.GetType();
var memberInfo = type.GetMember(value.ToString());
var attributes = memberInfo[0].GetCustomAttributes(typeof(T),false);
return attributes.Length > 0
? (T)attributes[0]
: null;
}
static EnumMetadataAttribute GetEnumMetadataAttribute(this Enum value)
{
var attr = value.GetAttribute<EnumMetadataAttribute>();
return attr ?? new EnumMetadataAttribute(value.ToString(),0);
}
public static string GetDescription(this Enum value) => value.GetEnumMetadataAttribute().Description;
public static int GetSortOrder(this Enum value) => value.GetEnumMetadataAttribute().SortOrder;
}
自动遍历所有枚举值的代码:
public enum EnumName
{
[EnumMetadata("None",1)]
None,[EnumMetadata("Option2",2)]
Option2,[EnumMetadata("Option3",3)]
Option3,}
在viewmodel类中:
void InitRadioButtonList()
{
foreach (EnumType enumval in Enum.GetValues(typeof(EnumName)))
{
RadioButtonList.Add(new ListItem
{
ID = enumval,Item = enumval.GetDescription(),SortOrder = enumval.GetSortOrder(),});
}
}
以下是绑定到上面创建的列表的XAML:
public List<ListItem> RadioButtonList { get; } = new List<ListItem>();
public List<ListItem> SortedRadioButtonList { get { return RadioButtonList.OrderBy(x => x.SortOrder); } }
最后是 <WrapPanel>
<Label>Field Name:</Label>
<ListBox
ItemsSource="{Binding SortedRadioButtonList}"
SelectedValuePath="ID"
SelectedValue="{Binding SortedRadioButtonList}"
Style="{StaticResource RadioButtonListBoxStyle}"
>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Desc}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</WrapPanel>
风格:
ListBox
更新-我最近阅读了https://brianlagunas.com/a-better-way-to-data-bind-enums-in-wpf/,它具有一些相似之处和不同之处,也值得一读。