Solved

Button Styling with ControlTemplate

Posted on 2011-03-10
10
937 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
ID: 35110258
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
ID: 35110283
I'm not seeing anything regarding mouseover event. Can we have the complete xaml?
0
 

Author Comment

by:Choran6619
ID: 35110539
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
Salesforce Made Easy to Use

On-screen guidance at the moment of need enables you & your employees to focus on the core, you can now boost your adoption rates swiftly and simply with one easy tool.

 

Author Comment

by:Choran6619
ID: 35110572
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
ID: 35111280
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
 

Author Comment

by:Choran6619
ID: 35113132
@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
ID: 35113318
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
ID: 35122225
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
ID: 35124254
@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
ID: 35163496
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

Free Tool: Subnet Calculator

The subnet calculator helps you design networks by taking an IP address and network mask and returning information such as network, broadcast address, and host range.

One of a set of tools we're offering as a way of saying thank you for being a part of the community.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Suggested Solutions

What my article will show is if you ever had to do processing to a listbox without being able to just select all the items in it. My software Visual Studio 2008 crystal report v11 My issue was I wanted to add crystal report to a form and show…
After several hours of googling I could not gather any information on this topic. There are several ways of controlling the USB port connected to any storage device. The best example of that is by changing the registry value of "HKEY_LOCAL_MACHINE\S…
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…

821 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