Как я могу заполнить поле со списком WPF в XAML всеми элементами из данного перечисления?

Скажем, у меня есть перечисление с четырьмя значениями:

public enum CompassHeading
{
    North,
    South,
    East,
    West
}

Какой XAML потребуется для заполнения ComboBox этими элементами?

<ComboBox ItemsSource="{Binding WhatGoesHere???}" />

В идеале мне не нужно было бы настраивать для этого код C #.


person Drew Noakes    schedule 11.02.2009    source источник
comment
Я только что прочитал недавний пост Эрика Берка о классе Swing JComboBox, который делает это, и подумал: «Эй, клянусь, я видел ТАК вопрос по этому поводу ... Я был близок, но вам нужен WPF, а не Java / Swing». В любом случае, вот он для потомков: stuffthathappens.com / blog / 2009/02/10 /   -  person JMD    schedule 12.02.2009


Ответы (6)


Для этого вы можете использовать ObjectDataProvider:

<ObjectDataProvider MethodName="GetValues" 
    ObjectType="{x:Type sys:Enum}" x:Key="odp">
    <ObjectDataProvider.MethodParameters>
        <x:Type TypeName="local:CompassHeading"/>
    </ObjectDataProvider.MethodParameters>
</ObjectDataProvider>

<ComboBox ItemsSource="{Binding Source={StaticResource odp}}" />

Я нашел решение здесь:

http://bea.stollnitz.com/blog/?p=28

person casperOne    schedule 11.02.2009
comment
Спасибо, это решение, похоже, также хорошо работает с привязкой TwoWay. Обратите внимание, что IsSynchronizedWithCurrentItem = true - отвлекающий маневр для этого вопроса (вы можете удалить его для ясности другим посетителям). - person Drew Noakes; 12.02.2009
comment
Это не поддерживает локализацию. - person Guge; 03.09.2010
comment
@Guge: Нет, но перечисления также не поддерживают локализацию. Вам нужно будет создать отдельное перечисление для каждой местности или прикрепить атрибут, который может выдавать вам значения локализации (он будет использовать какой-либо поиск по ключу), и в этом случае вопрос и ответ не будут больше не применяются. - person casperOne; 05.09.2010
comment
@casperOne: Совершенно верно! Я не думаю о перечислениях как о части пользовательского интерфейса, а просто как о способе дезинфекции использования целых чисел для символического использования в коде. От того, чтобы сделать перечисления видимыми для пользователей, пахнет плохим разделением слоев. - person Guge; 05.09.2010
comment
@Guge: Возможно, но это больше проблема для человека, задавшего вопрос, не так ли? Вопрос был очень конкретным, причины его не вызывали беспокойства, когда я на него отвечал. - person casperOne; 05.09.2010
comment
@Guge - В моем случае я создавал собственное программное обеспечение, в котором все говорили по-английски. Локализация не применялась. - person Drew Noakes; 06.10.2010
comment
@Drew: Приятно видеть, что вы согласны со мной в том, что это решение не универсально. - person Guge; 07.10.2010

Я думаю, что использование ObjectDataProvider для этого действительно утомительно ... У меня есть более краткое предложение (да, я знаю, немного поздно ...), используя расширение разметки:

<ComboBox ItemsSource="{local:EnumValues local:EmployeeType}"/>

Вот код расширения разметки:

[MarkupExtensionReturnType(typeof(object[]))]
public class EnumValuesExtension : MarkupExtension
{
    public EnumValuesExtension()
    {
    }

    public EnumValuesExtension(Type enumType)
    {
        this.EnumType = enumType;
    }

    [ConstructorArgument("enumType")]
    public Type EnumType { get; set; }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        if (this.EnumType == null)
            throw new ArgumentException("The enum type is not set");
        return Enum.GetValues(this.EnumType);
    }
}
person Thomas Levesque    schedule 11.05.2009
comment
Я придумал класс, почти точно соответствующий твоему, не видя твоего. Моя добавляет еще одну проверку: enumType.IsEnum перед оператором return. - person Jake Berger; 26.08.2011

Здесь подробно пример того, как привязать к перечислениям в WPF

Предположим, у вас есть следующее перечисление

public enum EmployeeType    
{
    Manager,
    Worker
}

Затем вы можете привязать код позади

typeComboBox.ItemsSource = Enum.GetValues(typeof(EmployeeType));

или используйте ObjectDataProvider

<ObjectDataProvider MethodName="GetValues" ObjectType="{x:Type sys:Enum}" x:Key="sysEnum">
    <ObjectDataProvider.MethodParameters>
        <x:Type TypeName="local:EmployeeType" />
    </ObjectDataProvider.MethodParameters>
</ObjectDataProvider>

и теперь вы можете привязать разметку

<ComboBox ItemsSource="{Binding Source={StaticResource sysEnum}}" />

Также проверьте: Привязка свойства перечисления к ComboBox в WPF

person rudigrobler    schedule 12.02.2009

Для пошагового ознакомления с альтернативами и производными техник попробуйте эту веб-страницу:

Отсутствующий .NET # 7: отображение перечислений в WPF

В этой статье также демонстрируется метод переопределения представления определенных значений. Хорошее чтение с большим количеством примеров кода.

person Drew Noakes    schedule 12.02.2009

Это может быть похоже на ругань в церкви, но я хотел бы явно объявить каждый ComboBoxItem в XAML по следующим причинам:

  • Если мне нужна локализация, я могу передать XAML переводчику, а код оставить себе.
  • Если у меня есть значения перечисления, которые не подходят для данного ComboBox, мне не нужно их показывать.
  • Порядок перечислений определяется в XAML, не обязательно в коде.
  • Количество значений перечисления на выбор обычно не очень велико. Я бы счел Enums с сотнями значений запахом кода.
  • Если мне нужна графика или другое украшение для некоторых элементов ComboBoxItem, было бы проще просто поместить его в XAML, где он принадлежит, вместо каких-то сложных шаблонов / триггеров.
  • Держать его просто глупо

C # Пример кода:

    public enum number { one, two, three };

public partial class MainWindow : Window, INotifyPropertyChanged
{
    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = this;
    }

    private number _number = number.one;
    public number Number
    {
        get { return _number; }
        set {
            if (_number == value)
                return;
            _number = value;
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs("Number"));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

Код XAML:

<Window x:Class="WpfApplication6.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="480" Width="677">
<Grid>
    <ComboBox SelectedValue="{Binding Number}" SelectedValuePath="Tag">
        <ComboBoxItem Content="En" Tag="One"/>
        <ComboBoxItem Content="To" Tag="Two"/>
        <ComboBoxItem Content="Tre" Tag="Three"/>
    </ComboBox>
</Grid>

As you can see, the XAML has been localized to Norwegian, without any need for changes in the C# code.

person Guge    schedule 03.09.2010
comment
И вы получаете поддержку во время разработки прямо из коробки - person surfen; 13.05.2011

Третье решение:

Это немного больше работы заранее, лучше в долгосрочной перспективе, если вы привязываетесь к множеству Enums. Используйте Converter, который принимает тип перечисления в качестве параметра и преобразует его в массив строк в качестве вывода.

В VB.NET:

Public Class EnumToNamesConverter
    Implements IValueConverter

    Public Function Convert(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.Convert
        Return [Enum].GetNames(DirectCast(value, Type))
    End Function

    Public Function ConvertBack(ByVal value As Object, ByVal targetType As System.Type, ByVal parameter As Object, ByVal culture As System.Globalization.CultureInfo) As Object Implements System.Windows.Data.IValueConverter.ConvertBack
        Throw New NotImplementedException()
    End Function
End Class

Or in C#:

public sealed class EnumToNamesConverter : IValueConverter
{
  object IValueConverter.Convert(object value, Type targetType, object parameter, CultureInfo culture)
  {
    return Enum.GetNames(value.GetType());
  }

  object IValueConverter.ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
  {
    throw New NotSupportedException()
  }
}

Затем в вашем Application.xaml добавьте глобальный ресурс для доступа к этому конвертеру:

<local:EnumToNamesConverter x:Key="EnumToNamesConverter" />

Наконец, используйте конвертер на любых страницах XAML, где вам нужны значения любого Enum ...

<ComboBox ItemsSource="{Binding
                        Source={x:Type local:CompassHeading},
                        Converter={StaticResource EnumToNamesConverter}}" />
person Mark    schedule 12.02.2009
comment
Привет, maranite2, мне нравится внешний вид этого решения, но я не мог заставить его работать с привязкой TwoWay. Привязка работает от элемента управления к данным (когда я сохраняю), но не работает от данных к элементу управления (поле со списком изначально пустое, где должно быть выбрано значение). - person Drew Noakes; 12.02.2009