3
\$\begingroup\$

I have a Combobox and I want to edit a boolean value.

        <dxe:ComboBoxEdit ItemsSource="{Binding EnumItemsSource}" 
                          DisplayMember="Name"
                          ValueMember="Id"
                          IsTextEditable="False"
                          EditValue="{Binding TargetValue, UpdateSourceTrigger=PropertyChanged}"/>

My ViewModel:

    /// <summary>
    /// Contains the ItemsSource for Enums
    /// </summary>
    public List<EnumItemObject> EnumItemsSource
    {
        get { return _enumItemsSource; }
        set
        {
            _enumItemsSource = value;
            OnPropertyChanged();
        }
    }

public class EnumItemObject
{
   public int Id {get;set;}
   public string Name {get;set;}
}

And I prepare the data for the Combobox ItemsSource that:

    /// <summary>
    /// Sets the value to the properties for the BitTemplate view. (similar with EnumTemplate)
    /// </summary>
    /// <param name="propertyInfo">a boolean property</param>
    private void PrepareDataForBitTemplate(PropertyInfo propertyInfo)
    {
        TargetValue = (int)propertyInfo.GetValue(_firstSelectedItem);
        EnumItemsSource = new List<EnumItemObject>();
        EnumItemsSource.Add(new EnumItemObject() { Id = 0, Name = "Nein" });
        EnumItemsSource.Add(new EnumItemObject() { Id = 1, Name = "Ja" });
    }

Is it the approach correct? Is there any easier solution?

UPDATE: Extract from my ViewModel code (you remember EnumItemsSource is ItemsSource for Combobox and TargetValue the Combobox selected item):

    private void LookUpViewData()
    {
        var propertyInfo = _firstSelectedItem.GetType().GetProperty(TargetFieldDescription.fdBigViewColumnName);
        if ((propertyInfo != null) && (propertyInfo.GetValue(_firstSelectedItem) != null))
        {
            if ((int) FieldDataType.ENum == TargetFieldDescription.fdDataType)
                PrepareDataForEnumTemplate(propertyInfo);
            if ((int) FieldDataType.Bit == TargetFieldDescription.fdDataType)
                PrepareDataForBitTemplate(propertyInfo); // like EnumTemplate
            else if ((int) FieldDataType.Time == TargetFieldDescription.fdDataType)
                PrepareDataForTimeTemplate(propertyInfo);
            else
                PrepareDataForDefaultTemplate(propertyInfo);
        }
        else
            TargetValue = String.Empty;
    }

    private void PrepareDataForEnumTemplate(PropertyInfo propertyInfo)
    {
        TargetValue = (int)propertyInfo.GetValue(_firstSelectedItem);
        if (_targetFieldDescription.fdEnumSource != null)
        {
            switch (_targetFieldDescription.fdEnumSource)
            {
                case "Station":
                    EnumItemsSource = ConvertListToEnumItemObjectList(_targetFieldDescription.fdEnumSource);
                    break;
                default:
                    EnumItemsSource = EnumUtil.ConvertEnumToEnumItemObjectList(_targetFieldDescription.fdEnumSource);
                    break;
            }

        }
    }

    private void PrepareDataForBitTemplate(PropertyInfo propertyInfo)
    {
        var value = propertyInfo.GetValue(_firstSelectedItem);
        bool bValue;
        if (value == null)
            TargetValue = 0;
        else if (bool.TryParse(value.ToString(), out bValue))
            TargetValue = (bValue) ? 1 : 0;
        else
            TargetValue = 0;
        //TargetValue = (int)propertyInfo.GetValue(_firstSelectedItem);
        var bitItems = new List<EnumItemObject>();
        bitItems.Add(new EnumItemObject { Id = 0, Name = "Nein" });
        bitItems.Add(new EnumItemObject { Id = 1, Name = "Ja" });
        EnumItemsSource = bitItems;
    }

So I want to load three possible kind of data in my Combobox. Enums, List of Objects (with name and id) and booleans (Yes/No List).

\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

It's not clear where this PrepareDataForBitTemplate(PropertyInfo) method is located. Is it code-behind? Whatever it is, if the goal is to bind a ComboBox with some enum values, I would much rather keep it standard - meaning the DataContext of the ComboBox is the ViewModel of the containing Window or UserControl:

public IEnumerable<SomeEnumType> ViewModelEnumValues
{
    get { return Enum.GetValues(typeof(SomeEnumType)).Cast<SomeEnumType>(); }
}

private SomeEnumType _selectedEnumValue;
public SomeEnumType SelectedEnumValue
{
    get { return _selectedEnumValue; }
    set { _selectedEnumValue = value; NotifyPropertyChanged(() => SelectedEnumValue); }
}

Before I move on to the corresponding XAML markup, a few observations:

  • The ViewModel implementation derives from some ViewModelBase abstract class which implements INotifyPropertyChanged and allows its derivatives to specify a property name in a strongly-typed way. I see your setter calls some OnPropertyChanged() method, but not how that method is able to notify the View that a specific property was changed.
  • The name Enum refers to a specific language construct. Calling something "Enum" when that something is not an enum is rather confusing.
  • I don't see why you're not using an actual enum when the values you're trying to load are basically "Yes" and "No".
  • EnumItemObject is an awful name, for several reasons:
    1. It's not an object, it's a class - an object is an instance of a class.
    2. It's not an enum item, it's essentially a ViewModel that exposes two properties - an int and a string.
    3. It's not immediately apparent how this class relates to any type of enum.

Now, given that your ViewModel exposes the above SelectedEnumValue and ViewModelEnumValues properties (both bad/stub names), with a ValueConverter the XAML for the ComboBox would be as simple as this:

<ComboBox ItemSource="{Binding ViewModelEnumValues}"
          SelectedItem="{Binding SelectedEnumValue, Mode=TwoWay}">
    <ComboBox.ItemTemplate>
        <DataTemplate>
            <Border Style="{StaticResource ListItemBorder}"> <!-- just some border -->
                <StackPanel>
                    <Image Source="{Binding Value, Converter={StaticResource EnumValueIconConverter}}" />
                    <Label Content="{Binding Value, Converter={StaticResource EnumValueNameConverter}}" />
                </StackPanel>
            </Border>
        </DataTemplate>
    </ComboBox.ItemTemplate>
</ComboBox>

And the converters would look like this:

using resx = Project.Properties.Resources;    

public class EnumValueToStringConverter : ConverterBase
{
    public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        SomeEnumType result;
        return Enum.TryParse(value.ToString(), out result)
                   ? resx.ResourceManager.GetString(promoType + "Caption")
                   : string.Empty;
    }
}

public class EnumValueToIconConverter : ConverterBase
{
    public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var result = string.Empty;
        var typedValue = value As SomeEnumType;
        if (typedValue != null)
        {
            switch(typedValue)
            {
                case SomeEnumType.EnumValue1:
                    result = "images/image-for-value1.png";
                    break;
                case SomeEnumType.EnumValue2:
                    result = "images/image-for-value2.png";
                    break;
            }
        }

        return new BitmapImage(new Uri("/project.namespace;component/" + result, UriKind.Relative));
    }
}

Of course this is overkill if all you want is the enum names in your dropdown list - but I tend to define a DataTemplate for just about everything (and I think I have a tendency to abuse converters, too).

If all you need is to display an enum value directly in the UI, then this will suffice:

public IEnumerable<string> ViewModelEnumNames
{
    get { return Enum.GetNames(typeof(SomeEnumType)).ToList(); }
}

private string _selectedEnumName;
public string SelectedEnumName
{
    get { return _selectedEnumName; }
    set { _selectedEnumName = value; NotifyPropertyChanged(() => SelectedEnumName); }
}
<ComboBox ItemSource="{Binding ViewModelEnumNames}"
          SelectedItem="{Binding SelectedEnumName, Mode=TwoWay}">
\$\endgroup\$
2
  • \$\begingroup\$ Hello retailcoder, thanks for your support. Your opinion is very interesting, thanks. PrepareDataForBitTemplate is a function located in my ViewModel. But one question. I do an update in my post. I want to be able to load in this Combobox not only enums. Also list of Stations, list of countries, list of enumvalues, list with Yes/No. How can I design the property public IEnumerable<SomeEnumType> ViewModelEnumValues to achieve this? Your solution is based only for enums, but my combobox should load list of objects and be able to update also boolean values. Pls take a look at my update above. \$\endgroup\$
    – Ziggy
    Nov 28, 2013 at 9:18
  • \$\begingroup\$ See the 2nd example; all the combobox needs is any IEnumerable, how the displayable data ends up there is irrelevant to the view (could be your model's concern) - for full-fledged objects all you need is a data template. :) \$\endgroup\$ Nov 28, 2013 at 11:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.