WPF Trigger won't set property if set in Element

This behavior seems incredibly odd to me, and I assume I am doing something wrong to get it. I have a ContentControl that uses a DataTemplete to render an TabControl. I want an image to display when there are no tabs open, and hide when there are. But here is the problem:

<Image Name="image1" Stretch="Uniform" Visibility="Hidden" Source="/Affinity;component/Images/affinity_logo.png">
            <Image.Style>
                <Style TargetType="Image">
                    <Style.Triggers>
                        <DataTrigger Binding="{Binding Items.Count, ElementName=tabcontrolworkspaces}"
                        Value="0">
                            <Setter Property="Visibility" Value="Visible" />
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </Image.Style>
        </Image>

This doesn't work... sort of.

I have tested this on Visiblity and Margin (just to be sure). This trigger will alter the property, unless that property is defined in the Image tags. If it is, the trigger will not update that property. So, if I don't define a visibility for the image, and the trigger hides it, it works. The problem is, the default is Visible and the trigger needs to show it when value=0 and hide it otherwise.

Why won't the trigger override properties that are explicitly defined? Isn't that its purpose?


Solution 1:

This is the normal Dependency Property Value Precedence. Setting it on Image is at #3, while in the Style trigger is at a lower precedence of #6.

You can do this instead:

<Image Name="image1" Stretch="Uniform" Source="/Affinity;component/Images/affinity_logo.png">
    <Image.Style>
        <Style TargetType="Image">
            <Setter Property="Visibility" Value="Hidden" />
            <Style.Triggers>
                <DataTrigger Binding="{Binding Items.Count, ElementName=tabcontrolworkspaces}"
                    Value="0">
                    <Setter Property="Visibility" Value="Visible" />
                </DataTrigger>
            </Style.Triggers>
        </Style>
    </Image.Style>
</Image>

Solution 2:

Set your Visibility in the Style in addition to in the Trigger

I've encountered this weird behavior with DataTriggers many times, where sometimes DataTrigger Setters won't take effect unless the Setter is also defined in the Style.

Won't work

<Image Visibility="Collapsed">
    <Image.Style>
        <Style TargetType="Image">
            <Style.Triggers>
                <DataTrigger Binding="{Binding Something}" Value="0">
                    <Setter Property="Visibility" Value="Visible" />
                </DataTrigger>
            </Style.Triggers>
        </Style>
    <Image.Style>
</Image>

Will work

<Image>
    <Image.Style>
        <Style TargetType="Image">
            <Setter Property="Visibility" Value="Collapsed" />
            <Style.Triggers>
                <DataTrigger Binding="{Binding Something}" Value="0">
                    <Setter Property="Visibility" Value="Visible" />
                </DataTrigger>
            </Style.Triggers>
        </Style>
    <Image.Style>
</Image>

Edit: See the accepted answer for an explanation on why this doesn't work. It has to do with the order in which dependency properties are determined, where properties defined in the <Tag> always take precedence over triggered values.