我想在我的 ViewModel 上有一个 Enum,在这种情况下,它代表用户可以选择的一组操作。目前,我通过绑定(bind)到各自的枚举成员对 XAML 中的每个单选按钮进行硬编码,使用 IValueConverters 获取显示值并设置选定的枚举。
这很好用,但是,我希望它为 Enum 中的每个成员动态生成和(如果可能)对 RadioButtons 进行分组。理想情况下,我希望根据 Enum 的 GroupName 添加 Enum 成员的单选按钮并将其放置在它所属的组(扩展器)中。
我已经搜索并搜索了一种方法来实现这一点,但到目前为止还没有找到任何合适的东西,而且对我来说理解起来并不复杂。但也许有人在那里有一些意见可以帮助我或一个巧妙的解决方案。
枚举:
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}, Converter={StaticResource EnumToNameConverter}}"
IsChecked="{Binding Path=E, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static enum:UtilityOperation.ClientShutdown}}" />
<RadioButton Margin="10 0 0 10"
Content="{Binding Source={x:Static enum:UtilityOperation.ClientRDP}, Converter={StaticResource EnumToNameConverter}}"
IsChecked="{Binding Path=E, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static enum:UtilityOperation.ClientRDP}}" />
</StackPanel>
</Expander>
IValue 转换器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, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return value != null && value.Equals(true) ? parameter : DependencyProperty.UnsetValue;
}
}
public class EnumToDisplayNameConverter : MarkupExtension, IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return ((Enum)value).GetAttributeOfType<DisplayAttribute>().Name;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
}
最佳答案
这是我如何做类似事情的一个例子。基本方法是重新模板 ListBox
并使用它来动态创建单选按钮。 viewmodel 包含要显示的值列表,这些值是基于枚举值创建的(一次,在初始化时)。那些绑定(bind)到 ListBox
.
此代码是从一个真实的应用程序中截取的,但您可能需要对其进行调整才能编译/运行而不会出现错误。
如前所述,其中一些当然是基于我在 SO 上学到的东西。
首先 - 代表每个单选按钮条目的类:
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,
}
自动循环遍历所有枚举值的代码: void InitRadioButtonList()
{
foreach (EnumType enumval in Enum.GetValues(typeof(EnumName)))
{
RadioButtonList.Add(new ListItem
{
ID = enumval,
Item = enumval.GetDescription(),
SortOrder = enumval.GetSortOrder(),
});
}
}
在 View 模型类内部: public List<ListItem> RadioButtonList { get; } = new List<ListItem>();
public List<ListItem> SortedRadioButtonList { get { return RadioButtonList.OrderBy(x => x.SortOrder); } }
这是绑定(bind)到上面创建的列表的 XAML: <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://stackoverflow.com/a/28571411/3195477 -->
<!-- https://stackoverflow.com/a/7487670/3195477 -->
<Style
x:Key="RadioButtonListBoxStyle"
TargetType="{x:Type ListBox}"
>
<Setter
Property="BorderBrush"
Value="Transparent"
/>
<Setter
Property="Background"
Value="Transparent"
/>
<Setter
Property="KeyboardNavigation.DirectionalNavigation"
Value="Cycle"
/>
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="{x:Type ListBoxItem}">
<Setter
Property="Margin"
Value="2,2,2,0"
/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border Background="Transparent">
<RadioButton
Content="{TemplateBinding ContentPresenter.Content}"
ContentTemplate="{TemplateBinding ContentPresenter.ContentTemplate}"
ContentTemplateSelector="{TemplateBinding ContentPresenter.ContentTemplateSelector}"
IsChecked="{Binding Path=IsSelected, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Style>
更新 - 我最近阅读了 https://brianlagunas.com/a-better-way-to-data-bind-enums-in-wpf/它有一些相似之处和不同之处,也值得一读。
关于c# - 动态生成、分组和绑定(bind)单选按钮到枚举,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63792146/