0

I'm trying to display status icons in status bar. The icons are defined as ViewBox static resources and displayed via ContentPresenter style with DataTriggers.

I would like no icon displayed if none of the triggers are matched, so I've tried setting the default Setter Content to x:Null or an empty string or remove the line at all, but the other icons stop displaying at all then.

Any ideas please?

My XAML code is as follows

<StatusBarItem Grid.Column="2">
    <ContentPresenter>
        <ContentPresenter.Style>
            <Style TargetType="ContentPresenter">
                <Setter Property="Content" Value="{x:Null}"/>
                <Style.Triggers>
                    <DataTrigger Binding="{Binding Path=State}" Value="Ok">
                        <Setter Property="Content" Value="{StaticResource StatusOK}"/>
                    </DataTrigger>
                    <DataTrigger Binding="{Binding Path=State}" Value="Invalid">
                        <Setter Property="Content" Value="{StaticResource StatusInvalid}"/>
                    </DataTrigger>
                    <DataTrigger Binding="{Binding Path=State}" Value="Warning">
                        <Setter Property="Content" Value="{StaticResource StatusWarning}"/>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </ContentPresenter.Style>
    </ContentPresenter>
</StatusBarItem>

Update

I've tried using visibility as suggested by Ed Plunkett but the icons stopped showing up at all. Here is the code.

<Style TargetType="ContentPresenter">
    <Setter Property="Content" Value="{StaticResource StatusOK}"/>
    <Setter Property="Visibility" Value="Collapsed"/>
    <Style.Triggers>
        <DataTrigger Binding="{Binding Path=State}" Value="Ok">
            <Setter Property="Content" Value="{StaticResource StatusOK}"/>
            <Setter Property="Visibility" Value="Visible"/>
        </DataTrigger>
        <DataTrigger Binding="{Binding Path=State}" Value="Invalid">
            <Setter Property="Content" Value="{StaticResource StatusInvalid}"/>
            <Setter Property="Visibility" Value="Visible"/>
        </DataTrigger>
        <DataTrigger Binding="{Binding Path=State}" Value="Warning">
            <Setter Property="Content" Value="{StaticResource StatusWarning}"/>
            <Setter Property="Visibility" Value="Visible"/>
        </DataTrigger>
    </Style.Triggers>
</Style>
yaugenka
  • 1,938
  • 1
  • 14
  • 30

1 Answers1

0

Your problem was misusing a ContentPresenter. There's no reason to use a ContentPresenter outside of a ControlTemplate. The only features it has which differ from ContentControl are things that only make sense inside a ControlTemplate. Now, as it happens, StatusBarItem is a subclass of ContentControl, so you could just style the StatusBarItem and set its Content. Either version in your question will work that way.

But there's a better and more powerful way to do this:

<Window.Resources>
    <Ellipse x:Key="StatusOK" Fill="Green" Width="20" Height="20" x:Shared="False" />
    <Path 
        x:Key="StatusInvalid" 
        Fill="Red" 
        Width="20" 
        Height="20" 
        x:Shared="False"  
        Data="M 34.2,87.4 L 12.3,65.5 L 12.3,34.5 L 34.2,12.6 L 65.2,12.6 L 87.1,34.5 L 87.1,65.5 L 65.2,87.4 Z" 
        Stretch="Uniform" 
        />
    <Rectangle x:Key="StatusWarning" Fill="Orange" Width="20" Height="20" x:Shared="False" />

    <DataTemplate x:Key="StateDataTemplate">
        <ContentControl x:Name="Icon" Width="20" Height="20" />

        <DataTemplate.Triggers>
            <DataTrigger Binding="{Binding}" Value="Ok">
                <Setter TargetName="Icon" Property="Content" Value="{StaticResource StatusOK}"/>
                <Setter TargetName="Icon" Property="Visibility" Value="Visible"/>
            </DataTrigger>
            <DataTrigger Binding="{Binding}" Value="Invalid">
                <Setter TargetName="Icon" Property="Content" Value="{StaticResource StatusInvalid}"/>
                <Setter TargetName="Icon" Property="Visibility" Value="Visible"/>
            </DataTrigger>
            <DataTrigger Binding="{Binding}" Value="Warning">
                <Setter TargetName="Icon" Property="Content" Value="{StaticResource StatusWarning}"/>
                <Setter TargetName="Icon" Property="Visibility" Value="Visible"/>
            </DataTrigger>
        </DataTemplate.Triggers>
    </DataTemplate>
</Window.Resources>

StatusBarItem:

<StatusBarItem 
    Grid.Column="2" 
    Content="{Binding State}" 
    ContentTemplate="{StaticResource StateDataTemplate}" 
    />
<StatusBarItem Grid.Column="3">
    <!--  
    This demonstrates how we can reuse the State datatemplate. My enum is named Status. 
    -->
    <ComboBox 
        SelectedItem="{Binding State}" 
        ItemTemplate="{StaticResource StateDataTemplate}" 
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        >
        <x:Null/>
        <local:Status>Ok</local:Status>
        <local:Status>Invalid</local:Status>
        <!-- Anything that stringifies as "Warning" works with the trigger -->
        <sys:String>Warning</sys:String>
    </ComboBox>
</StatusBarItem>

Note that I put x:Shared="False" on each of the icon content resources. When we do that, we'll be able to reuse this DataTemplate elsewhere with other properties that use the same enum type. Without x:Shared, there would only be one "Ok" state Ellipse, and if two controls tried to use it, the second would steal it from the first. It doesn't matter if the resource is a ViewBox with an image in it or an Ellipse or whatever; just put x:Shared on it.

Note also Binding="{Binding}" in the DataTriggers. We are templating the content of the StatusBarItem. That means that whatever is bound to the Content property of the StatusBarItem will be the DataContext inside the DataTemplate. That means we can reuse the data template for other controls bound to properties with have other names, as long as the type of those properties is the same (or similar; this will work with any value that stringifies as "Ok", "Invalid", or "Warning"). {Binding} means "Don't bind to some named property of the DataContext; just bind to the DataContext object itself". In this case, that's the enum value.

We don't hide the Icon content control by default because in the default state, there's no icon. I don't know if your State property is nullable or if there's a fourth, "none" state. By the way, there's no such thing as "a static resource". A resource is a resource. StaticResource is a class that retrieves resources one way; DynamicResource retrieves them another way.

  • Your answer gave me an idea of simply replacing `ContentPresenter` with `ContentControl` in my code and suprisingly it works! Without any default Setter, Visibility property, DataTemplates or ControlTemplates. Just the style set. I'm quite new to WPF programming. Is it a good practice to do it like so? – yaugenka Jun 05 '19 at 20:49
  • @yaugenka There’s no harm in it; it’s just that the ContentControl is unnecessary, since its direct parent is also a type of ContentControl. The first thing I tried was just what you did, then I cleaned it up and generalized it. – 15ee8f99-57ff-4f92-890c-b56153 Jun 05 '19 at 20:59
  • I think it would make sense when there are several controls within one StatusBarItem, e.g. wrapped with a StackPanel. – yaugenka Jun 05 '19 at 21:32
  • @yaugenka Yes, If you had several controls would have to have a parent that can contain multiple controls. – 15ee8f99-57ff-4f92-890c-b56153 Jun 05 '19 at 22:04
  • Thanks for the help, Ed – yaugenka Jun 05 '19 at 22:21