Dat8StringGuy
Dat8StringGuy

Reputation: 351

Overriding IsMouseOver on ControlTemplate makes Button disappear

I'm new to WPF, and I'm trying to define a button style for the buttons in my app. I've added the following ResourceDictionary and added it in the App's resources in App.xaml:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:local="clr-namespace:ReciepeFinder">

<Style x:Key="NavButton" TargetType="Button">

    <Setter Property="Width"
            Value="120" />
    <Setter Property="Height"
            Value="120" />
    <Setter Property="BorderThickness"
            Value="0" />

    <Setter Property="Background">
        <Setter.Value>
            <ImageBrush ImageSource="/ReciepeFinder;component/resources/images/home.png" />
        </Setter.Value>
    </Setter>

    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="Button">
                <ControlTemplate.Triggers>
                    <Trigger Property="IsMouseOver"
                             Value="True">
                        <Setter Property="Background">
                            <Setter.Value>
                                <ImageBrush ImageSource="/ReciepeFinder;component/resources/images/home_selected.png" />
                            </Setter.Value>
                        </Setter>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>

</Style>

Basically, on no hover, I want the button to show resources/images/home.png and on hover, resources/images/home_selected.png. When I try to define the ControlTemplate as in the code above and I run the app, the button just doesn't appear. However, if I remove the ControlTemplate tag entirely, the button is there with the proper Background, but obviously no hover effect. What am I doing wrong?

Bonus question: If I wanted to use binding so that I could just set the path for the no hover image and the on hover image in other buttons' attributes and have it behave the same way, how could i achieve that?

Upvotes: 0

Views: 818

Answers (2)

Konstantin Borisov
Konstantin Borisov

Reputation: 149

Because when you are setting the ControlTemplate you basically have to redefine the way the control is displayed. And you have defined just the triggers without any content representation. The ContentPresenter control will help you with that. Just add some Border and Content presenter after the ControlTemplate.Triggers and ControlTemplate. This should do the trick:

</ControlTemplate.Triggers>
<Border Background="{TemplateBinding Background}">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
</ControlTemplate>

Upvotes: 1

thatguy
thatguy

Reputation: 22119

If you want to restyle the appearance of a control along with its visual states, you have to edit its ControlTemplate. However, a control template contains definitions for mandatory parts of a control, as well as its appearance in different states and transitions between them. To make a ControlTemplate work as expected, you have to implement all of these parts and states. You can find the information on required parts ans states for each built-in control on MSDN, e.g. for Button.

Instead of creating your ControlTemplate from scratch, you can copy the default template and adapt it, which is much easier. You can extract it with Blend or Visual Studio.

Your control template does not work, because it is essentially empty. There is nothing to display a background or any content. It does not contain any visuals or controls. I have created a sample control template for you that should fit your requirements.

<Style x:Key="NavButton" TargetType="{x:Type Button}">
   <Setter Property="Background">
      <Setter.Value>
         <ImageBrush ImageSource="/ReciepeFinder;component/resources/images/home.png" />
      </Setter.Value>
   </Setter>
   <Setter Property="BorderThickness" Value="0"/>
   <Setter Property="HorizontalContentAlignment" Value="Center"/>
   <Setter Property="VerticalContentAlignment" Value="Center"/>
   <Setter Property="Padding" Value="1"/>
   <Setter Property="Template">
      <Setter.Value>
         <ControlTemplate TargetType="{x:Type Button}">
            <Border x:Name="border" Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}" SnapsToDevicePixels="true"/>
            <ControlTemplate.Triggers>
               <Trigger Property="IsMouseOver" Value="true">
                  <Setter Property="Background" TargetName="border">
                     <Setter.Value>
                        <ImageBrush ImageSource="/ReciepeFinder;component/resources/images/home_selected.png" />
                     </Setter.Value>
                  </Setter>
               </Trigger>
               <Trigger Property="IsEnabled" Value="false">
                  <Setter TargetName="border" Property="Opacity" Value="0.5"/>
               </Trigger>
            </ControlTemplate.Triggers>
         </ControlTemplate>
      </Setter.Value>
   </Setter>
</Style>

The Border displays the image background. In general, you would put a ContentPresenter inside the Border, because it would show the content that you assigned to the Content property of your Button, but since you display images instead of the content I have omitted that.

Please note that there are TemplateBindings inside this template. These are used to bind the specified properties from the control that the template is applied on. In other words, if you set the Background on a button directly or create Style derived from NavButton where you set it, this value would be used. If you just hardcoded the value in the control template, it would not change.

For your bonus question: When you use these TemplateBindings, setting the Background directly or in a derived Style will work as stated above, so you can reuse it. As there is no HoverBackground property, you would either have to create a derived button and add a dependency property to that, create an attached property or use e.g. the unused Foreground property for that, although that does not feel right, as it is a dirty workaround. This is an example for Foreground, just because it is easy to test for you, but it works for the other options stated above, too.

<Setter Property="Foreground">
   <Setter.Value>
      <ImageBrush ImageSource="/ReciepeFinder;component/resources/images/home_selected.png" />
   </Setter.Value>
</Setter>
<Trigger Property="IsMouseOver" Value="true">
   <Setter Property="Background" TargetName="border" Value="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Foreground}"/>
</Trigger>

Please note, that for accessing a property of the templated parent in Setter, you have to use a different binding syntax, that has the same effect as TemplateBinding.

Upvotes: 1

Related Questions