How to target a template setter at non-element content

Here’s a technique you can follow to use property triggers in a template on non-element type objects. First, though, some background on what that means …

 

Take this example of a Button with a custom template which is simply a rectangle:

 

<Button>

  <Button.Template>

    <ControlTemplate TargetType="Button">

      <Rectangle Width='60' Height='40' Fill='Red' x:Name='MyRectangle'>

      </Rectangle>

      <ControlTemplate.Triggers>

        <Trigger Property="IsMouseOver" Value="True">

          <Setter TargetName="MyRectangle" Property="Fill" Value="Green"/>

        </Trigger>

      </ControlTemplate.Triggers>

    </ControlTemplate>

  </Button.Template>

</Button>

 

… the rectangle is Red, but changes to Green when the mouse goes over it.

 

Now fill the Rectangle with a LinearGradientBrush, rather than the SolidColorBrush:

 

<Button>

<Button.Template>

<ControlTemplate TargetType="Button">

<Rectangle Width='60' Height='40' x:Name='MyRectangle'>

<Rectangle.Fill>

<LinearGradientBrush >

<GradientStop Color='Red' />

<GradientStop Color='Blue' Offset='1' x:Name='SecondStop' />

</LinearGradientBrush>

</Rectangle.Fill>

</Rectangle>

<ControlTemplate.Triggers>

<Trigger Property="IsMouseOver" Value="True">

<Setter TargetName="MyRectangle" Property="Fill" Value="Green"/>

</Trigger>

</ControlTemplate.Triggers>

</ControlTemplate>

</Button.Template>

</Button>

 

 

… and now the Rectangle is red-to-blue by default, but still the Rectangle changes to green when the mouse is over it.

 

Now say you want the mouse-over behavior to keep LinearGradientBrush, but to instead just modify the second stop to green. One way you could do that would be to have two copies of the LinearGradientBrush:

 

<Button>

  <Button.Template>

    <ControlTemplate TargetType="Button">

      <Rectangle Width='60' Height='40' x:Name='MyRectangle'>

        <Rectangle.Fill>

          <LinearGradientBrush >

            <GradientStop Color='Red' />

            <GradientStop Color='Blue' Offset='1' x:Name='SecondStop' />

          </LinearGradientBrush>

        </Rectangle.Fill>

      </Rectangle>

      <ControlTemplate.Triggers>

        <Trigger Property="IsMouseOver" Value="True">

          <Setter TargetName='MyRectangle' Property="Fill" >

            <Setter.Value>

              <LinearGradientBrush >

                <GradientStop Color='Red' />

                <GradientStop Color= 'Green' Offset='1' />

              </LinearGradientBrush>

            </Setter.Value>

          </Setter>

        </Trigger>

      </ControlTemplate.Triggers>

    </ControlTemplate>

  </Button.Template>

</Button>

 

But if you have a more complicated object, such as a Drawing, you might not want to have to duplicate the entire thing. The difficulty is that the Setter of a Trigger can only target a FrameworkElement or FrameworkContentElement, not (in this case) a GradientStop. For example, if you tried make the second stop green with a trigger:

 

<Button>

  <Button.Template>

    <ControlTemplate TargetType="Button">

      <Rectangle Width='60' Height='40' x:Name='MyRectangle'>

        <Rectangle.Fill>

          <LinearGradientBrush >

            <GradientStop Color='Red' />

            <GradientStop Color='Blue' Offset='1' x:Name='SecondStop' />

          </LinearGradientBrush>

        </Rectangle.Fill>

      </Rectangle>

      <ControlTemplate.Triggers>

        <Trigger Property="IsMouseOver" Value="True">

          <Setter TargetName="SecondStop" Property="Color" Value="Green"/>

        </Trigger>

      </ControlTemplate.Triggers>

    </ControlTemplate>

  </Button.Template>

</Button>

 

… you’ll get the error “Cannot find the Trigger target 'SecondStop'. (The target must appear before any Setters, Triggers, or Conditions that use it.) ”.

 

There is a way to solve this, though, by using data binding. With a Binding, you can find your way out of the brush to an element, and then the Setter can target that element. For example, the following markup gives us the desired LinearGradientBrush of red-to-blue when the mouse is away from the button, and red-to-green when the mouse is over it:

 

<Button>

  <Button.Template>

    <ControlTemplate TargetType="Button">

      <Rectangle Width='60' Height='40' x:Name='MyRectangle' >

        <Rectangle.Tag>

          <Color>Blue</Color>

        </Rectangle.Tag>

        <Rectangle.Fill>

          <LinearGradientBrush >

            <GradientStop Color='Red' />

            <GradientStop

                  Color='{Binding ElementName=MyRectangle, Path=Tag}'

                  Offset='1' x:Name='SecondStop' />

          </LinearGradientBrush>

        </Rectangle.Fill>

      </Rectangle>

      <ControlTemplate.Triggers>

        <Trigger Property="IsMouseOver" Value="True">

          <Setter TargetName="MyRectangle" Property="Tag" Value="Green"/>

        </Trigger>

      </ControlTemplate.Triggers>

    </ControlTemplate>

  </Button.Template>

</Button>