私は列挙型がそのまま表示される簡単な例を見つけようとしています。私が見たすべての例は、見栄えの良い表示文字列を追加しようとしていますが、その複雑さは望ましくありません。
基本的に、最初にこのクラスにDataContextを設定し、次にxamlファイルでこのようなバインディングを指定することにより、バインドするすべてのプロパティを保持するクラスがあります。
<ComboBox ItemsSource="{Binding Path=EffectStyle}"/>
しかし、これはComboBox
の列挙値をアイテムとして表示しません。
たとえば、Window Loaded
イベントハンドラーに次のコードを配置することで、コードから実行できます。
yourComboBox.ItemsSource = Enum.GetValues(typeof(EffectStyle)).Cast<EffectStyle>();
XAMLでバインドする必要がある場合は、ObjectDataProvider
を使用して、バインドソースとして使用可能なオブジェクトを作成する必要があります。
<Window x:Class="YourNamespace.MainWindow"
xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
xmlns:System="clr-namespace:System;Assembly=mscorlib"
xmlns:StyleAlias="clr-namespace:Motion.VideoEffects">
<Window.Resources>
<ObjectDataProvider x:Key="dataFromEnum" MethodName="GetValues"
ObjectType="{x:Type System:Enum}">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="StyleAlias:EffectStyle"/>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</Window.Resources>
<Grid>
<ComboBox ItemsSource="{Binding Source={StaticResource dataFromEnum}}"
SelectedItem="{Binding Path=CurrentEffectStyle}" />
</Grid>
</Window>
次のコードに注目してください:
xmlns:System="clr-namespace:System;Assembly=mscorlib"
xmlns:StyleAlias="clr-namespace:Motion.VideoEffects"
MSDN で読むことができる名前空間とアセンブリのマッピング方法をガイドします。
バインドしているすべてのオブジェクトがViewModel
で定義されるのが好きなので、可能な限りxamlで<ObjectDataProvider>
を使用しないようにします。
私のソリューションでは、ビューで定義されたデータもコードビハインドも使用していません。 DataBinding、再利用可能なValueConverter、Enum型の説明のコレクションを取得するメソッド、およびバインドするViewModelの単一のプロパティのみ。
Enum
をComboBox
にバインドする場合、表示するテキストはEnum
の値と決して一致しないため、[Description()]
属性を使用して、実際にComboBox
に表示するテキストを指定します。ゲームにキャラクタークラスの列挙がある場合、次のようになります。
public enum PlayerClass
{
// add an optional blank value for default/no selection
[Description("")]
NOT_SET = 0,
[Description("Shadow Knight")]
SHADOW_KNIGHT,
...
}
最初に、列挙型を処理するいくつかのメソッドを持つヘルパークラスを作成しました。 1つのメソッドは特定の値の説明を取得し、もう1つのメソッドは型のすべての値とその説明を取得します。
public static class EnumHelper
{
public static string Description(this Enum value)
{
var attributes = value.GetType().GetField(value.ToString()).GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attributes.Any())
return (attributes.First() as DescriptionAttribute).Description;
// If no description is found, the least we can do is replace underscores with spaces
// You can add your own custom default formatting logic here
TextInfo ti = CultureInfo.CurrentCulture.TextInfo;
return ti.ToTitleCase(ti.ToLower(value.ToString().Replace("_", " ")));
}
public static IEnumerable<ValueDescription> GetAllValuesAndDescriptions(Type t)
{
if (!t.IsEnum)
throw new ArgumentException($"{nameof(t)} must be an enum type");
return Enum.GetValues(t).Cast<Enum>().Select((e) => new ValueDescription() { Value = e, Description = e.Description() }).ToList();
}
}
次に、ValueConverter
を作成します。 MarkupExtension
から継承すると、XAMLでの使用が簡単になるため、リソースとして宣言する必要がなくなります。
[ValueConversion(typeof(Enum), typeof(IEnumerable<ValueDescription>))]
public class EnumToCollectionConverter : MarkupExtension, IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return EnumHelper.GetAllValuesAndDescriptions(value.GetType());
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
ViewModel
には、コンボボックスのView
とSelectedValue
の両方に対してItemsSource
がバインドできるプロパティが1つだけ必要です。
private PlayerClass playerClass;
public PlayerClass SelectedClass
{
get { return playerClass; }
set
{
if (playerClass != value)
{
playerClass = value;
OnPropertyChanged(nameof(SelectedClass));
}
}
}
最後に、ComboBox
ビューをバインドします(ValueConverter
バインディングでItemsSource
を使用)...
<ComboBox ItemsSource="{Binding Path=SelectedClass, Converter={x:EnumToCollectionConverter}, Mode=OneTime}"
SelectedValuePath="Value"
DisplayMemberPath="Description"
SelectedValue="{Binding Path=SelectedClass}" />
このソリューションを実装するには、EnumHelper
クラスとEnumToCollectionConverter
クラスをコピーするだけです。 any列挙型で動作します。また、ここには含めませんでしたが、ValueDescription
クラスは、2つのパブリックオブジェクトプロパティを持つ単純なクラスです。1つはValue
、もう1つはDescription
と呼ばれます。自分で作成することも、コードを変更してTuple<object, object>
またはKeyValuePair<object, object>
を使用することもできます
MarkupExtensionを使用した別のソリューションを使用しました。
アイテムのソースを提供するクラスを作成しました:
public class EnumToItemsSource : MarkupExtension
{
private readonly Type _type;
public EnumToItemsSource(Type type)
{
_type = type;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return Enum.GetValues(_type)
.Cast<object>()
.Select(e => new { Value = (int)e, DisplayName = e.ToString() });
}
}
それがほとんどすべてです... XAMLで使用します。
<ComboBox DisplayMemberPath="DisplayName"
ItemsSource="{persons:EnumToItemsSource {x:Type enums:States}}"
SelectedValue="{Binding Path=WhereEverYouWant}"
SelectedValuePath="Value" />
「enums:States」を列挙型に変更します
ObjectDataProviderを使用します。
<ObjectDataProvider x:Key="enumValues"
MethodName="GetValues" ObjectType="{x:Type System:Enum}">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="local:ExampleEnum"/>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
次に、静的リソースにバインドします。
ItemsSource="{Binding Source={StaticResource enumValues}}"
この記事 に基づく
ニックの答えは本当に助けになりましたが、余分なクラスValueDescriptionを避けるために、少し調整できることに気付きました。フレームワークにはすでにKeyValuePairクラスが存在することを思い出したので、代わりにこれを使用できます。
コードはわずかに変更されます。
public static IEnumerable<KeyValuePair<string, string>> GetAllValuesAndDescriptions<TEnum>() where TEnum : struct, IConvertible, IComparable, IFormattable
{
if (!typeof(TEnum).IsEnum)
{
throw new ArgumentException("TEnum must be an Enumeration type");
}
return from e in Enum.GetValues(typeof(TEnum)).Cast<Enum>()
select new KeyValuePair<string, string>(e.ToString(), e.Description());
}
public IEnumerable<KeyValuePair<string, string>> PlayerClassList
{
get
{
return EnumHelper.GetAllValuesAndDescriptions<PlayerClass>();
}
}
そして最後に、XAML:
<ComboBox ItemSource="{Binding Path=PlayerClassList}"
DisplayMemberPath="Value"
SelectedValuePath="Key"
SelectedValue="{Binding Path=SelectedClass}" />
これが他の人に役立つことを願っています。
System.Enum.GetValues() を呼び出して、アイテムに必要な列挙型のType
を渡すことで作成できる列挙型の値の配列を作成する必要があります。
ItemsSource
プロパティにこれを指定する場合、列挙型のすべての値を設定する必要があります。 SelectedItem
をEffectStyle
にバインドすることをお勧めします(同じ列挙型のプロパティであり、現在の値が含まれていると仮定します)。
上記の投稿はすべて、簡単なトリックを逃しています。 SelectedValueのバインディングから、XAMLマークアップが適切になるように、ItemsSourceを自動的に設定する方法を見つけることができます。
<Controls:EnumComboBox SelectedValue="{Binding Fool}"/>
たとえば、私のViewModelには
public enum FoolEnum
{
AAA, BBB, CCC, DDD
};
FoolEnum _Fool;
public FoolEnum Fool
{
get { return _Fool; }
set { ValidateRaiseAndSetIfChanged(ref _Fool, value); }
}
ValidateRaiseAndSetIfChangedは私のINPCフックです。異なる場合があります。
EnumComboBoxの実装は次のとおりですが、最初に列挙文字列と値を取得するには少しヘルパーが必要です
public static List<Tuple<object, string, int>> EnumToList(Type t)
{
return Enum
.GetValues(t)
.Cast<object>()
.Select(x=>Tuple.Create(x, x.ToString(), (int)x))
.ToList();
}
およびメインクラス
using ReactiveUI;
using ReactiveUI.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
using System.Windows.Documents;
namespace My.Controls
{
public class EnumComboBox : System.Windows.Controls.ComboBox
{
static EnumComboBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(EnumComboBox), new FrameworkPropertyMetadata(typeof(EnumComboBox)));
}
protected override void OnInitialized( EventArgs e )
{
base.OnInitialized(e);
this.WhenAnyValue(p => p.SelectedValue)
.Where(p => p != null)
.Select(o => o.GetType())
.Where(t => t.IsEnum)
.DistinctUntilChanged()
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(FillItems);
}
private void FillItems(Type enumType)
{
List<KeyValuePair<object, string>> values = new List<KeyValuePair<object,string>>();
foreach (var idx in EnumUtils.EnumToList(enumType))
{
values.Add(new KeyValuePair<object, string>(idx.Item1, idx.Item2));
}
this.ItemsSource = values.Select(o=>o.Key.ToString()).ToList();
UpdateLayout();
this.ItemsSource = values;
this.DisplayMemberPath = "Value";
this.SelectedValuePath = "Key";
}
}
}
また、Generic.XAMLでスタイルを正しく設定する必要があります。そうしないと、ボックスは何もレンダリングせず、髪を引き出します。
<Style TargetType="{x:Type local:EnumComboBox}" BasedOn="{StaticResource {x:Type ComboBox}}">
</Style>
それがそれです。これは明らかにi18nをサポートするように拡張できますが、投稿が長くなります。
この質問には多くの優れた答えがあり、謙虚に提出します。私のほうがややシンプルでエレガントだと思います。値コンバーターのみが必要です。
列挙型を与え...
public enum ImageFormat
{
[Description("Windows Bitmap")]
BMP,
[Description("Graphics Interchange Format")]
GIF,
[Description("Joint Photographic Experts Group Format")]
JPG,
[Description("Portable Network Graphics Format")]
PNG,
[Description("Tagged Image Format")]
TIFF,
[Description("Windows Media Photo Format")]
WDP
}
および値コンバータ...
public class ImageFormatValueConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is ImageFormat format)
{
return GetString(format);
}
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is string s)
{
return Enum.Parse(typeof(ImageFormat), s.Substring(0, s.IndexOf(':')));
}
return null;
}
public string[] Strings => GetStrings();
public static string GetString(ImageFormat format)
{
return format.ToString() + ": " + GetDescription(format);
}
public static string GetDescription(ImageFormat format)
{
return format.GetType().GetMember(format.ToString())[0].GetCustomAttribute<DescriptionAttribute>().Description;
}
public static string[] GetStrings()
{
List<string> list = new List<string>();
foreach (ImageFormat format in Enum.GetValues(typeof(ImageFormat)))
{
list.Add(GetString(format));
}
return list.ToArray();
}
}
リソース...
<local:ImageFormatValueConverter x:Key="ImageFormatValueConverter"/>
XAML宣言...
<ComboBox Grid.Row="9" ItemsSource="{Binding Source={StaticResource ImageFormatValueConverter}, Path=Strings}"
SelectedItem="{Binding Format, Converter={StaticResource ImageFormatValueConverter}}"/>
モデルを表示...
private ImageFormat _imageFormat = ImageFormat.JPG;
public ImageFormat Format
{
get => _imageFormat;
set
{
if (_imageFormat != value)
{
_imageFormat = value;
OnPropertyChanged();
}
}
}
結果のコンボボックス...
public class EnumItemsConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!value.GetType().IsEnum)
return false;
var enumName = value.GetType();
var obj = Enum.Parse(enumName, value.ToString());
return System.Convert.ToInt32(obj);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return Enum.ToObject(targetType, System.Convert.ToInt32(value));
}
}
オブジェクトモデルプロパティを列挙型に直接バインドする場合は、RogersとGregの答えをそのようなEnum値コンバーターで拡張する必要があります。
ユニバーサルアプリの動作は少し異なるようです。フル機能のXAMLのすべての機能を備えているわけではありません。私のために働いたのは:
楽しみのために、これを支援するために小さなテンプレートクラスを作成し、それを MSDNサンプルページ に公開しました。余分なビットにより、オプションで列挙型の名前をオーバーライドし、列挙型の一部を非表示にすることができます。私のコードは、ニック(上記)のようにひどく見えます。
列挙型のint表現ではなく、ViewModelの実際の列挙型プロパティにバインドしている場合、注意が必要です。上記のすべての例で予想されるint値ではなく、文字列表現にバインドする必要があることがわかりました。
ViewModelでバインドするプロパティに単純なテキストボックスをバインドすることで、これが当てはまるかどうかを確認できます。テキストが表示されている場合は、文字列にバインドします。数値が表示されている場合は、値にバインドします。注:通常はエラーになるDisplayを2回使用しましたが、それが唯一の動作方法です。
<ComboBox SelectedValue="{Binding ElementMap.EdiDataType, Mode=TwoWay}"
DisplayMemberPath="Display"
SelectedValuePath="Display"
ItemsSource="{Binding Source={core:EnumToItemsSource {x:Type edi:EdiDataType}}}" />
グレッグ
シンプルで明確な説明: http://brianlagunas.com/a-better-way-to-data-bind-enums-in-wpf/
xmlns:local="clr-namespace:BindingEnums"
xmlns:sys="clr-namespace:System;Assembly=mscorlib"
...
<Window.Resources>
<ObjectDataProvider x:Key="dataFromEnum" MethodName="GetValues"
ObjectType="{x:Type sys:Enum}">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="local:Status"/>
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</Window.Resources>
...
<Grid>
<ComboBox HorizontalAlignment="Center" VerticalAlignment="Center" MinWidth="150"
ItemsSource="{Binding Source={StaticResource dataFromEnum}}"/>
</Grid>
tom.maruska's answer が好きでしたが、実行時にテンプレートが遭遇する可能性のある列挙型をサポートする必要がありました。そのために、バインディングを使用して、マークアップ拡張機能に型を指定する必要がありました。 nicolay.anykienkoの この回答 で作業することができ、非常に柔軟なマークアップ拡張機能を思い付きました。次のように消費されます。
<ComboBox SelectedValue="{Binding MyEnumProperty}"
SelectedValuePath="Value"
ItemsSource="{local:EnumToObjectArray SourceEnum={Binding MyEnumProperty}}"
DisplayMemberPath="DisplayName" />
上記でマッシュアップされたマークアップ拡張機能のソース:
class EnumToObjectArray : MarkupExtension
{
public BindingBase SourceEnum { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
IProvideValueTarget target = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
DependencyObject targetObject;
DependencyProperty targetProperty;
if (target != null && target.TargetObject is DependencyObject && target.TargetProperty is DependencyProperty)
{
targetObject = (DependencyObject)target.TargetObject;
targetProperty = (DependencyProperty)target.TargetProperty;
}
else
{
return this;
}
BindingOperations.SetBinding(targetObject, EnumToObjectArray.SourceEnumBindingSinkProperty, SourceEnum);
var type = targetObject.GetValue(SourceEnumBindingSinkProperty).GetType();
if (type.BaseType != typeof(System.Enum)) return this;
return Enum.GetValues(type)
.Cast<Enum>()
.Select(e => new { Value=e, Name = e.ToString(), DisplayName = Description(e) });
}
private static DependencyProperty SourceEnumBindingSinkProperty = DependencyProperty.RegisterAttached("SourceEnumBindingSink", typeof(Enum)
, typeof(EnumToObjectArray), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits));
/// <summary>
/// Extension method which returns the string specified in the Description attribute, if any. Oherwise, name is returned.
/// </summary>
/// <param name="value">The enum value.</param>
/// <returns></returns>
public static string Description(Enum value)
{
var attrs = value.GetType().GetField(value.ToString()).GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attrs.Any())
return (attrs.First() as DescriptionAttribute).Description;
//Fallback
return value.ToString().Replace("_", " ");
}
}
私はコメントを追加しています(VBでは、残念ながら、ハートビートで概念をC#に簡単に複製できます)。これを参照するだけで、回答が複雑すぎて好きではなかったためです。これは難しいことではないはずです。
そこで、私はもっと簡単な方法を思いつきました。列挙子を辞書にバインドします。その辞書をComboboxにバインドします。
私のコンボボックス:
<ComboBox x:Name="cmbRole" VerticalAlignment="Stretch" IsEditable="False" Padding="2"
Margin="0" FontSize="11" HorizontalAlignment="Stretch" TabIndex="104"
SelectedValuePath="Key" DisplayMemberPath="Value" />
私のコードビハインド。うまくいけば、これは他の誰かを助けます。
Dim tDict As New Dictionary(Of Integer, String)
Dim types = [Enum].GetValues(GetType(Helper.Enumerators.AllowedType))
For Each x As Helper.Enumerators.AllowedType In types
Dim z = x.ToString()
Dim y = CInt(x)
tDict.Add(y, z)
Next
cmbRole.ClearValue(ItemsControl.ItemsSourceProperty)
cmbRole.ItemsSource = tDict
ニックの解決策はより単純化することができ、空想なしで、単一のコンバーターのみが必要です。
[ValueConversion(typeof(Enum), typeof(IEnumerable<Enum>))]
public class EnumToCollectionConverter : MarkupExtension, IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var r = Enum.GetValues(value.GetType());
return r;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return null;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
}
次に、コンボボックスを表示する場所でこれを使用します。
<ComboBox ItemsSource="{Binding PagePosition, Converter={converter:EnumToCollectionConverter}, Mode=OneTime}" SelectedItem="{Binding PagePosition}" />
ReactiveUI
を使用して、次の代替ソリューションを作成しました。エレガントなオールインワンのソリューションではありませんが、少なくとも読みやすいと思います。
私の場合、enum
のリストをコントロールにバインドするのはまれなケースなので、コードベース全体でソリューションをスケーリングする必要はありません。ただし、EffectStyleLookup.Item
をObject
に変更することにより、コードをより汎用的にすることができます。コードでテストしましたが、他の変更は必要ありません。つまり、1つのヘルパークラスを任意のenum
リストに適用できます。読みやすさは低下しますが、ReactiveList<EnumLookupHelper>
には大きなリングはありません。
次のヘルパークラスを使用します。
public class EffectStyleLookup
{
public EffectStyle Item { get; set; }
public string Display { get; set; }
}
ViewModelで、列挙型のリストを変換し、プロパティとして公開します。
public ViewModel : ReactiveObject
{
private ReactiveList<EffectStyleLookup> _effectStyles;
public ReactiveList<EffectStyleLookup> EffectStyles
{
get { return _effectStyles; }
set { this.RaiseAndSetIfChanged(ref _effectStyles, value); }
}
// See below for more on this
private EffectStyle _selectedEffectStyle;
public EffectStyle SelectedEffectStyle
{
get { return _selectedEffectStyle; }
set { this.RaiseAndSetIfChanged(ref _selectedEffectStyle, value); }
}
public ViewModel()
{
// Convert a list of enums into a ReactiveList
var list = (IList<EffectStyle>)Enum.GetValues(typeof(EffectStyle))
.Select( x => new EffectStyleLookup() {
Item = x,
Display = x.ToString()
});
EffectStyles = new ReactiveList<EffectStyle>( list );
}
}
ComboBox
で、SelectedValuePath
プロパティを使用して、元のenum
値にバインドします。
<ComboBox Name="EffectStyle" DisplayMemberPath="Display" SelectedValuePath="Item" />
ビューでは、これにより、元のenum
をViewModelのSelectedEffectStyle
にバインドできますが、ComboBox
のToString()
値を表示できます。
this.WhenActivated( d =>
{
d( this.OneWayBind(ViewModel, vm => vm.EffectStyles, v => v.EffectStyle.ItemsSource) );
d( this.Bind(ViewModel, vm => vm.SelectedEffectStyle, v => v.EffectStyle.SelectedValue) );
});