Solved

Button Styling with ControlTemplate

Posted on 2011-03-10
10
929 Views
Last Modified: 2013-11-12
If I assign a brush to fill a rectangle within a Button.Content tag the image renders correctly and I get the expected mouseover highlighting behavior.

If I do the exact same thing in a style(within a resource dictionary) the mouseover highlighting no longer occurs.  Here is the Style Xaml:
1)  {StaticResource undo} resolves to a DrawingBrush in an included resourcedictionary.
2)  The IsEnabled trigger fires properly and operates as expected.

    <Style TargetType="{x:Type Button}" x:Key="Undo" BasedOn="{StaticResource {x:Type Button}}">
        <Setter Property="Command" Value="ApplicationCommands.Undo"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type Button}">
                    <StackPanel Name="PART_Backplane" Orientation="Horizontal">
                        <Rectangle Name="PART_Image" Width="16" Height="16" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Fill="{StaticResource undo}"/>
                    </StackPanel>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsEnabled" Value="False">
                            <Setter TargetName="PART_Image" Property="Fill" Value="{StaticResource undoDisabled}"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

So my question is:  How can I recover/re-implement the default behavior.
0
Comment
Question by:Choran6619
  • 6
  • 3
10 Comments
 
LVL 96

Expert Comment

by:Bob Learned
Comment Utility
I don't see any triggers for the mouse over?  How was that working in the Button.Content tag?
0
 
LVL 25

Expert Comment

by:apeter
Comment Utility
I'm not seeing anything regarding mouseover event. Can we have the complete xaml?
0
 

Author Comment

by:Choran6619
Comment Utility
That is the point.  In a normal button, without a style applied the mouseover works correctly.  As soon as I apply this style the mouseover no longer works, even though the style is based on {x:Type Button}
0
 

Author Comment

by:Choran6619
Comment Utility
Just to clarify,

My goal is to use different brushes for enabled/disabled states.  This template does that just fine, an unexpected side-effect is the loss of mouseover functionality.  The side effect is unexpected since the style is based on {x:Type Button}.
0
 
LVL 25

Expert Comment

by:apeter
Comment Utility
Try below using >style.triggers>

<Style TargetType="{x:Type Button}" x:Key="Undo" BasedOn="{StaticResource {x:Type Button}}">
        <Setter Property="Command" Value="ApplicationCommands.Undo"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type Button}">
                    <StackPanel Name="PART_Backplane" Orientation="Horizontal">
                        <Rectangle Name="PART_Image" Width="16" Height="16" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Fill="{StaticResource undo}"/>
                    </StackPanel>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsEnabled" Value="False">
                            <Setter TargetName="PART_Image" Property="Fill" Value="{StaticResource undoDisabled}"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
       <Style.Triggers>         <Trigger Property="IsMouseOver" Value="True">             <Setter Property="Background" Value="Beige" />         </Trigger>     </Style.Triggers>

    </Style>

0
How to run any project with ease

Manage projects of all sizes how you want. Great for personal to-do lists, project milestones, team priorities and launch plans.
- Combine task lists, docs, spreadsheets, and chat in one
- View and edit from mobile/offline
- Cut down on emails

 

Author Comment

by:Choran6619
Comment Utility
@apeter, no matter what as soon as you set a Style you seem to lose the default behaviors...

I apologize for the wall of XAML, but this sample shows exactly what I mean.  The button on the left has a style(The style does not include a controltemplate) applied and it loses the default behaviors (mouseover, highlight, press,....)  The button on the right is using a contenttemplate and still has the default behaviors...this seems to be much harder than it really should be :(

The code block below can be pasted into Kaxaml to demonstrate....
<Page
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <Page.Resources>  	
		<DrawingBrush x:Key="undo" Stretch="Uniform">
		<DrawingBrush.Drawing>
			<DrawingGroup>
				<DrawingGroup.Children>
					<GeometryDrawing Geometry="F1 M 10.7197,28.7708C 9.96263,28.6955 16.0499,19.2414 18.5065,14.0811C 20.963,8.92094 19.7888,8.05463 18.8155,7.62147C 17.8421,7.18831 17.0696,7.18831 15.4165,8.58195C 13.7633,9.97558 11.2295,12.7628 10.2099,14.665C 10.0436,14.9753 9.91754,15.262 9.81778,15.5268L 13.3333,19.1875L 1.65958,19.2889L 1.5522,6.92002L 6.74321,12.3253C 7.239,12.3224 7.76302,12.2135 8.30948,11.8777C 10.1789,10.7289 12.311,6.92465 14.7655,5.41874C 17.22,3.91282 19.9969,4.70522 22.0363,5.89169C 24.0757,7.07816 25.3776,8.6587 22.2279,14.1007C 19.0781,19.5427 11.4767,28.8461 10.7197,28.7708 Z ">
						<GeometryDrawing.Pen>
							<Pen LineJoin="Round" Brush="#FF000000"/>
						</GeometryDrawing.Pen>
						<GeometryDrawing.Brush>
							<LinearGradientBrush StartPoint="-0.0221584,0.487491" EndPoint="1.07802,0.487491">
								<LinearGradientBrush.GradientStops>
									<GradientStop Color="#FF0D5BE9" Offset="0"/>
									<GradientStop Color="#FFF6F7F9" Offset="1"/>
								</LinearGradientBrush.GradientStops>
							</LinearGradientBrush>
						</GeometryDrawing.Brush>
					</GeometryDrawing>
				</DrawingGroup.Children>
			</DrawingGroup>
		</DrawingBrush.Drawing>
	</DrawingBrush>
	  <DrawingBrush x:Key="undoDisabled" Stretch="Uniform">
		<DrawingBrush.Drawing>
			<DrawingGroup>
				<DrawingGroup.Children>
					<GeometryDrawing Geometry="F1 M 10.7197,28.7708C 9.96263,28.6955 16.0499,19.2414 18.5065,14.0811C 20.963,8.92094 19.7888,8.05463 18.8155,7.62147C 17.8421,7.18831 17.0696,7.18831 15.4165,8.58195C 13.7633,9.97558 11.2295,12.7628 10.2099,14.665C 10.0436,14.9753 9.91754,15.262 9.81778,15.5268L 13.3333,19.1875L 1.65958,19.2889L 1.5522,6.92002L 6.74321,12.3253C 7.239,12.3224 7.76302,12.2135 8.30948,11.8777C 10.1789,10.7289 12.311,6.92465 14.7655,5.41874C 17.22,3.91282 19.9969,4.70522 22.0363,5.89169C 24.0757,7.07816 25.3776,8.6587 22.2279,14.1007C 19.0781,19.5427 11.4767,28.8461 10.7197,28.7708 Z ">
						<GeometryDrawing.Pen>
							<Pen LineJoin="Round" Brush="#FF949594"/>
						</GeometryDrawing.Pen>
						<GeometryDrawing.Brush>
							<LinearGradientBrush StartPoint="-0.0221584,0.487491" EndPoint="1.07802,0.487491">
								<LinearGradientBrush.GradientStops>
									<GradientStop Color="#FF202021" Offset="0"/>
									<GradientStop Color="#FF8C8E93" Offset="1"/>
								</LinearGradientBrush.GradientStops>
							</LinearGradientBrush>
						</GeometryDrawing.Brush>
					</GeometryDrawing>
				</DrawingGroup.Children>
			</DrawingGroup>
		</DrawingBrush.Drawing>
	</DrawingBrush>

	<DataTemplate x:Key="undocontent">
	  <StackPanel Name="PART_Backplane" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Orientation="Horizontal">
	    <Rectangle Name="PART_Image" Width="18" Height="18" Fill="{StaticResource undo}"/>
	  </StackPanel>
	  <DataTemplate.Triggers>
	    <DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor,Button,1},Path=IsMouseOver}" Value="True">
	      <Setter TargetName="PART_Image" Property="Fill" Value="{StaticResource undoDisabled}"/>
	    </DataTrigger>
	  </DataTemplate.Triggers>
	</DataTemplate>
	<Style x:Key="UndoButton" TargetType="{x:Type Button}" BasedOn="{StaticResource {x:Type Button}}" >
	  <Setter Property="Command" Value="ApplicationCommands.Undo"/>
	   <Setter Property="ContentTemplate" Value="{StaticResource undocontent}"/>
	</Style>
</Page.Resources>
  <Grid>  
  <Grid.ColumnDefinitions>
    <ColumnDefinition Width="Auto"/>
    <ColumnDefinition Width="Auto"/>
  </Grid.ColumnDefinitions>
    <Button Grid.Column="1" Width="24" Height="24" ContentTemplate="{StaticResource undocontent}"/>
    <Button Grid.Column="0" Width="24" Height="24" Style="{StaticResource UndoButton}"/>
  </Grid>
</Page>

Open in new window

0
 

Author Comment

by:Choran6619
Comment Utility
Well this is frustrating...changing the Xaml to the following (I'll skip the brush definitions) gives the expected behaviors.  The only changes are:
1)  Change RelativeSource to TemplatedParent from FindAncenstor (both resolve to the same object!)
2)  Remove the BasedOn attribute from the Style definition!

There does not seem to be any rhyme or reason to why these changes work....
<DataTemplate x:Key="undocontent">
        <StackPanel Name="PART_Backplane" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Orientation="Horizontal">
            <Rectangle Name="PART_Image" Width="18" Height="18" Fill="{StaticResource undo}"/>
        </StackPanel>
        <DataTemplate.Triggers>
            <DataTrigger Binding="{Binding RelativeSource={RelativeSource TemplatedParent},Path=IsEnabled}" Value="False">
                <Setter TargetName="PART_Image" Property="Fill" Value="{StaticResource undoDisabled}"/>
            </DataTrigger>
        </DataTemplate.Triggers>
    </DataTemplate>
    <Style x:Key="UndoButton" TargetType="{x:Type Button}">
        <Setter Property="Command" Value="ApplicationCommands.Undo"/>        
        <Setter Property="ContentTemplate" Value="{StaticResource undocontent}"/>
    </Style>

Open in new window

0
 
LVL 25

Expert Comment

by:apeter
Comment Utility
Ohh..then looks like we have to provide default behaviors..try below code sample.


<Style x:Key="MouseOverButtonStyle" TargetType="Button">    <Setter Property="Template">        <Setter.Value>            <ControlTemplate TargetType="Button">                <ControlTemplate.Resources>                    <Style x:Key="ShadowStyle">                        <Setter Property="Control.Foreground" Value="LightGray" />                    </Style>                </ControlTemplate.Resources>                <Border Name="border" BorderThickness="1" Padding="4,2" BorderBrush="DarkGray" CornerRadius="3" Background="{TemplateBinding Background}">                    <Grid >                        <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" Name="contentShadow" Style="{StaticResource ShadowStyle}">                            <ContentPresenter.RenderTransform>                                <TranslateTransform X="1.0" Y="1.0" />                            </ContentPresenter.RenderTransform>                        </ContentPresenter>                        <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" Name="content"/>                    </Grid>                </Border>            </ControlTemplate>        </Setter.Value>    </Setter>    <Style.Triggers>        <Trigger Property="IsMouseOver" Value="True">            <Setter Property="Background" Value="Beige" />        </Trigger>    </Style.Triggers></Style>

Open in new window

0
 

Accepted Solution

by:
Choran6619 earned 0 total points
Comment Utility
@apeter,  That certainly does give a mouseover effect....to extend that would mean that we have to re-implement default behaviors as soon we apply a style....  In any case I went an entirely different direction :D

The problems I needed to solve were
1)  Maintain base behaviors.
2) Supply separate images for enabled and disabled states
3) Have the image autosize to the button content area.

#1 was resolved by using a contenttemplate rather than a controltemplate for the buttons.
#2 was resolved by creating attached properties for the enabled and disabled brushes
#3 was resolved by creating an attached behavior for the content.

As a bonus I was able to put a handy enable/disable animation and a slight scale change for mouseover for the buttons as well :D

I'll post the code in the reverse order as the control template using the attached properties and the attached behavior:

====================Attached Behavior==============
    public static class ButtonContentBehaviors
    {

        public static readonly DependencyProperty AutoSizeProperty =
            DependencyProperty.RegisterAttached("AutoSize", typeof(bool), typeof(ButtonContentBehaviors),
                                                new UIPropertyMetadata(false, OnButtonContentAutoSizeChanged));

        public static bool GetAutoSize(DependencyObject dep)
        {
            if (dep == null) return false;
            return (bool)dep.GetValue(AutoSizeProperty);
        }
        public static void SetAutoSize(DependencyObject dep,bool value)
        {
            if (dep == null) return;
            dep.SetValue(AutoSizeProperty, value);
        }

        public static void OnButtonContentAutoSizeChanged(DependencyObject dep,DependencyPropertyChangedEventArgs e)
        {
            var fe = dep as FrameworkElement;
            if (fe == null) return;
            if (e.NewValue is bool == false) return;
            if ((bool)e.NewValue)
                fe.Initialized += FeInitialized;
            else
                fe.Initialized -= FeInitialized;
                
        }

        static void FeInitialized(object sender, EventArgs e)
        {
            var dep = sender as FrameworkElement;
            if (dep == null) return;
            var febtn = FindAncestorOrSelf<Button>(dep);
            var tpad = febtn.Padding;
            var tborder = febtn.BorderThickness;
            var total = tpad.Add(tborder);
            dep.Width = febtn.Width - total.Left - total.Right;
            dep.Height = febtn.Height - total.Top - total.Bottom;
        }

        public static T FindAncestorOrSelf<T>(DependencyObject obj) where T : DependencyObject
        {
            while (obj != null)
            {
                var objTest = obj as T;
                if (objTest != null)
                    return objTest;
                obj = GetParent(obj);
            }
            return null;
        }

        public static DependencyObject GetParent(DependencyObject obj)
        {

            if (obj == null)
                return null;
            var ce = obj as ContentElement;
            if (ce != null)
            {
                DependencyObject parent = ContentOperations.GetParent(ce);
                if (parent != null)
                    return parent;
                var fce = ce as FrameworkContentElement;
                return fce != null ? fce.Parent : null;
            }
            return VisualTreeHelper.GetParent(obj);
        }

    }

Open in new window


=================Attached Properties=================
public static class BrushTypes
    {
        #region Enabled
        public static readonly DependencyProperty EnabledProperty =
        DependencyProperty.RegisterAttached("Enabled", typeof(DrawingBrush), typeof(BrushTypes),
                                        new UIPropertyMetadata(new DrawingBrush(), OnDrawingBrushChanged));

        public static DrawingBrush GetEnabled(DependencyObject dep)
        {
            if (dep == null) return null;
            return (DrawingBrush)dep.GetValue(EnabledProperty);
        }
        public static void SetEnabled(DependencyObject dep, DrawingBrush value)
        {
            if (dep == null) return;
            dep.SetValue(EnabledProperty, value);
        }
        #endregion
        #region Disabled
        public static readonly DependencyProperty DisabledProperty =
        DependencyProperty.RegisterAttached("Disabled", typeof(DrawingBrush), typeof(BrushTypes),
                                new UIPropertyMetadata(new DrawingBrush(), OnDrawingBrushChanged));

        public static DrawingBrush GetDisabled(DependencyObject dep)
        {
            if (dep == null) return null;
            return (DrawingBrush)dep.GetValue(DisabledProperty);
        }
        public static void SetDisabled(DependencyObject dep, DrawingBrush value)
        {
            if (dep == null) return;
            dep.SetValue(DisabledProperty, value);
        }

        #endregion

        private static void OnDrawingBrushChanged(DependencyObject dep,DependencyPropertyChangedEventArgs e)
        {}
    }

Open in new window


==============The content Template================
    <Style x:Key="TSTbBtn" TargetType="{x:Type Button}">
        <Setter Property="ContentTemplate">
            <Setter.Value>
                <DataTemplate>
                    <Grid HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
                        <Rectangle Behaviors:ButtonContentBehaviors.AutoSize="True" Name="PART_Image" Fill="{Binding Path=(Attach:BrushTypes.Enabled), RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type Button}}}">
                            <Rectangle.RenderTransform>
                                <ScaleTransform ScaleX="1" ScaleY="1"/>
                            </Rectangle.RenderTransform>
                        </Rectangle>
                        <Rectangle Behaviors:ButtonContentBehaviors.AutoSize="True" Name="PART_DisabledBackground" Fill="{Binding Path=(Attach:BrushTypes.Disabled), RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type Button}}}" RenderTransformOrigin="1,0.5">
                            <Rectangle.RenderTransform>
                                <ScaleTransform ScaleX="0"/>
                            </Rectangle.RenderTransform>
                        </Rectangle>
                    </Grid>
                    <DataTemplate.Triggers>
                        <DataTrigger Binding="{Binding RelativeSource={RelativeSource TemplatedParent},Path=IsMouseOver}" Value="True">
                            <DataTrigger.EnterActions>
                                <BeginStoryboard>
                                    <Storyboard TargetName="PART_Image" TargetProperty="RenderTransform.ScaleX">
                                        <DoubleAnimation To="1.1" Duration="0:0:0.1" DecelerationRatio="0.25"/>
                                    </Storyboard>
                                </BeginStoryboard>
                                <BeginStoryboard>
                                    <Storyboard TargetName="PART_Image" TargetProperty="RenderTransform.ScaleY">
                                        <DoubleAnimation To="1.1" Duration="0:0:0.1" DecelerationRatio="0.25"/>
                                    </Storyboard>
                                </BeginStoryboard>
                            </DataTrigger.EnterActions>
                            <DataTrigger.ExitActions>
                                <BeginStoryboard>
                                    <Storyboard TargetName="PART_Image" TargetProperty="RenderTransform.ScaleX">
                                        <DoubleAnimation To="1" Duration="0:0:0.1" DecelerationRatio="0.25"/>
                                    </Storyboard>
                                </BeginStoryboard>
                                <BeginStoryboard>
                                    <Storyboard TargetName="PART_Image" TargetProperty="RenderTransform.ScaleY">
                                        <DoubleAnimation To="1" Duration="0:0:0.1" DecelerationRatio="0.25"/>
                                    </Storyboard>
                                </BeginStoryboard>
                            </DataTrigger.ExitActions>

                        </DataTrigger>
                            <DataTrigger Binding="{Binding RelativeSource={RelativeSource TemplatedParent},Path=IsEnabled}" Value="False">
                            <DataTrigger.EnterActions>
                                <BeginStoryboard>
                                    <Storyboard TargetName="PART_DisabledBackground" TargetProperty="RenderTransform.ScaleX">
                                        <DoubleAnimation To="1" Duration="0:0:0.5" DecelerationRatio="0.25"/>
                                    </Storyboard>
                                </BeginStoryboard>
                            </DataTrigger.EnterActions>
                            <DataTrigger.ExitActions>
                                <BeginStoryboard>
                                    <Storyboard TargetName="PART_DisabledBackground" TargetProperty="RenderTransform.ScaleX">
                                        <DoubleAnimation To="0" Duration="0:0:0.5" DecelerationRatio="0.25"/>
                                    </Storyboard>
                                </BeginStoryboard>
                            </DataTrigger.ExitActions>
                        </DataTrigger>
                    </DataTemplate.Triggers>
                </DataTemplate>
            </Setter.Value>
        </Setter>
    </Style>

Open in new window


And finally the payoff.....
        <ToolBarTray Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3">
            <ToolBar Name="Document">
                <Button Name="Undo" Command="ApplicationCommands.Undo" ToolTip="Undo" Attach:BrushTypes.Enabled="{StaticResource undo}"  Attach:BrushTypes.Disabled="{StaticResource undoDisabled}" Height="24" Width="24" CommandTarget="{Binding ElementName=rtf}" Style="{StaticResource TSTbBtn}"/>
                <Button Name="Redo" Command="ApplicationCommands.Redo" ToolTip="Redo"  Attach:BrushTypes.Enabled="{StaticResource redo}"  Attach:BrushTypes.Disabled="{StaticResource redoDisabled}" Height="24" Width="24" CommandTarget="{Binding ElementName=rtf}" Style="{StaticResource TSTbBtn}"/>
                <Separator/>
                <Button Name="Cut" Command="ApplicationCommands.Cut" ToolTip="Cut"  Attach:BrushTypes.Enabled="{StaticResource cut}"  Attach:BrushTypes.Disabled="{StaticResource cutDisabled}" Height="24" Width="24" CommandTarget="{Binding ElementName=rtf}" Style="{StaticResource TSTbBtn}"/>
                <Button Name="Copy" Command="ApplicationCommands.Copy" ToolTip="Copy"  Attach:BrushTypes.Enabled="{StaticResource copy}"  Attach:BrushTypes.Disabled="{StaticResource copyDisabled}" Height="24" Width="24" CommandTarget="{Binding ElementName=rtf}" Style="{StaticResource TSTbBtn}"/>
                <Button Name="Paste" Command="ApplicationCommands.Paste" ToolTip="Paste"  Attach:BrushTypes.Enabled="{StaticResource paste}"  Attach:BrushTypes.Disabled="{StaticResource pasteDisabled}" Height="24" Width="24" CommandTarget="{Binding ElementName=rtf}" Style="{StaticResource TSTbBtn}"/>
            </ToolBar>

Open in new window

0
 

Author Closing Comment

by:Choran6619
Comment Utility
The answer was a result of a learning curve, I have no guarantee that this is the best approach, but it does meet the given criteria.
0

Featured Post

What Security Threats Are You Missing?

Enhance your security with threat intelligence from the web. Get trending threat insights on hackers, exploits, and suspicious IP addresses delivered to your inbox with our free Cyber Daily.

Join & Write a Comment

For most people, the WrapPanel seems like a magic when they switch from WinForms to WPF. Most of us will think that the code that is used to write a control like that would be difficult. However, most of the work is done by the WPF engine, and the W…
Calculating holidays and working days is a function that is often needed yet it is not one found within the Framework. This article presents one approach to building a working-day calculator for use in .NET.
This is Part 3 in a 3-part series on Experts Exchange to discuss error handling in VBA code written for Excel. Part 1 of this series discussed basic error handling code using VBA. http://www.experts-exchange.com/videos/1478/Excel-Error-Handlin…
This demo shows you how to set up the containerized NetScaler CPX with NetScaler Management and Analytics System in a non-routable Mesos/Marathon environment for use with Micro-Services applications.

763 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

5 Experts available now in Live!

Get 1:1 Help Now