6

I have a model with an enum property (in this case, related to Export Control Regulations). When displaying the value to the user, I want to show a corresponding string. Sometimes this is in ComboBox (where the user can select a value), and sometimes it is in a TextBlock (where it is read-only).

Example: for ExportRegulationType.EAR, I want to display "EAR", while for ExportRegulationType.DoNotExport, I want to display "Do Not Export". Note that I don't have any language localization needs, but I recognize the issue...

Currently, in my ViewModel, I have a property that returns a string based on the current enum value, and also another property that returns a Dictionary<ExportRegulationType, string>. For the ComboBoxes, I can bind ItemsSource to the dictionary property, and for the TextBlocks, I can bind to the string property. This works, but is kind of clumsy.

Two questions:

1) It seems to me that I should be able to declare the dictionary (with keys and values) as a static resource in XAML (probably in App.xaml), and use that for the ItemsSource for the ComboBox version. However, I can't figure out how to declare and reference such a thing. How can I do that?

2) Assuming the above is in place, I would think I could also set up a binding with the textblock, so based on the enum property, it will look up the string in the dictionary.

I have seen the following questions relating to a static or dynamic enum value. The first isn't adequate, and the second isn't answered...

These should be a XAML-only, and will enable me to remove the methods from my ViewModel (having only the one exposed ExportRegulationType enumerated property. Are these possible?

Edit: Additional information:

In the application, I will have many different sets of views, models, and ViewModels. However, as export control regulations are a common and consistent requirement, I am using composition to keep it DRY. i.e., Models A and B both have an ExportControl model. ViewModels A1, A2, B1 and B2 will have an ExportControlViewModel. The views will have controls bound to the ExportControlViewModel of their ViewModel. The views will have either a ComboBox or a TextBlock, but not both (Depending on if the user can change the value).

Community
  • 1
  • 1
mbmcavoy
  • 2,628
  • 5
  • 23
  • 34

5 Answers5

3

I don't know if this will work for your case, but here is a possible solution. In your view model, expose a ExportRegulationType property and then create a value converter to display your desired string.

First create your value converter:

class ExportRegulationTypeToStringConverter: IValueConverter
{
    #region IValueConverter Members

    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        ExportRegulationType regType = (ExportRegulationType)value;

        switch(regType)
        {
            case ExportRegulationType.EAR:
                return "EAR";
            case ExportRegulationType.DoNotExport:
                return "Do Not Export";

            //handle other cases
        }
    }

    public object ConvertBack(object value, Type targetType, object parameter,    System.Globalization.CultureInfo culture)
    {
        return Binding.DoNothing;
    }

    #endregion
}

Then add a reference to your converter in your xaml. local is the namespace in which your class is located.

<local:ExportRegulationTypeToStringConverter x:Key="exportRegConverter" />

Finally, set the value of your text box to use the converter. pathToEnum is the property exposed on your ViewModel of type ExportRegulationType.

<TextBlock Text="{Binding pathToEnum, Converter={StaticResource exportRegConverter}}" />

Use ObjectDataProvider to fill the ComboBox with the values of the enum.

<Window.Resources>
 <ObjectDataProvider x:Key="dataFromEnum"
   MethodName="GetValues" ObjectType="{x:Type System:Enum}">
      <ObjectDataProvider.MethodParameters>
           <x:Type TypeName="local:ExportRegulationType"/>
      </ObjectDataProvider.MethodParameters>
 </ObjectDataProvider>
</Window.Resources>

Now we create the ComboBox and use a container style with our value converter to display the desired strings for our enum.

<ComboBox ItemsSource="{Binding Source={StaticResource dataFromEnum}}">
    <ComboBox.ItemContainerStyle>
        <Style TargetType="ComboBoxItem">
            <Setter Property="Content" Value="{Binding Converter={StaticResource exportRegConverter}}" />
        </Style>
    </ComboBox.ItemContainerStyle>
</ComboBox>
Dylan Meador
  • 2,381
  • 1
  • 19
  • 32
  • I wish I could pick two answers; I'm giving you the credit for being quick in the right direction, and needing more rep! – mbmcavoy Aug 26 '11 at 23:24
  • Oh, as a note: using the ComboBox.ItemContainerStyle almost worked - the items in the drop-down list were correct. However, when an item was selected and the drop-down closes, the enum.ToString() value was shown. Using a Data Template instead works correctly. – mbmcavoy Aug 27 '11 at 00:03
2

Instead of the Dictionary you have another option.

See the following question: WPF Binding a ListBox to an enum, displaying the Description Attribute

You could add a Description Attribute to your enums like this

public enum ExportRegulationType
{
    [Description("EAR")]
    EAR,
    [Description("Do Not Export")]
    DoNotExport
}

And when you want to display it, you can just use EnumDescriptionConverter Converter found in the question I linked

Community
  • 1
  • 1
Fredrik Hedblad
  • 83,499
  • 23
  • 264
  • 266
  • Slick. As. Heck! I love that the string definition is embedded with the enum, and that the converter (with a minor tweak) is generic to any Enum and is graceful without the Description Attribute. – mbmcavoy Aug 26 '11 at 23:20
2

I solved this with a blend of what @Dylan and @Meleak wrote. I'm putting this as an answer to show what the final solution was:

First, I implemented an IValueConverter, (based on @Meleak's answer):

class EnumDescriptionConverter : IValueConverter
{

    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        Enum regulation = (Enum)value;
        return GetEnumDescription(regulation);
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return String.Empty;
    }

    /// <summary>
    /// Returns text intended for display based on the Description Attribute of the enumeration value.
    /// If no Description Attribute is applied, the value is converted to a string and returned.
    /// </summary>
    /// <param name="enumObj">The enumeration value to be converted.</param>
    /// <returns>Text of the Description Attribute or the Enumeration itself converted to string.</returns>
    private string GetEnumDescription(Enum enumObj)
    {
        // Get the DescriptionAttribute of the enum value.
        FieldInfo fieldInfo = enumObj.GetType().GetField(enumObj.ToString());
        object[] attributeArray = fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);

        if (attributeArray.Length == 0)
        {
            // If no Description Attribute was found, default to enum value conversion.
            return enumObj.ToString();
        }
        else
        {
            // Get the text of the Description Attribute
            DescriptionAttribute attrib = attributeArray[0] as DescriptionAttribute;
            return attrib.Description;
        }
    }
}

I tagged my enum (note that several values are not tagged as the desired text is the same as the value itself):

public enum ExportRegulationType
{
    [Description("Not Determined")]
    NotDetermined,   // Export authority not determined

    EAR,            // Controlled by EAR Regulations

    ITAR,           // Controlled by ITAR Regulations

    [Description("Do Not Export")]
    DoNotExport,    // Export not allowed

    Unrestricted    // Export not controlled
}

In my App.xaml, I declared the ObjectDataProvider to get the list of enum values and the EnumDisplayConverter (Here since they will be used by several different views):

<Application.Resources>
    [Other stuff...]
    <ObjectDataProvider MethodName="GetValues"
                        ObjectType="{x:Type sys:Enum}"
                        x:Key="ExportRegulationValues">
        <ObjectDataProvider.MethodParameters>
            <x:Type TypeName="models:ExportRegulationType"/>
        </ObjectDataProvider.MethodParameters>      
    </ObjectDataProvider>
    <local:EnumDescriptionConverter x:Key="ExportDisplayConverter"/>
</Application.Resources>

For a TextBlock:

<TextBlock Text="{Binding Export.Regulation, Converter={StaticResource ExportDisplayConverter}}"/>

For a Combo Box:

<ComboBox ItemsSource="{Binding Source={StaticResource ExportRegulationValues}}"
          SelectedValue="{Binding Document.Export.Regulation}">
    <ComboBox.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Converter={StaticResource ExportDisplayConverter}}"/>
        </DataTemplate>
    </ComboBox.ItemTemplate>
</ComboBox>

This works perfectly!

mbmcavoy
  • 2,628
  • 5
  • 23
  • 34
1

Use an ObjectDataProvider then bind the ComboBox's Items to it, and set "DisplayMemberPath" to "Value".

What this should do is to show the values of your dictionary, but in code-behind the SelectedValue is a KeyValuePair<>.

For your textblock, use a Binding using ElementName=yourcombobox and Path=SelectedItem:

<TextBlock Text="{Binding SelectedItem, ElementName=yourcombobox}" />

Let me know how it goes =)

Louis Kottmann
  • 16,268
  • 4
  • 64
  • 88
0

Here is a blog post of mine with an approach using attached behaviors.

It is based on the principle that different enumeration values don't need to be limited to switching strings. Instead, you can declare whatever pieces of UI you want to represent each value (strings, images, different controls and layouts, etc.) and use an attached behavior to control their visibility.

Your situation, then, can be framed as having two different text blocks, each bound to the same property of type ExportRegulationType. Since they are bound to the same property, their visibilities are mutually exclusive:

<Grid>
    <TextBlock
        Text="EAR"
        local:EnumVisibility.Value="{Binding ExportRegulationType}"
        local:EnumVisibility.TargetValue="EAR"
    />
    <TextBlock
        Text="Do Not Export"
        local:EnumVisibility.Value="{Binding ExportRegulationType}"
        local:EnumVisibility.TargetValue="DoNotExport"
        FontWeight="Bold"
    />
</Grid>

I included the FontWeight="Bold" to show that you can make different decisions for each enumeration value. This also supports XAML localization because the text is set like any other text block.

See the post for a complete walkthrough of the solution, code samples, and a zip file containing the framework and an example application.

Edit in response to additional information:

Here is another post in the same series which describes how to select enumeration values with Selector controls.

A ComboBox bound to the ExportRegulationType property would look this this:

<ComboBox local:EnumSelector.SelectedValue="{Binding ExportRegulationType, Mode=TwoWay}">
    <ComboBoxItem Content="EAR" local:EnumSelector.ItemValue="EAR" />
    <ComboBoxItem Content="Do Not Export" local:EnumSelector.ItemValue="DoNotExport" />
</ComboBox>

We associate each item with an enumeration value, then use a TwoWay binding to EnumSelector.SelectedValue so it will write back to the view model's property whenever it changes.

This provides the same flexibility as with the text blocks: you can make whatever decisions you want about how to set the text and what is contained by each item.

Bryan Watts
  • 44,911
  • 16
  • 83
  • 88
  • Interesting idea. It doesn't really address the concerns at hand, but I can see how it could come in handy for certain situations. – mbmcavoy Aug 27 '11 at 00:09
  • @mbmcavoy: Your question is phrased in terms of a preconceived solution (dubbed "asking for a thin metal ruler" by Eric Lippert, http://blogs.msdn.com/b/ericlippert/archive/2003/11/03/a-parable.aspx). I identified and addressed the problem you are trying to solve, friendly enumeration names, and incorporated your main requirement, no cruft in the view model. Granted, it does not involve the dictionary binding, but that is not an inherent constraint of your underlying goal. – Bryan Watts Aug 27 '11 at 04:05