2

I have an Enum of "Invoice Actions" that populate a combobox, but i would like to disable one of the enum members ("View") based on a "CanDisplayDetails" property that is in the ViewModel.

I can't quite figure out how to bind the IsEnabled property to "CanDisplayDetails" in a ComboBoxItem ItemContainerStyle, since the context seems to be the Enum instead of my ViewModel. How do i correct the binding so that it can provide the enum value, but IsEnabled binds to my ViewModel property? And it also needs to only effect the "View" ComboBoxItem. Thanks!

Style idea so far:

<ComboBox.ItemContainerStyle>
    <Style TargetType="{x:Type ComboBoxItem}">
        <Setter Property="IsEnabled" Value="{Binding CanDisplayDetails}"/>
    </Style>
</ComboBox.ItemContainerStyle>

In the XAML UserControl's Resources:

<ObjectDataProvider x:Key="ActionsEnum" MethodName="GetValues" ObjectType="{x:Type sys:Enum}">
        <ObjectDataProvider.MethodParameters>
            <x:TypeExtension TypeName="Constants:InvoiceActionsLong"/>
        </ObjectDataProvider.MethodParameters>
    </ObjectDataProvider>

The ComboBox in the same UserControl:

<ComboBox Grid.Column="1" ItemsSource="{Binding Source={StaticResource ActionsEnum}}" IsEnabled="{Binding SelectedItem, Converter={StaticResource SelectionConverter}}"
                      SelectedItem="{Binding SelectedAction}">
    <ComboBox.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Converter={StaticResource EnumDescriptionConverter}}" />
        </DataTemplate>
    </ComboBox.ItemTemplate>
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="SelectionChanged">
            <i:InvokeCommandAction Command="{Binding ActionCommand}"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
</ComboBox>

The EnumDescriptionConverter:

public class EnumDescriptionConverter : IValueConverter
{
    /// <summary>
    /// Get enum description
    /// </summary>
    /// <param name="enumObj"></param>
    /// <returns></returns>
    private string GetEnumDescription(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;
        }
    }

    /// <summary>
    /// Returns an enum member's description
    /// </summary>
    /// <param name="value"></param>
    /// <param name="targetType"></param>
    /// <param name="parameter"></param>
    /// <param name="culture"></param>
    /// <returns></returns>
    object IValueConverter.Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        Enum myEnum = (Enum)value;
        string description = GetEnumDescription(myEnum);
        return description;
    }

    object IValueConverter.ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return string.Empty;
    }
}

The enum:

/// <summary>
/// Enum lists all available Actions for Action Combo Box at the Invoice level for DETAIL types ie. active, plan
/// </summary>
public enum InvoiceActionsLong
{
    [Description("Contacts")]
    Contacts,
    [Description("Delivery")]
    Delivery,
    [Description("Documents")]
    Documents,
    [Description("Note")]
    Note,
    [Description("Payments")]
    Payments,
    [Description("Print")]
    Print,
    [Description("Process")]
    Process,
    [Description("Reload")]
    Reload,
    [Description("Send")]
    Send,
    [Description("View")]
    View
}

The property in my ViewModel:

/// <summary>
/// Bool denotes whether can display details
/// </summary>
private bool CanDisplayDetails
{
    get
    {
        bool b = true;

        if (SelectedItem != null)
        {
            if (SelectedItem.AdjustmentTypeID == 1)
            {
                b = false;
            }
        }

        return b;
    }
}
Xaruth
  • 4,034
  • 3
  • 19
  • 26

2 Answers2

2

The data context that your Style binding is using is the data context of the ComboboxItem (this will be the enum element itself) not the view model, as you are hoping.

Try using a relative source on your binding to walk up the logical tree and get the data context of the ComboBox, not the ComboBoxItem.

<Setter Property="IsEnabled" Value="{Binding DataContext.CanDisplayDetails, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ComboBox}}}"/>

You can diagnose this yourself by checking the visual studio output window. You will see a binding error telling you something like;

Data Error : CanDisplayDetails property cannot be found on a _InvoiceActionsLong_ instance

EDIT

In order to have this apply to only your View item consider;

  • a custom StyleSelector that will only apply this style is the content of the ComboBoxItem is View. Set the ComboBox.ItemContainerStyleSelector property to an instance of your selector.
  • an IMultiValueConverter implementation for the binding to CanDisplayDetails and the enum value. This converter would always return true if the value is not View.

Why does this happen?

The data context of generated ItemSelector items like ComboBoxItem is automatically set to be the data that the item represents.

Gusdor
  • 14,001
  • 2
  • 52
  • 64
  • @Gusdor-This is perfectly what i was missing, except: How do i only get this to operate on the "View" comboBoxItem instead of all of them? – Theodosius Von Richthofen Mar 13 '14 at 13:06
  • for either route, how would i tell the styleSelector or converter that the item is "View"? I started to make a converter, but the object passed to it is the boolean CanDisplayDetails. – Theodosius Von Richthofen Mar 13 '14 at 13:25
  • 1
    @FrederickVonStubenstein for the converter, use the `ConverterParameter` on the xaml binding and the `parameter` parameter in the `IValueConverter.Convert` implementation. `StyleSelector` requires you to set the selector on the `ComboBox.ItemContainerStyleSelector` property and you will recieve each item in the `Select(object)` implementaion. – Gusdor Mar 13 '14 at 13:54
  • @Gusdor- But what do i put for the ConverterParameter in the xaml Binding? I have tried ConverterParameter={x:Static Constants:InvoiceActionsLong.View}, but obviously that always results in the parameter being "View". Also tried ConverterParameter={RelativeSource FindAncestor, AncestorType={x:Type ComboBoxItem}} but that isn't it. Thanks for your help, I'm almost there! – Theodosius Von Richthofen Mar 13 '14 at 13:58
  • @FrederickVonStubenstein `{Binding}` is all you need. Remember, you want to pass the current `ComboBoxItem.DataContext` (the enum value) to the converter! – Gusdor Mar 13 '14 at 14:05
  • Now I'm confused! Here's what I've got, it says {Binding} cannot be set on the 'ConverterParameter': – Theodosius Von Richthofen Mar 13 '14 at 14:09
  • 1
    @FrederickVonStubenstein I gave you the wrong information. Bindings cannot be set on converterparameter. See this solution http://stackoverflow.com/questions/15309008/binding-converterparameter – Gusdor Mar 13 '14 at 14:11
  • This lead me to a solution, I needed to use MultiBinding and a MultiConverter. See my answer below... – Theodosius Von Richthofen Mar 13 '14 at 14:49
1

Using @Gusdor's help, I finally solved this. I needed to use a MultiBinding and MultiConverter as below. Basically, I pass both my ViewModel property "CanDisplayDetails" boolean and the ComboBoxItem itself to the MultiConverter. If the passed boolean was false, it checks if the ComboBoxItem's content was my "View" enum value. If both of these, it returns false, disabling the ComboBoxItem. Otherwise, it just returns true, keeping all other items enabled. Thanks to @Gusdor for the continued help and pointing me to the correct SO discussion! Hopefully this will help someone else in the future.

Edited ComboBox ItemContainerStyle:

<ComboBox.ItemContainerStyle>
                <Style TargetType="{x:Type ComboBoxItem}">
                    <Setter Property="IsEnabled">
                        <Setter.Value>
                            <MultiBinding Converter="{StaticResource IsViewItemConverter}">
                                <Binding Path="DataContext.CanDisplayDetails" RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type ComboBox}}" />
                                <Binding RelativeSource="{RelativeSource Self}"/>
                            </MultiBinding>
                        </Setter.Value>
                    </Setter>
                </Style>
            </ComboBox.ItemContainerStyle>

New MultiConverter instead of IValueConverter:

public class IsViewItemConverter : IMultiValueConverter
{
    /// <summary>
    /// COnverter checks CanDisplayDetails and if ComboBoxItem is View Details, will disable if false and is View Details
    /// </summary>
    /// <param name="values">object array</param>
    /// <param name="targetType"></param>
    /// <param name="parameter"></param>
    /// <param name="culture"></param>
    /// <returns>true if should enabled item</returns>
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        bool? b = values[0] as bool?;
        ComboBoxItem c = values[1] as ComboBoxItem;
        InvoiceActionsLong? item = c.Content as InvoiceActionsLong?;

        if (b == false && (InvoiceActionsLong)item == InvoiceActionsLong.View)
        {
            return false;
        }
        else
        {
            return true;
        }
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotSupportedException();
    }
}