0

wpf C# xaml

In my Generic.xaml, I have many styles of the form:

  <Style x:Key="ToggleButtonStyle12" TargetType="{x:Type ToggleButton}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type ToggleButton}">
                    <Grid>
                        <Path x:Name="path1" Data="{StaticResource InsideQuarter3}" Fill="DarkOrange" Stroke="Black" />
                        <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" RecognizesAccessKey="True" 
                                          SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
                    </Grid>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsMouseOver" Value="true">
                            <Setter TargetName="path1" Property = "Opacity" Value="0.4"/>
                        </Trigger>
                        <Trigger Property="IsChecked" Value="true">
                            <Trigger.EnterActions>
                                <BeginStoryboard Storyboard="{StaticResource Blink_On}"/>
                            </Trigger.EnterActions>
                            <Trigger.ExitActions>
                                <BeginStoryboard Storyboard="{StaticResource Blink_Off}"/>
                            </Trigger.ExitActions>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

The only difference between all these toggle buttons is the Path definition and the Fill color.

Could this style be templated/simplified in such a way where only the Path and Fill color need be supplied?

That is something along the lines of :

<Style x:Key="ToggleButtonStyle12" BasedOn(??????)>
    <Setter Property = "Path" Value="InsideQuarter3"/>
    <Setter Property = "Fill" Value="DarkOrange"/>
</Style>

Thank you for any help.

Edit#1 Well, I thought I had it--I was wrong. The below code will correctly set the Path Data and Fill properties. However, only the first created ToggleButton keeps the "MouseOver" and other ControlTemplate.Triggers. I need ALL the toggle buttons in the RingControl to respect their own triggers.

  public static class ButtonProperties
    {
        public static Color GetMyForegroundColor(DependencyObject obj)
        {
            return (Color)obj.GetValue(MyForegroundColorProperty);
        }

        public static void SetMyForegroundColor(DependencyObject obj, Color value)
        {
            obj.SetValue(MyForegroundColorProperty, value);
        }

        // Using a DependencyProperty as the backing store for MyForegroundColor.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty MyForegroundColorProperty =
            DependencyProperty.RegisterAttached("MyForegroundColor", typeof(Color), typeof(ButtonProperties), new PropertyMetadata(Colors.Black));



        public static Geometry GetData(DependencyObject obj)
        {
            return (Geometry)obj.GetValue(DataProperty);
        }

        public static void SetData(DependencyObject obj, Geometry value)
        {
            obj.SetValue(DataProperty, value);
        }

        // Using a DependencyProperty as the backing store for Data.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty DataProperty =
            DependencyProperty.RegisterAttached("Data", typeof(Geometry), typeof(ButtonProperties), new PropertyMetadata(null));



        public static Brush GetFill(DependencyObject obj)
        {
            return (Brush)obj.GetValue(FillProperty);
        }

        public static void SetFill(DependencyObject obj, Brush value)
        {
            obj.SetValue(FillProperty, value);
        }

        // Using a DependencyProperty as the backing store for Fill.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty FillProperty =
            DependencyProperty.RegisterAttached("Fill", typeof(Brush), typeof(ButtonProperties), new PropertyMetadata(null));

    }

Generic.xaml -- BaseButtonStyle

  <Style x:Key="BaseButtonStyle" TargetType="{x:Type ToggleButton}">
        <Setter Property="local:ButtonProperties.MyForegroundColor" Value="Blue"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type ToggleButton}">
                    <Grid>
                    <TextBlock Text="Some Text">
                        <TextBlock.Foreground>
                            <SolidColorBrush Color="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(local:ButtonProperties.MyForegroundColor)}" />
                        </TextBlock.Foreground>
                    </TextBlock>

                        <Path x:Name="path1" Data="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(local:ButtonProperties.Data)}" 
                              Fill="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(local:ButtonProperties.Fill)}" 
                              Stroke="Black"/>

                        <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" RecognizesAccessKey="True" 
                                          SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
                    </Grid>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsMouseOver" Value="true">
                            <Setter TargetName="path1" Property = "Opacity" Value="0.4"/>
                        </Trigger>
                        <Trigger Property="IsChecked" Value="true">
                            <Trigger.EnterActions>
                                <BeginStoryboard Storyboard="{StaticResource Blink_On}"/>
                            </Trigger.EnterActions>
                            <Trigger.ExitActions>
                                <BeginStoryboard Storyboard="{StaticResource Blink_Off}"/>
                            </Trigger.ExitActions>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

Generic.xaml -- ModifiedButtonStyle1

 <Style x:Key="ModifiedButtonStyle1" TargetType="{x:Type ToggleButton}" BasedOn="{StaticResource BaseButtonStyle}">
        <Setter Property="local:ButtonProperties.MyForegroundColor" Value="Red" />
        <Setter Property="local:ButtonProperties.Data" Value="{StaticResource Arc0}" />
        <Setter Property="local:ButtonProperties.Fill" Value="LightGreen"/>
    </Style>
    <Style x:Key="ModifiedButtonStyle2" TargetType="{x:Type ToggleButton}" BasedOn="{StaticResource BaseButtonStyle}">
        <Setter Property="local:ButtonProperties.MyForegroundColor" Value="Red" />
        <Setter Property="local:ButtonProperties.Data" Value="{StaticResource Arc45}" />
        <Setter Property="local:ButtonProperties.Fill" Value="LightPink"/>
    </Style>
    <Style x:Key="ModifiedButtonStyle3" TargetType="{x:Type ToggleButton}" BasedOn="{StaticResource BaseButtonStyle}">
        <Setter Property="local:ButtonProperties.MyForegroundColor" Value="Red" />
        <Setter Property="local:ButtonProperties.Data" Value="{StaticResource Arc90}" />
        <Setter Property="local:ButtonProperties.Fill" Value="LightCoral"/>
    </Style>

Generic.xaml -- Using the ModifiedButtonStyles in a custom control, RingControl

 <Style TargetType="{x:Type local:RingButtons2}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:RingButtons2}">
                <Border Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}">

                    <Viewbox>
                        <Grid>
                            <!--Outer Rim -->
                            <Path Data="{StaticResource OuterRim}"  Fill="Silver" Stroke="Black" />
                            <Path Data="{StaticResource OuterWheelBackground}" Fill="White" Stroke="Black" />

                            <ToggleButton x:Name="PART_Button1" Style="{StaticResource ModifiedButtonStyle1}"/>
                            <ToggleButton x:Name="PART_Button2" Style="{StaticResource ModifiedButtonStyle2}"/>
                            <ToggleButton x:Name="PART_Button3" Style="{StaticResource ModifiedButtonStyle3}"/>
  ........................................................
  </Grid>
                        </Viewbox>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

Using the RingControl2 in the user interface:

       <w:RingButtons2/>

It would appear that clicking anywhere in the RingButtons2 control results only in the first defined togglebutton responding -- not any of the others.

How can I fix this so that each of the togglebuttons acts independently of the others and respects its own controltemplate triggers?

Thanks again.

Edit#2

Upon removing the TextBlock definition from the BaseButtonStyle,

 <TextBlock Text="Some Text">
                        <TextBlock.Foreground>
                            <SolidColorBrush Color="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(local:ButtonProperties.MyForegroundColor)}" />
                        </TextBlock.Foreground>
                    </TextBlock>

ALL WORKS! Why is this??

Thanks.

Alan Wayne
  • 5,122
  • 10
  • 52
  • 95

1 Answers1

2

If the target control has dependency properties that you can use for those custom bindings then you can use TemplateBinding, here's an example:

    <Style x:Key="BaseButtonStyle" TargetType="{x:Type Button}">
        <Setter Property="Foreground" Value="Blue" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type Button}">
                    <TextBlock Text="Some Text" Foreground="{TemplateBinding Foreground}" />
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

    <Style x:Key="ModifiedButtonStyle" TargetType="{x:Type Button}" BasedOn="{StaticResource BaseButtonStyle}">
        <Setter Property="Foreground" Value="Red" />
    </Style>

Which you would then refer to like so:

<Grid>
    <Button Style="{StaticResource ModifiedButtonStyle}" />
</Grid>

However, in the example you've given above you're using a Path and a Fill inside a template, which I'm guessing don't have associated properties. In this case your options are to either create a new control and add those properties to it or, preferably, use attached properties. In the case of the latter you'd create an attached property like this:

public static class ButtonProperties
{
    public static Color GetMyForegroundColor(DependencyObject obj)
    {
        return (Color)obj.GetValue(MyForegroundColorProperty);
    }

    public static void SetMyForegroundColor(DependencyObject obj, Color value)
    {
        obj.SetValue(MyForegroundColorProperty, value);
    }

    // Using a DependencyProperty as the backing store for MyForegroundColor.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty MyForegroundColorProperty =
        DependencyProperty.RegisterAttached("MyForegroundColor", typeof(Color), typeof(ButtonProperties), new PropertyMetadata(Colors.Black));
}

And then you'd reference and override it like this in your XAML:

<Style x:Key="BaseButtonStyle" TargetType="{x:Type Button}">
    <Setter Property="behaviors:ButtonProperties.MyForegroundColor" Value="Blue"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type Button}">
                <TextBlock Text="Some Text">
                    <TextBlock.Foreground>
                        <SolidColorBrush Color="{Binding RelativeSource={RelativeSource TemplatedParent},
                            Path=(behaviors:ButtonProperties.MyForegroundColor)}" />
                    </TextBlock.Foreground>
                </TextBlock>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

<Style x:Key="ModifiedButtonStyle" TargetType="{x:Type Button}" BasedOn="{StaticResource BaseButtonStyle}">
    <Setter Property="behaviors:ButtonProperties.MyForegroundColor" Value="Red" />
</Style>

These are more-or-less the same, it's just that in the first case you're using existing properties already inside the control (or creating a new control that has them) whereas in the second case you're declaring and attaching them to an existing control externally.

Mark Feldman
  • 15,731
  • 3
  • 31
  • 58
  • It looks very promising, but I will need some sleep before checking this. Thanks. :) – Alan Wayne Dec 30 '18 at 06:32
  • 1
    Excellent! The syntax is completely new to me, but works perfectly. Thank you much! – Alan Wayne Dec 30 '18 at 20:08
  • Actually you've probably been using them all along without realizing. Whenever you add something like `Grid.Row="0"` to a control...that isn't an internal property that every WPF control supports, it's an attached property that the Grid class creates and references (when present) during the layout process. – Mark Feldman Dec 30 '18 at 21:09
  • Sorry...Upon further work, the Data and Fill properties work correctly for each ModifiedButtonStyle. However, the controltemplate triggers only seem to work for the first defined button--no other button seems to respond visually to the triggers. Any suggestions?? Thanks. (Please see Edit#1). – Alan Wayne Dec 31 '18 at 04:04
  • Upon removing the TextBlock element from the BaseButtonStyle, the controltemplate triggers now work correctly for each button...Why??? Thanks. :) – Alan Wayne Dec 31 '18 at 04:21
  • What happens if you leave the TextBlock element in and add `x:Shared="False"` to the ControlTemplate tag? – Mark Feldman Dec 31 '18 at 05:52
  • 'Shared attribute in namespace 'http://schemas.microsoft.com/winfx/2006/xaml' can be used only in compiled resource dictionaries.' -- apparently not in Generic.xaml :( – Alan Wayne Dec 31 '18 at 18:50