Câu hỏi WPF ràng buộc ComboBox để enum (với một twist)


Vâng vấn đề là tôi có enum này, NHƯNG tôi không muốn combobox để hiển thị các giá trị của enum. Đây là enum:

public enum Mode
    {
        [Description("Display active only")]
        Active,
        [Description("Display selected only")]
        Selected,
        [Description("Display active and selected")]
        ActiveAndSelected
    }

Vì vậy, trong ComboBox thay vì hiển thị Active, Selected hoặc ActiveAndSelected, tôi muốn hiển thị DescriptionProperty cho mỗi giá trị của enum. Tôi có một phương thức mở rộng gọi là GetDescription () cho enum:

public static string GetDescription(this Enum enumObj)
        {
            FieldInfo fieldInfo =
                enumObj.GetType().GetField(enumObj.ToString());

            object[] attribArray = fieldInfo.GetCustomAttributes(false);

            if (attribArray.Length == 0)
            {
                return enumObj.ToString();
            }
            else
            {
                DescriptionAttribute attrib =
                    attribArray[0] as DescriptionAttribute;
                return attrib.Description;
            }
        }

Vì vậy, có cách nào tôi có thể ràng buộc enum vào ComboBox VÀ hiển thị nội dung của nó với phương pháp mở rộng GetDescription?

Cảm ơn!


13
2018-05-27 15:04


gốc




Các câu trả lời:


Tôi thích cách nghĩ của bạn. Nhưng GetCustomAttributes sử dụng sự phản chiếu. Điều đó sẽ làm gì đối với hiệu suất của bạn?

Xem bài đăng này: WPF - Hiển thị enums trong điều khiển ComboBox http://www.infosysblogs.com/microsoft/2008/09/wpf_displaying_enums_in_combob.html


6
2018-05-27 15:20



Dude, sự phản chiếu không phải cái đó chậm, đặc biệt so với thời gian cần để hiển thị GUI. Tôi không ngờ nó là một vấn đề. - Joe White
Chà, đừng dùng lời của tôi vì điều đó. Bài viết được đề cập ở trên nói rằng đó là một mối quan tâm. - Robert Harvey♦
Nhưng không trích dẫn bất kỳ kết quả hồ sơ nào. Tác giả đã quan tâm đến nó, nhưng điều đó không có nghĩa nó thực sự là một vấn đề. - Joe White
Đây là người duy nhất tôi có thể làm việc. Tôi đã có thể làm cho nó ngắn hơn một chút bằng cách sử dụng phương pháp mở rộng GetDescription khi khởi tạo từ điển. Cảm ơn! - Carlo


Tôi sẽ đề nghị một DataTemplate và một ValueConverter. Điều đó sẽ cho phép bạn tùy chỉnh cách nó được hiển thị, nhưng bạn vẫn có thể đọc thuộc tính SelectedItem của combobox và nhận giá trị enum thực tế.

ValueConverters yêu cầu rất nhiều mã boilerplate, nhưng không có gì quá phức tạp ở đây. Trước tiên, bạn tạo lớp ValueConverter:

public class ModeConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter,
        CultureInfo culture)
    {
        return ((Mode) value).GetDescription();
    }
    public object ConvertBack(object value, Type targetType, object parameter,
        CultureInfo culture)
    {
        throw new NotSupportedException();
    }
}

Vì bạn chỉ chuyển đổi các giá trị enum thành chuỗi (để hiển thị), bạn không cần ConvertBack - đó chỉ là các kịch bản ràng buộc hai chiều.

Sau đó, bạn đặt một thể hiện của ValueConverter vào tài nguyên của bạn, với một cái gì đó như thế này:

<Window ... xmlns:WpfApplication1="clr-namespace:WpfApplication1">
    <Window.Resources>
        <WpfApplication1:ModeConverter x:Key="modeConverter"/>
    </Window.Resources>
    ....
</Window>

Sau đó, bạn đã sẵn sàng cung cấp cho ComboBox một DisplayTemplate định dạng các mục của nó bằng cách sử dụng ModeConverter:

<ComboBox Name="comboBox" ...>
    <ComboBox.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Converter={StaticResource modeConverter}}"/>
        </DataTemplate>
    </ComboBox.ItemTemplate>
</ComboBox>

Để kiểm tra điều này, tôi đã ném vào một Label, điều đó sẽ cho tôi thấy giá trị SelectedItem thực tế, và nó thực sự cho thấy rằng SelectedItem là enum thay vì văn bản hiển thị, đó là những gì tôi muốn:

<Label Content="{Binding ElementName=comboBox, Path=SelectedItem}"/>

19
2018-05-27 16:04



Dude, câu trả lời của bạn cuối cùng đã giải quyết vấn đề của tôi sau vài giờ đào internet. Cảm ơn! - mcy


Đây là cách tôi đang làm nó với MVVM. Trên mô hình của tôi, tôi đã xác định enum của mình:

    public enum VelocityUnitOfMeasure
    {
        [Description("Miles per Hour")]
        MilesPerHour,
        [Description("Kilometers per Hour")]
        KilometersPerHour
    }

Trên ViewModel của tôi, tôi trưng ra một thuộc tính cung cấp các lựa chọn có thể có như chuỗi cũng như một thuộc tính để lấy / thiết lập giá trị của mô hình. Điều này rất hữu ích nếu chúng ta không muốn sử dụng mọi giá trị enum trong kiểu:

    //UI Helper
    public IEnumerable<string> VelocityUnitOfMeasureSelections
    {
        get
        {
            var units = new []
                            {
                               VelocityUnitOfMeasure.MilesPerHour.Description(),
                               VelocityUnitOfMeasure.KilometersPerHour.Description()
                            };
            return units;
        }
    }

    //VM property
    public VelocityUnitOfMeasure UnitOfMeasure
    {
        get { return model.UnitOfMeasure; }
        set { model.UnitOfMeasure = value; }
    }

Hơn nữa, tôi sử dụng một EnumDescriptionCoverter chung:

public class EnumDescriptionConverter : IValueConverter
{
    //From Binding Source
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (!(value is Enum)) throw new ArgumentException("Value is not an Enum");
        return (value as Enum).Description();
    }

    //From Binding Target
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (!(value is string)) throw new ArgumentException("Value is not a string");
        foreach(var item in Enum.GetValues(targetType))
        {
            var asString = (item as Enum).Description();
            if (asString == (string) value)
            {
                return item;
            }
        }
        throw new ArgumentException("Unable to match string to Enum description");
    }
}

Và cuối cùng, với quan điểm tôi có thể làm như sau:

<Window.Resources>
    <ValueConverters:EnumDescriptionConverter x:Key="enumDescriptionConverter" />
</Window.Resources>
...
<ComboBox SelectedItem="{Binding UnitOfMeasure, Converter={StaticResource enumDescriptionConverter}}"
          ItemsSource="{Binding VelocityUnitOfMeasureSelections, Mode=OneWay}" />

6
2018-04-09 22:52



hàm Enum.Description () và phương thức mở rộng là gì? Tôi không thể tìm thấy phương thức đó trên kiểu System.Enum .. - mtijn
.Description () là một phương thức mở rộng nhận thuộc tính mô tả. Trong tầm nhìn phía sau, nó có thể phù hợp hơn để sử dụng thuộc tính DisplayName. - Mike Rowley
Tôi đã bỏ qua phương thức mở rộng trong phần thân câu hỏi, đó có thể là những gì bạn đang đề cập đến, và DisplayName không được sử dụng vì nó không áp dụng cho các mục tiêu trường enum (trừ khi bạn mở rộng việc sử dụng thuộc tính) - mtijn
Tôi thích bạn tiếp cận nhưng tôi muốn nâng cấp phương thức chuyển đổi trở lại của bạn thành: public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { return Enum.GetValues(targetType) .OfType<Enum>() .Single(element => element.Description() == (string)value); } - memory of a dream


Tôi khuyên bạn nên sử dụng tiện ích mở rộng đánh dấu mà tôi đã đăng đây, chỉ với một chút sửa đổi:

[MarkupExtensionReturnType(typeof(IEnumerable))]
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).Select(o => GetDescription(o));
    }
}

Bạn có thể sử dụng nó như sau:

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

EDIT: phương pháp tôi đề nghị sẽ liên kết với một danh sách các chuỗi, đó là không mong muốn kể từ khi chúng tôi muốn SelectedItem được loại chế độ. Nó sẽ là tốt hơn để loại bỏ phần .Select (...), và sử dụng một ràng buộc với một bộ chuyển đổi tùy chỉnh trong ItemTemplate.


3
2018-05-27 15:33



Nó sẽ không làm cho SelectedItem của combo box là "Display active only" thay vì Mode.Active? Có vẻ như một tác dụng phụ không mong muốn đối với tôi. - Joe White
Vì vậy, bạn có nghĩa là với cách tiếp cận này tôi sẽ không thể thiết lập các mục đã chọn cho những gì các đối tượng với enum hiện đã chọn? - Carlo
@ Joe: vâng, bạn nói đúng ... đó là một vấn đề thực sự. Tôi sẽ cập nhật câu trả lời của mình - Thomas Levesque


Các câu hỏi về việc sử dụng sự phản chiếu và các thuộc tính sang một bên, có một vài cách bạn có thể làm điều này, nhưng tôi nghĩ cách tốt nhất là chỉ tạo một lớp mô hình xem nhỏ kết thúc tốt đẹp giá trị đếm:

public class ModeViewModel : ViewModel
{
    private readonly Mode _mode;

    public ModeViewModel(Mode mode)
    {
        ...
    }

    public Mode Mode
    {
        get { ... }
    }

    public string Description
    {
        get { return _mode.GetDescription(); }
    }
}

Ngoài ra, bạn có thể xem xét sử dụng ObjectDataProvider.


3
2018-05-27 15:23





Tôi đã thực hiện nó như thế này:

<ComboBox x:Name="CurrencyCodeComboBox" Grid.Column="4" DisplayMemberPath="." HorizontalAlignment="Left" Height="22"   Margin="11,6.2,0,10.2" VerticalAlignment="Center" Width="81" Grid.Row="1" SelectedValue="{Binding currencyCode}" >
            <ComboBox.ItemsPanel>
                <ItemsPanelTemplate>
                    <VirtualizingStackPanel/>
                </ItemsPanelTemplate>
            </ComboBox.ItemsPanel>
        </ComboBox>

trong mã tôi đặt itemSource:

CurrencyCodeComboBox.ItemsSource = [Enum].GetValues(GetType(currencyCode))

0
2018-01-17 14:24