我的 WPF 应用程序中有下拉菜单,其中的选择是基于其他控件的选定值的动态选择。
目前我正在对选择进行硬编码,例如-
if (dropDown1.SelectedValue == "Name 1")
dropDown2.ItemSource = new List<string>() { "one 1", "Two 2", "Three 3" };
else
dropDown2.ItemSource = new List<string>() { "one 1", "Three 3" };
但是,在整个应用程序中,下拉项中的更改可能必须复制多达 100 个位置。因此,我正在研究使用
Enum
s 来填充其选择取决于第一个值的所有控件的选择列表,而不是使用静态文本。
在这种情况下,我有两个问题
Enum
属性绑定到下拉列表(例如 ComboBox
),这个规模如何?Enum
属性绑定到 ComboBox
我如何自定义标签以使其易于阅读,例如在视图中使用空格(例如:“one 1”)?使用
Enum
来填充下拉值绝对是可能的,而且我一直在做。尽管有多种可能的方法,但我首选的方法是使用绑定和两个转换器 - 一个将 Enum
值转换为可以绑定到 ItemsSource
的数组,另一个转换单个选定值。可以通过使用 DescriptionAttribute
属性来处理空格。这是代码:
这是
Enum
值的支持视图模型:
internal class EnumViewModel
{
public EnumViewModel(Enum value)
{
this.Value = value;
var backingField = value.GetType().GetField(value.ToString());
var attr = backingField.GetCustomAttribute<DescriptionAttribute>();
if (attr != null)
this.Name = attr.Description;
else
this.Name = value.ToString();
}
public Enum Value
{
get;
}
public string Name
{
get;
}
// This is needed to ensure SelectedItem
// works properly.
public override bool Equals(object? obj)
{
if (obj is EnumViewModel evm)
return Enum.Equals(this.Value, evm.Value);
return false;
}
public override int GetHashCode()
{
return this.Value.GetHashCode();
}
public override string ToString()
{
return this.Name;
}
}
两个 XAML 转换器:
// IMPORTANT - Generally use OneTime binding here, otherwise the choice array
// will be re-created every time the underlying bound value changes. If the choice list might change, use a second backing
// property to regenerate the choice list, see below
public class EnumToItemsSourceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!(value is Enum e))
throw new Exception("EnumToItemsSourceConverter requires binding to an Enum value");
var values = Enum.GetValues(e.GetType());
return values.Cast<Enum>().Select(v => new EnumViewModel(v)).ToArray();
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class EnumToSelectedItemConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!(value is Enum e))
throw new Exception("EnumToSelectedItemConverter requires binding to an Enum value");
return new EnumViewModel(e);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!(value is EnumViewModel evm))
throw new Exception("EnumToSelectedItemConverter requires binding to an Enum value");
return evm.Value;
}
}
如果这看起来工作量很大,请考虑在示例中使用它是多么简单:
C#:
public enum TestEnum
{
FirstChoice,
SecondChoice,
[Description("Custom Choice")]
CustomChoice
}
public class TestViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
TestEnum _TestValue = TestEnum.FirstChoice;
public TestEnum TestValue
{
get => _TestValue;
set
{
_TestValue = value;
PropertyChanged?.Invoke(
this,
new PropertyChangedEventArgs(nameof(TestValue)));
}
}
}
XAML:
<ComboBox ItemsSource="{Binding TestValue,
Mode=OneTime,
Converter={StaticResource EnumToItemsSourceConverter}}"
SelectedItem="{Binding TestValue,
Mode=TwoWay,
Converter={StaticResource EnumToSelectedItemConverter}}"
/>
以上符合 99% 的情况,但是你有一个有点不寻常的要求,要根据另一个选择来改变选择。尽管对视图模型进行了一些小的更改,但这没有问题。我们只需要向视图模型添加第二个属性,其特定目的是生成选择列表,然后将实际值绑定到通用
Enum
而不是强类型:
public enum TestEnum
{
FirstChoice,
SecondChoice,
[Description("Custom Choice")]
CustomChoice
}
public enum TestEnum2
{
OtherChoice,
AnotherChoice,
YetAnotherChoice
}
public class TestViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
Enum _ChoiceGenerator = default(TestEnum);
public Enum ChoiceGenerator
{
get => _ChoiceGenerator;
set
{
_ChoiceGenerator = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ChoiceGenerator)));
}
}
Enum _TestValue = default(TestEnum);
public Enum TestValue
{
get => _TestValue;
set
{
_TestValue = value;
PropertyChanged?.Invoke(
this,
new PropertyChangedEventArgs(nameof(TestValue)));
}
}
bool _UseOtherChoiceList;
public bool UseOtherChoiceList
{
get => _UseOtherChoiceList;
set
{
_UseOtherChoiceList = value;
PropertyChanged?.Invoke(
this,
new PropertyChangedEventArgs(nameof(UseOtherChoiceList)));
if (value)
this.ChoiceGenerator = this.TestValue = default(TestEnum2);
else
this.ChoiceGenerator = this.TestValue = default(TestEnum);
}
}
}
XAML:
<ComboBox ItemsSource="{Binding ChoiceGenerator,
Converter={StaticResource EnumToItemsSourceConverter}}"
SelectedItem="{Binding TestValue,
Mode=TwoWay,
Converter={StaticResource EnumToSelectedItemConverter}}"
/>
<CheckBox IsChecked="{Binding UseOtherChoiceList}">Switch Choices</CheckBox>
注意在这种不寻常的情况下,我们将使用默认模式而不是一次性绑定
ItemsSource
,因为我们确实希望它是动态的。
最后,您关心可伸缩性这一事实,选择列表需要传播到整个应用程序中多达 100 个位置,这也使得这种方法比评论中建议的替代方法更合适,因为
ChoiceGenerator
属性可以获取任意数量的 ComboBox
ItemsSource
s - 一旦您更新 ChoiceGenerator
就会自动更改 - 而每个 SelectedValue
都可以绑定到它自己的 viewmodel 属性。您将无法使用 MarkupExtension
s 或我能想到的任何其他方法来做到这一点。