WPF - How to combine DataTrigger and Trigger?

.NetWpfTriggersSelectionListboxitem

.Net Problem Overview


> NOTE I have asked the related question: How to combine DataTrigger and EventTrigger?

I have a list box containing several items. The item's class implements INotifyPropertyChanged and has a property IsAvailable. I use that property to indicate unavailable options in the list using a different colour.

However, if a selected item is not available, then the foreground colour should be red.

<ListBox>
  <ListBox.Resources>
    <DataTemplate DataType="{x:Type local:InstitutionViewModel}">
      <TextBlock Name="Name" Text="{Binding Name}"/>
      <DataTemplate.Triggers>
        <DataTrigger Binding="{Binding IsAvailable}" Value="False">
          <Setter TargetName="Name" Property="Foreground" Value="#888"/>
        </DataTrigger>
      </DataTemplate.Triggers>
    </DataTemplate>
  </ListBox.Resources>
</ListBox>

I use the above data trigger to grey out unavailable items.

The problem I'm facing is that the fact that the item is selected has nothing to do with the underlying data to which the template is bound. What I really want is some kind of multi-trigger that supports both a regular Trigger on a dependency property (ListBoxItem.IsSelected) along with a DataTrigger on the bound data item.

Can this be done without introducing the concept of selection into my view model?

For anyone wondering why I do not disable unavailable items, understand that it is a requirement of the application that unavailable options may be selected. There are actually a few list boxes, and selection in one effects what's available in the others. I cannot disable the items as the user would not be able to change their minds or explore different combinations if items were disabled based upon earlier selections.

.Net Solutions


Solution 1 - .Net

For anyone else who's up against this problem, I found a solution that works for me. Of course, I'm still interested to see other interesting answers.

Here's what I did:

<MultiDataTrigger>
  <MultiDataTrigger.Conditions>
    <Condition Binding="{Binding
      RelativeSource={
        RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}},
        Path=IsSelected}" Value="True"/>
    <Condition Binding="{Binding IsAvailable}" Value="False"/>
  </MultiDataTrigger.Conditions>
  <Setter TargetName="Name" Property="Foreground" Value="#F00"/>
</MultiDataTrigger>

There's nothing special about this being a multi trigger though. If you just wanted to style the selected item differently in your data template, you could use:

<DataTrigger Binding="{Binding 
  RelativeSource={
    RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}},
    Path=IsSelected}" Value="True">
  <Setter TargetName="Name" Property="Foreground" Value="#888"/>
</DataTrigger>

Solution 2 - .Net

To use it with DataGridRow change binding mode to Self:

Binding="{Binding RelativeSource={RelativeSource Mode=Self}, Path=... 

Solution 3 - .Net

For DevExpress GridControl there is an example for combining field Value format condition and row IsFocused property.

XAML:

<dxg:GridControl.View>
    <dxg:TableView CustomRowAppearance="CustomRowAppearance" NavigationStyle="Row"> 
        <dxg:TableView.FormatConditions> 
            <dxg:DataBarFormatCondition FieldName="Profit" PredefinedFormatName="GreenGradientDataBar" /> 
            <dxg:FormatCondition FieldName="Profit" Expression="[Profit]&lt;0" PredefinedFormatName="RedText"/> 
            <dxg:FormatCondition FieldName="Profit" Expression="[Profit]&gt;=0" PredefinedFormatName="GreenText"/> 
            <dxg:FormatCondition FieldName="Profit" Expression="[Profit]&lt;=0" PredefinedFormatName="LightRedFillWithDarkRedText" ApplyToRow="True"/> 
        </dxg:TableView.FormatConditions> 
    </dxg:TableView>
</dxg:GridControl.View>

C#:

void CustomRowAppearance(object sender, CustomRowAppearanceEventArgs e) {
    if (e.RowSelectionState != SelectionState.None) {
        object result = e.ConditionalValue;
        if (e.Property == TextBlock.ForegroundProperty || e.Property == TextBlock.BackgroundProperty) {
            SolidColorBrush original = e.OriginalValue as SolidColorBrush;
            SolidColorBrush conditional = e.ConditionalValue as SolidColorBrush;
            if (conditional != null && (original == null || original.Color != conditional.Color))
                result = ShadeBrush(conditional);
        }
        e.Result = result;
        e.Handled = true;
    }
}

SolidColorBrush ShadeBrush(SolidColorBrush brush) {
    Color originalColor = brush.Color;
    float coefficient = 0.75f;
    byte a = originalColor.A;
    if (!grid.IsKeyboardFocusWithin) // I commented this line in WPF
        a = (byte)(originalColor.A / 2);
    byte r = (byte)(originalColor.R * coefficient);
    byte g = (byte)(originalColor.G * coefficient);
    byte b = (byte)(originalColor.B * coefficient);
    return new SolidColorBrush(Color.FromArgb(a, r, g, b));
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDrew NoakesView Question on Stackoverflow
Solution 1 - .NetDrew NoakesView Answer on Stackoverflow
Solution 2 - .NetVadimView Answer on Stackoverflow
Solution 3 - .NetFreesView Answer on Stackoverflow