Làm cách nào để liên kết enum với điều khiển combobox trong WPF?


182

Tôi đang cố gắng tìm một ví dụ đơn giản trong đó các enum được hiển thị như là. Tất cả các ví dụ tôi đã thấy cố gắng thêm các chuỗi hiển thị đẹp mắt nhưng tôi không muốn sự phức tạp đó.

Về cơ bản tôi có một lớp chứa tất cả các thuộc tính mà tôi liên kết, bằng cách trước tiên đặt DataContext cho lớp này, sau đó chỉ định liên kết như thế này trong tệp xaml:

<ComboBox ItemsSource="{Binding Path=EffectStyle}"/>

Nhưng điều này không hiển thị các giá trị enum trong các ComboBoxmục.


9
Đây là những gì bạn đang tìm kiếm: WPF ObjectDataProvider - Binding Enum cho ComboBox Bạn cũng có thể tải xuống ví dụ mã nguồn hoàn chỉnh từ đó.

Câu trả lời hay nhất theo ý kiến ​​của tôi là trong: stackoverflow.com/questions/58743/ từ
gimpy

Câu trả lời:


307

Bạn có thể làm điều đó từ mã bằng cách đặt đoạn mã sau vào Loadedtrình xử lý sự kiện Window , ví dụ:

yourComboBox.ItemsSource = Enum.GetValues(typeof(EffectStyle)).Cast<EffectStyle>();

Nếu bạn cần liên kết nó trong XAML, bạn cần sử dụng ObjectDataProviderđể tạo đối tượng có sẵn dưới dạng nguồn ràng buộc:

<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>

Thu hút sự chú ý vào mã tiếp theo:

xmlns:System="clr-namespace:System;assembly=mscorlib"
xmlns:StyleAlias="clr-namespace:Motion.VideoEffects"

Hướng dẫn cách ánh xạ không gian tên và lắp ráp bạn có thể đọc trên MSDN .


1
Ví dụ thử nghiệm từ liên kết đầu tiên, hoạt động tốt. Xem thêm mã và nhận xét trong câu trả lời của tôi.
Kyrylo M

1
Tìm thấy sự cố của bạn trên các diễn đàn MSDN ( social.msdn.microsoft.com/Forums/en/wpf/thread/ mẹo ). Cố gắng làm sạch và xây dựng lại dự án. Có lẽ bạn nên hỏi vấn đề đó ở đây về một câu hỏi khác. Đây là điều duy nhất tôi có thể khuyên ... Dù sao, ví dụ hiển thị là chính xác.
Kyrylo M

1
Cảm ơn, điều đó thật kỳ quái nhưng tôi đã thấy những thứ tương tự với sự điên rồ của wpf. Sẽ làm và cho bạn biết. Btw đây là vấn đề tương tự được mô tả ở đây: social.msdn.microsoft.com/Forums/en-US/wpf/thread/ mẹo
Joan Venge

2
Bạn cần thêm tham chiếu đến nó và thêm xmlns:DllAlias="clr-namespace:NamespaceInsideDll; assembly=DllAssemblyName"vào XAML để sử dụng nó. Dưới đây là hướng dẫn: msdn.microsoft.com/en-us/l Library / ms747086.aspx
Kyrylo M

4
Bạn có thể sử dụng các công cụ như ReSharper. Nó phân tích tất cả các hội đồng tham chiếu và đưa ra gợi ý những gì cần bao gồm. Không cần phải viết - chỉ cần chọn từ các tùy chọn.
Kyrylo M

115

Tôi thích tất cả các đối tượng mà tôi ràng buộc được xác định trong tôi ViewModel, vì vậy tôi cố gắng tránh sử dụng <ObjectDataProvider>trong xaml khi có thể.

Giải pháp của tôi sử dụng không có dữ liệu được xác định trong Chế độ xem và không có mã phía sau. Chỉ một DataBinding, ValueConverter có thể sử dụng lại, một phương thức để có được một bộ mô tả cho bất kỳ loại Enum nào và một thuộc tính duy nhất trong ViewModel để liên kết.

Khi tôi muốn để ràng buộc một Enumđến một ComboBoxvăn bản tôi muốn hiển thị không bao giờ phù hợp với các giá trị của Enum, vì vậy tôi sử dụng các [Description()]thuộc tính để cho nó văn bản mà tôi thực sự muốn nhìn thấy trong ComboBox. Nếu tôi có một số ngày trong tuần, nó sẽ trông giống như thế này:

public enum DayOfWeek
{
  // add an optional blank value for default/no selection
  [Description("")]
  NOT_SET = 0,
  [Description("Sunday")]
  SUNDAY,
  [Description("Monday")]
  MONDAY,
  ...
}

Đầu tiên tôi tạo lớp trợ giúp với một vài phương thức để đối phó với enums. Một phương thức nhận được một mô tả cho một giá trị cụ thể, phương thức kia nhận tất cả các giá trị và mô tả của chúng cho một loại.

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();
  }
}

Tiếp theo, chúng tôi tạo ra một ValueConverter. Kế thừa từ MarkupExtensionviệc sử dụng dễ dàng hơn trong XAML vì vậy chúng tôi không phải khai báo nó là tài nguyên.

[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;
  }
}

Tôi ViewModelchỉ cần 1 thuộc tính mà tôi Viewcó thể liên kết cho cả SelectedValueItemsSourcecủa hộp tổ hợp:

private DayOfWeek dayOfWeek;

public DayOfWeek SelectedDay
{
  get { return dayOfWeek; }
  set
  {
    if (dayOfWeek != value)
    {
      dayOfWeek = value;
      OnPropertyChanged(nameof(SelectedDay));
    }
  }
}

Và cuối cùng để ràng buộc ComboBoxchế độ xem (sử dụng ValueConvertertrong ItemsSourceliên kết) ...

<ComboBox ItemsSource="{Binding Path=SelectedDay, Converter={x:EnumToCollectionConverter}, Mode=OneTime}"
          SelectedValuePath="Value"
          DisplayMemberPath="Description"
          SelectedValue="{Binding Path=SelectedDay}" />

Để thực hiện giải pháp này, bạn chỉ cần sao chép EnumHelperlớp và lớp của tôi EnumToCollectionConverter. Họ sẽ làm việc với bất kỳ enum. Ngoài ra, tôi không bao gồm nó ở đây, nhưng ValueDescriptionlớp chỉ là một lớp đơn giản với 2 thuộc tính đối tượng công khai, một được gọi Value, một được gọi Description. Bạn có thể tự tạo hoặc bạn có thể thay đổi mã để sử dụng một Tuple<object, object>hoặcKeyValuePair<object, object>


9
Để thực hiện công việc này, tôi đã phải tạo một ValueDescriptionlớp có các thuộc tính công khai cho ValueDescription
Perchik

4
Có, bạn cũng có thể thay đổi mã này để sử dụng một Tuple<T1, T2>hoặc KeyValuePair<TKey, TValue>thay vì ValueDescriptionlớp và sau đó bạn sẽ không phải tạo mã của riêng mình.
Nick

Tôi cần triển khai OnPropertyChanged (hoặc tương đương) cho cả hai thuộc tính ViewModel, không chỉ là ChọnClass.
Sẽ

Bạn không cần phải triển khai OnPropertyChanged cho thuộc tính trả về danh sách. Danh sách được tạo từ các giá trị trong Enum. Nó sẽ không bao giờ thay đổi trong thời gian chạy, và khi nó không bao giờ thay đổi, nó không bao giờ cần thông báo cho bất cứ ai rằng nó đã thay đổi. Ngoài ra, với phiên bản cập nhật, thuộc tính danh sách thậm chí không cần thiết.
Nick

ItemSource và chọnValue của tài sản kết hợp như thế nào. Không phải ItemSource cần phải là một danh sách? Ồ, tôi hiểu rồi, đó là EnumHelper tạo ra một danh sách các đối tượng. điều này thực sự làm cho ViewModel của tôi đơn giản hơn vì tôi không phải duy trì một danh sách các đối tượng riêng biệt để điền vào ItemSource.
Rabbi tàng hình

46

Tôi đã sử dụng một giải pháp khác bằng MarkupExtension.

  1. Tôi đã tạo lớp cung cấp nguồn vật phẩm:

    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() });
        }
    }
  2. Đó là gần như tất cả ... Bây giờ sử dụng nó trong XAML:

        <ComboBox DisplayMemberPath="DisplayName"
              ItemsSource="{persons:EnumToItemsSource {x:Type enums:States}}"
              SelectedValue="{Binding Path=WhereEverYouWant}"
              SelectedValuePath="Value" />
  3. Thay đổi 'enums: States' thành enum của bạn


1
@Nick: Câu trả lời được chấp nhận là tham chiếu enum (hoặc mô hình như bạn đã nói) trong xaml. Giải pháp của bạn là tạo 2 thuộc tính và trường sao lưu trong mô hình xem mà tôi không thích (quy tắc DRY). Và tất nhiên, bạn không phải sử dụng e.ToString()cho tên hiển thị. Bạn có thể sử dụng trình dịch riêng, trình phân tích cú pháp thuộc tính descrtiption, bất cứ điều gì.
tom.maruska

2
@ tom.maruska Tôi không cố gắng đi vào câu trả lời của tôi so với câu trả lời của bạn, nhưng vì bạn đã đưa nó lên, có 2 thuộc tính không vi phạm quy tắc DRY khi chúng là 2 thuộc tính riêng biệt phục vụ các mục đích khác nhau. Và câu trả lời của bạn cũng sẽ yêu cầu thêm một thuộc tính (thậm chí bạn đã tự gọi ra thuộc tính này {Binding Path=WhereEverYouWant}) và nếu bạn muốn nó hỗ trợ ràng buộc 2 chiều, bạn cũng sẽ có một trường sao lưu cho nó. Vì vậy, bạn không thay thế 2 thuộc tính và 1 trường sao lưu bằng cách này, bạn chỉ thay thế 1 thuộc tính chỉ đọc một dòng.
Nick

@Nick Có, bạn đúng về tài sản và lĩnh vực hỗ trợ đó :)
tom.maruska

24

Sử dụng ObjectDataProvider:

<ObjectDataProvider x:Key="enumValues"
   MethodName="GetValues" ObjectType="{x:Type System:Enum}">
      <ObjectDataProvider.MethodParameters>
           <x:Type TypeName="local:ExampleEnum"/>
      </ObjectDataProvider.MethodParameters>
 </ObjectDataProvider>

và sau đó liên kết với tài nguyên tĩnh:

ItemsSource="{Binding Source={StaticResource enumValues}}"

dựa trên bài viết này


4
Giải pháp hoàn hảo đơn giản. Không gian tên cho hệ thống như trong câu trả lời của kirmir:xmlns:System="clr-namespace:System;assembly=mscorlib"
Jonathan Twite

Hoạt động tốt trong các dự án WPF của Visual Studio 2017.
Sorush 26/03/19

10

Câu trả lời của Nick đã thực sự giúp tôi, nhưng tôi nhận ra nó có thể được điều chỉnh một chút, để tránh một lớp học thêm, ValueDes mô tả. Tôi nhớ rằng đã tồn tại một lớp KeyValuePair trong khung, vì vậy nó có thể được sử dụng thay thế.

Mã chỉ thay đổi một chút:

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>();
   }
}

và cuối cùng là XAML:

<ComboBox ItemSource="{Binding Path=PlayerClassList}"
          DisplayMemberPath="Value"
          SelectedValuePath="Key"
          SelectedValue="{Binding Path=SelectedClass}" />

Tôi hy vọng điều này hữu ích cho người khác.


Việc triển khai đầu tiên của tôi đã sử dụng một KeyValuePairnhưng cuối cùng tôi đã quyết định sử dụng một KeyValuePairđại diện cho thứ gì đó không phải là một cặp giá trị khóa chỉ để tránh viết một lớp đơn giản tầm thường không có ý nghĩa gì cả. Các ValueDescriptionlớp học là chỉ có 5 dòng, và 2 trong số đó là chỉ {}
Nick

8

Bạn sẽ cần tạo một mảng các giá trị trong enum, có thể được tạo bằng cách gọi System.Enum.GetValues ​​() , truyền cho nóType thành enum mà bạn muốn các mục của.

Nếu bạn chỉ định điều này cho thuộc ItemsSourcetính, thì nó sẽ được điền với tất cả các giá trị của enum. Bạn có thể muốn liên kết SelectedItemvới EffectStyle(giả sử đó là một thuộc tính của cùng enum và chứa giá trị hiện tại).


Cảm ơn, bạn có thể hiển thị phần đầu tiên trong mã không? Tôi không chắc chắn nơi lưu trữ các giá trị enum như mảng? Các tài sản enum nằm trong một lớp khác. Tôi có thể thực hiện bước GetValues ​​này trong xaml không?
Joan Venge

4

Tất cả các bài viết trên đã bỏ lỡ một thủ thuật đơn giản. Có thể từ ràng buộc của ChọnValue để tìm hiểu làm thế nào để điền vào mục TỰ ĐỘNG TỰ ĐỘNG để đánh dấu XAML của bạn.

<Controls:EnumComboBox SelectedValue="{Binding Fool}"/>

Ví dụ: trong ViewModel của tôi, tôi có

public enum FoolEnum
    {
        AAA, BBB, CCC, DDD

    };


    FoolEnum _Fool;
    public FoolEnum Fool
    {
        get { return _Fool; }
        set { ValidateRaiseAndSetIfChanged(ref _Fool, value); }
    }

ValidateRaiseAndSet IfChanged là hook INPC của tôi. Bạn có thể khác nhau.

Việc triển khai EnumComboBox như sau nhưng trước tiên tôi sẽ cần một người trợ giúp nhỏ để nhận các chuỗi và giá trị liệt kê của tôi

    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();
    }

và lớp chính (Lưu ý Tôi đang sử dụng ReactiveUI để nối các thay đổi thuộc tính thông qua KhiAny)

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";

        }
    }
}

Bạn cũng cần đặt kiểu chính xác trong Generic.XAML hoặc hộp của bạn sẽ không hiển thị bất cứ thứ gì và bạn sẽ kéo tóc ra.

<Style TargetType="{x:Type local:EnumComboBox}" BasedOn="{StaticResource {x:Type ComboBox}}">
</Style>

và đó là. Điều này rõ ràng có thể được mở rộng để hỗ trợ i18n nhưng sẽ làm cho bài đăng dài hơn.


3

Các ứng dụng phổ quát dường như hoạt động hơi khác một chút; nó không có tất cả sức mạnh của XAML đầy đủ tính năng. Những gì làm việc cho tôi là:

  1. Tôi đã tạo một danh sách các giá trị enum dưới dạng enum (không được chuyển đổi thành chuỗi hoặc thành số nguyên) và ràng buộc ComboBox ItemSource với điều đó
  2. Sau đó, tôi có thể liên kết Mục ComboBox được chọn vào tài sản công cộng của mình có loại là enum trong câu hỏi

Để giải trí, tôi đã tạo ra một lớp templated nhỏ để trợ giúp việc này và xuất bản nó lên các trang Mẫu MSDN . Các bit bổ sung cho phép tôi tùy ý ghi đè tên của enum và để tôi ẩn một số enum. Mã của tôi trông rất tệ như của Nick (ở trên), mà tôi ước tôi đã thấy trước đó.

Chạy mẫu;  nó bao gồm nhiều ràng buộc twoway với enum


3

Có rất nhiều câu trả lời tuyệt vời cho câu hỏi này và tôi khiêm tốn gửi câu hỏi của mình. Tôi thấy rằng của tôi là một phần đơn giản và thanh lịch hơn. Nó chỉ yêu cầu một công cụ chuyển đổi giá trị.

Cho một enum ...

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
}

và một công cụ chuyển đổi giá trị ...

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();
    }
}

tài nguyên...

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

Khai báo XAML ...

    <ComboBox Grid.Row="9" ItemsSource="{Binding Source={StaticResource ImageFormatValueConverter}, Path=Strings}"
              SelectedItem="{Binding Format, Converter={StaticResource ImageFormatValueConverter}}"/>

Xem mô hình ...

    private ImageFormat _imageFormat = ImageFormat.JPG;
    public ImageFormat Format
    {
        get => _imageFormat;
        set
        {
            if (_imageFormat != value)
            {
                _imageFormat = value;
                OnPropertyChanged();
            }
        }
    }

Combobox kết quả ...

ComboBox ràng buộc với enum


Đối với tôi, đây là giải pháp tốt nhất cho câu hỏi: đơn giản, dễ hiểu, đơn giản để thực hiện.
Thông tin

Vấn đề với giải pháp này là nó không thể định vị được.
Robin Davies

2
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));
    }
}

Bạn nên mở rộng câu trả lời của Rogers và Greg bằng loại công cụ chuyển đổi giá trị Enum như vậy, nếu bạn liên kết thẳng với các thuộc tính mô hình đối tượng enum.


1

Nếu bạn đang ràng buộc với một thuộc tính enum thực tế trên ViewModel của bạn, không phải là đại diện int của enum, mọi thứ sẽ trở nên khó khăn. Tôi thấy cần phải liên kết với biểu diễn chuỗi, KHÔNG phải giá trị int như mong đợi trong tất cả các ví dụ trên.

Bạn có thể biết nếu đây là trường hợp bằng cách ràng buộc một hộp văn bản đơn giản vào thuộc tính bạn muốn liên kết trên ViewModel của bạn. Nếu nó hiển thị văn bản, liên kết với chuỗi. Nếu nó hiển thị một số, liên kết với giá trị. Lưu ý Tôi đã sử dụng Hiển thị hai lần thường là một lỗi, nhưng đó là cách duy nhất để nó hoạt động.

<ComboBox SelectedValue="{Binding ElementMap.EdiDataType, Mode=TwoWay}"
                      DisplayMemberPath="Display"
                      SelectedValuePath="Display"
                      ItemsSource="{Binding Source={core:EnumToItemsSource {x:Type edi:EdiDataType}}}" />

Greg


Câu trả lời này có vẻ không đầy đủ: * Lõi / là gì?
bẫy bẫy

1

Tôi thích câu trả lời của tom.maruska , nhưng tôi cần hỗ trợ bất kỳ loại enum nào mà mẫu của tôi có thể gặp phải khi chạy. Vì vậy, tôi đã phải sử dụng một ràng buộc để chỉ định loại cho phần mở rộng đánh dấu. Tôi đã có thể làm việc trong câu trả lời này từ nicolay.anykienko để đưa ra một phần mở rộng đánh dấu rất linh hoạt sẽ hoạt động trong mọi trường hợp tôi có thể nghĩ ra. Nó được tiêu thụ như thế này:

<ComboBox SelectedValue="{Binding MyEnumProperty}" 
          SelectedValuePath="Value"
          ItemsSource="{local:EnumToObjectArray SourceEnum={Binding MyEnumProperty}}" 
          DisplayMemberPath="DisplayName" />

Nguồn cho phần mở rộng đánh dấu đã trộn được tham chiếu ở trên:

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("_", " ");
    }
}

1

Giải thích đơn giản và rõ ràng: 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>

0

Sử dụng ReactiveUI, tôi đã tạo ra giải pháp thay thế sau đây. Đó không phải là một giải pháp tất cả trong một thanh lịch, nhưng tôi nghĩ ít nhất nó cũng có thể đọc được.

Trong trường hợp của tôi, việc ràng buộc một danh sách enumkiểm soát là một trường hợp hiếm gặp, vì vậy tôi không cần phải mở rộng giải pháp trên cơ sở mã. Tuy nhiên, mã có thể được làm cho chung chung hơn bằng cách thay đổi EffectStyleLookup.Itemthành một Object. Tôi đã thử nó với mã của tôi, không cần sửa đổi gì khác. Điều đó có nghĩa là một lớp người trợ giúp có thể được áp dụng cho bất kỳ enumdanh sách nào . Mặc dù điều đó sẽ làm giảm khả năng đọc của nó - ReactiveList<EnumLookupHelper>không có một vòng tuyệt vời cho nó.

Sử dụng lớp trợ giúp sau:

public class EffectStyleLookup
{
    public EffectStyle Item { get; set; }
    public string Display { get; set; }
}

Trong ViewModel, chuyển đổi danh sách các enum và hiển thị nó dưới dạng một thuộc tính:

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 );
  }
}

Trong ComboBox, sử dụng SelectedValuePathtài sản, để liên kết với enumgiá trị ban đầu :

<ComboBox Name="EffectStyle" DisplayMemberPath="Display" SelectedValuePath="Item" />

Trong Chế độ xem, điều này cho phép chúng tôi liên kết bản gốc enumvới SelectedEffectStyleViewModel, nhưng hiển thị ToString()giá trị trong ComboBox:

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) );
});

Tôi nghĩ rằng ViewModel của bạn có lỗi. 1) Không phải là một ReactiveList của EffectStyleLookup?, 2) Trước tiên, bạn nên tạo một ReactiveList trống <T> (). Sau đó thêm các mục. Cuối cùng: ReactiveList <T> hiện không dùng nữa (nhưng vẫn hoạt động). EffectStyles = new ReactiveList <EffectStyleLookup> (); EffectStyles.AddRange (danh sách); Cảm ơn đã dành thời gian để thể hiện điều này.
dùng1040323

0

Tôi đang thêm nhận xét của mình (trong VB, thật đáng buồn, nhưng khái niệm này có thể dễ dàng được sao chép sang C # trong một nhịp tim), vì tôi chỉ cần tham khảo điều này và không thích bất kỳ câu trả lời nào vì chúng quá phức tạp. Nó không phải là khó khăn này.

Vì vậy, tôi đã đưa ra một cách dễ dàng hơn. Ràng buộc các điều tra viên vào một từ điển. Liên kết từ điển đó vào Combobox.

Combobox của tôi:

<ComboBox x:Name="cmbRole" VerticalAlignment="Stretch" IsEditable="False" Padding="2" 
    Margin="0" FontSize="11" HorizontalAlignment="Stretch" TabIndex="104" 
    SelectedValuePath="Key" DisplayMemberPath="Value" />

Mã của tôi phía sau. Hy vọng, điều này sẽ giúp người khác ra ngoài.

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

Câu trả lời của Kyrylo đơn giản hơn nhiều so với của bạn - Tôi không hiểu điều gì phức tạp về nó? Yêu cầu chuyển đổi bằng không trong mã.
Johnathon Sullinger

Tôi không muốn đặt tất cả logic của mình vào tay XAML. Tôi thích làm theo logic của tôi theo cách của tôi (không phải lúc nào cũng là cách tốt nhất), nhưng nó cho phép tôi hiểu nơi nào và tại sao một cái gì đó không theo kế hoạch. Cái này ít phức tạp hơn, nhưng dựa vào XAML / WPF để làm logic. Tôi chỉ không phải là một fan hâm mộ của điều đó. 10.000 cách để da mèo, bạn biết không?
Laki Politis

Đủ công bằng. Cá nhân tôi thích sử dụng các tính năng đã được xây dựng, ngoài hộp, đối với tôi nhưng đó chỉ là sở thích của tôi;)
Johnathon Sullinger

Vâng thưa ngài! Tôi hoàn toàn hiểu. Tôi đã bị buộc phải phát triển Phần mềm đến từ phát triển web. Tôi đã không được cập nhật trên WPF và phải học rất nhiều khi tôi đi cùng. Tôi vẫn không hiểu tất cả những điều phức tạp của các điều khiển WPF / XAML, và vì vậy tôi đã tìm thấy nhiều trục trặc hơn là các giải pháp theo cách tôi mong đợi mọi thứ sẽ hoạt động. Nhưng tôi đánh giá cao cuộc trò chuyện này. Nó khiến tôi phải nghiên cứu thêm.
Laki Pol viêm

0

Solutuion của Nick có thể được đơn giản hóa nhiều hơn, không có gì lạ mắt, bạn sẽ chỉ cần một trình chuyển đổi duy nhất:

[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;
    }
}

Sau đó, bạn sử dụng nó bất cứ nơi nào bạn muốn hộp tổ hợp của mình xuất hiện:

<ComboBox ItemsSource="{Binding PagePosition, Converter={converter:EnumToCollectionConverter}, Mode=OneTime}"  SelectedItem="{Binding PagePosition}" />

0

Tôi không khuyên bạn nên thực hiện điều này như vậy nhưng hy vọng điều này có thể truyền cảm hứng cho một giải pháp tốt.

Hãy nói enum của bạn là Foo. Sau đó, bạn có thể làm một cái gì đó như thế này.

public class FooViewModel : ViewModel
{
    private int _fooValue;

    public int FooValue
    {
        get => _fooValue;
        set
        {
            _fooValue = value;
            OnPropertyChange();
            OnPropertyChange(nameof(Foo));
            OnPropertyChange(nameof(FooName));
        }
    }
    public Foo Foo 
    { 
        get => (Foo)FooValue; 
        set 
        { 
            _fooValue = (int)value;
            OnPropertyChange();
            OnPropertyChange(nameof(FooValue));
            OnPropertyChange(nameof(FooName));
        } 
    }
    public string FooName { get => Enum.GetName(typeof(Foo), Foo); }

    public FooViewModel(Foo foo)
    {
        Foo = foo;
    }
}

Sau đó, trên Window.Loadphương thức, bạn có thể tải tất cả các enum ObservableCollection<FooViewModel>mà bạn có thể đặt làm DataContext của combobox.


0

Tôi chỉ giữ nó đơn giản. Tôi đã tạo một danh sách các mục có giá trị enum trong ViewModel của mình:

public enum InputsOutputsBoth
{
    Inputs,
    Outputs,
    Both
}

private IList<InputsOutputsBoth> _ioTypes = new List<InputsOutputsBoth>() 
{ 
    InputsOutputsBoth.Both, 
    InputsOutputsBoth.Inputs, 
    InputsOutputsBoth.Outputs 
};

public IEnumerable<InputsOutputsBoth> IoTypes
{
    get { return _ioTypes; }
    set { }
}

private InputsOutputsBoth _selectedIoType;

public InputsOutputsBoth SelectedIoType
{
    get { return _selectedIoType; }
    set
    {
        _selectedIoType = value;
        OnPropertyChanged("SelectedIoType");
        OnSelectionChanged();
    }
}

Trong mã xaml của tôi, tôi chỉ cần điều này:

<ComboBox ItemsSource="{Binding IoTypes}" SelectedItem="{Binding SelectedIoType, Mode=TwoWay}">
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.