WPF: Windows 7 like Button Color Hot Tracking






4.26/5 (11 votes)
A nice idea that you can apply to your code

Introduction
A few days ago, I read a great blog post from my good friend Rudi Grobler, where he explained how to implement Color Hot Tracking in Windows 7 on a Button
. I commented to him that it generates a major shift in implementation and I would like to share this with you all. I take this opportunity to thank Rudi, who in addition to a great developer, is also an amazing guy.
Background
This article is based on the implementation described in this blog post: Make your WPF buttons color hot-track!
And the idea of the converter came from Grant Hinkson in his post: “McGuffin”-Enabling Image Converter.
Using the Code
The technique is actually very simple. We create an AttachedProperty
that will be used to apply the background brush with the most common color in the element it contains.
The AttachedProperty
is defined like this:
public static readonly DependencyProperty ApplyHotTrackingProperty =
DependencyProperty.RegisterAttached("ApplyHotTracking",
typeof(Boolean),
typeof(Win7ColorHotTrackExtension),
new UIPropertyMetadata(
new PropertyChangedCallback(
(sender, e) =>
{
if ((bool)e.NewValue)
{
(sender as FrameworkElement).PreviewMouseMove +=
new System.Windows.Input.MouseEventHandler(
Win7ColorHotTrackExtenssion_PreviewMouseMove);
(sender as FrameworkElement).Loaded +=
new RoutedEventHandler(Win7ColorHotTrackExtenssion_Loaded);
}
else
(sender as FrameworkElement).PreviewMouseMove -=
Win7ColorHotTrackExtenssion_PreviewMouseMove;
})));
public static Boolean GetApplyHotTracking(DependencyObject sender)
{
return (Boolean)sender.GetValue(Win7ColorHotTrackExtension.ApplyHotTrackingProperty);
}
public static void SetApplyHotTracking(DependencyObject sender, Boolean value)
{
sender.SetValue(Win7ColorHotTrackExtension.ApplyHotTrackingProperty, value);
}
As you can see, the PropertyChangedCallback
is defined as an Anonymous Method, and inside this method, we have two events being wired: the first one is the PreviewMouseMove
to animate as the cursor moves over the Control
, and the other is the load event so we can perform the conversions after the visual is rendered. These methods are defined as follows:
static void Win7ColorHotTrackExtenssion_Loaded(object sender,
RoutedEventArgs e)
{
// As stated before, it will not work if the element
// is not a content control so an exception is thrown
if (!(sender is ContentControl))
throw new NotSupportedException("This attached property " +
"is just supported by an ContentControl");
var control = sender as ContentControl;
// verify if any data is binded to the Tag property,
// because if it is, we don't want to lose it
if (control.GetValue(FrameworkElement.TagProperty) == null)
{
// Instantiate and Invalidate the VisualBrush
// needed for the analysis of the content
VisualBrush b = new VisualBrush();
b.SetValue(VisualBrush.VisualProperty, control.Content);
control.InvalidateVisual();
// if the control has no visual (with a height lesser or equal to zero)
// we don't need to perform any action,
// because the result will be a transparent brush anyway
if ((control as FrameworkElement).ActualHeight <= 0)
return;
// Render the visual of the element
// to an bitmap with the RenderTargetBitmap class
RenderTargetBitmap RenderBmp = new RenderTargetBitmap(
(int)(control.Content as FrameworkElement).Width,
(int)(control.Content as FrameworkElement).Height,
96,
96,
PixelFormats.Pbgra32);
RenderBmp.Render(b.Visual);
// Set the value to the Tag property
control.SetValue(FrameworkElement.TagProperty, RenderBmp);
// Instantiate and initialize a Binding element to handle the new tag property
Binding bindBG = new Binding("Tag");
bindBG.Source = control;
// Define the converter that will be used to handle
// the transformation from an image to average color
bindBG.Converter = new IconToAvgColorBrushConverter();
// Set the binding to the Background property
control.SetBinding(ContentControl.BackgroundProperty, bindBG);
// if the Background is a LinearGradientBrush
// we also want our control to use the border
// colored as Win7 does
if (control.Background is LinearGradientBrush)
{
Binding bindBorder = new Binding("GradientStops[1].Color");
bindBorder.Source = control.Background;
control.SetBinding(ContentControl.BorderBrushProperty, bindBorder);
}
}
}
static void Win7ColorHotTrackExtenssion_PreviewMouseMove(object sender,
System.Windows.Input.MouseEventArgs e)
{
// As already said the sender must be a Content Control
if (!(sender is ContentControl))
return;
ContentControl element = sender as ContentControl;
// if the Brush is not a linearGradientBrush
// we don't need to do anything so just returns
if (!(element.GetValue(ContentControl.BackgroundProperty)
is LinearGradientBrush))
return;
// Get the brush
LinearGradientBrush b = element.GetValue(ContentControl.BackgroundProperty)
as LinearGradientBrush;
// Get the ActualWidth of the sender
Double refZeroX = (double)element.GetValue(ContentControl.ActualWidthProperty);
// Get the new point for the StartPoint and EndPoint of the Gradient
System.Windows.Point p =
new System.Windows.Point(e.GetPosition(element).X / refZeroX, 1);
// Set the new values
b.StartPoint = new System.Windows.Point(1 - p.X, 0);
b.EndPoint = p;
}
So now, all we have to do is make use of the AttachedProperty
in our XAML as follows:
<Window
x:Class="ColorHotTrackButton.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ColorHotTrackButton"
Title="Color Hot-Track Buttons"
SizeToContent="WidthAndHeight">
<Window.Resources>
<BooleanToVisibilityConverter
x:Key="BooleanToVisibilityConverter"
/>
<LinearGradientBrush
x:Key="GlossGradient"
EndPoint="0.0149999996647239,0.0160000007599592"
StartPoint="0.486000001430511,0.723999977111816"
>
<GradientStop
Color="#0CFFFFFF"
/>
<GradientStop
Color="#4CFFFFFF"
Offset="1"
/>
</LinearGradientBrush>
<local:IconToAvgColorBrushConverter
x:Key="iconToAvgColorBrushConverter"
/>
<Style
x:Key="ColorHotTrackButton"
TargetType="{x:Type Button}"
BasedOn="{x:Null}"
>
<Setter
Property="Background"
Value="#00FFFFFF"
/>
<Setter
Property="BorderBrush"
Value="#00FFFFFF"
/>
<Setter
Property="Template"
>
<Setter.Value>
<ControlTemplate
TargetType="{x:Type Button}"
>
<ControlTemplate.Resources>
<Storyboard
x:Key="GotFocus"
>
<DoubleAnimationUsingKeyFrames
BeginTime="00:00:00"
Storyboard.TargetName="rectangle"
Storyboard.TargetProperty="(UIElement.Opacity)"
>
<SplineDoubleKeyFrame
KeyTime="00:00:00"
Value="0"
/>
<SplineDoubleKeyFrame
KeyTime="00:00:00.3000000"
Value="1"
/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</ControlTemplate.Resources>
<Grid
x:Name="Grid"
ClipToBounds="True"
>
<Border
x:Name="Border"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Padding="{TemplateBinding Padding}"
CornerRadius="3,3,3,3"
Opacity="0.0"
ClipToBounds="True"
/>
<Rectangle
x:Name="rectangle"
RadiusX="3"
RadiusY="3"
Fill="#33FFFFFF"
Opacity="0"
/>
<ContentPresenter
HorizontalAlignment="{TemplateBinding
HorizontalContentAlignment}"
Margin="{TemplateBinding Padding}"
VerticalAlignment="{TemplateBinding
VerticalContentAlignment}"
RecognizesAccessKey="True"
/>
<Path
x:Name="gloss"
Fill="{StaticResource GlossGradient}"
Stretch="Fill"
Margin="2,2,2,29.204"
ClipToBounds="True"
Data="M2.9999995,0 L151,0 C152.65686,1.0728836E-06 154,
1.3431468 154,3.0000018 L154,21.0382 151.53519,
21.193919 C90.378815,
25.365844 36.495198,48.231778 1.1935941,
97.114381 L0,98.795694 0,3.0000018 C4.7683716E-07,
1.3431468 1.3431462,1.0728836E-06 2.9999995,0 z"
/>
</Grid>
<ControlTemplate.Triggers>
<Trigger
Property="IsFocused"
Value="True"
>
<Trigger.ExitActions>
<RemoveStoryboard
BeginStoryboardName="GotFocus_BeginStoryboard"
/>
</Trigger.ExitActions>
<Trigger.EnterActions>
<BeginStoryboard
x:Name="GotFocus_BeginStoryboard"
Storyboard="{StaticResource GotFocus}"
/>
</Trigger.EnterActions>
</Trigger>
<Trigger
Property="IsKeyboardFocused"
Value="true"
>
<Setter
Property="BorderBrush"
Value="{DynamicResource DefaultedBorderBrush}"
TargetName="Border"
/>
</Trigger>
<Trigger
Property="IsMouseOver"
Value="true"
>
<Setter
Property="Opacity"
Value="1.0"
TargetName="Border"
/>
</Trigger>
<Trigger
Property="IsEnabled"
Value="true"
/>
<Trigger
Property="IsEnabled"
Value="false"
>
<Setter
Property="Background"
Value="{DynamicResource DisabledBackgroundBrush}"
TargetName="Border"
/>
<Setter
Property="BorderBrush"
Value="{DynamicResource DisabledBorderBrush}"
TargetName="Border"
/>
<Setter
Property="Foreground"
Value="{DynamicResource DisabledForegroundBrush}"
/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Window.Background>
<LinearGradientBrush
EndPoint="0.5,1"
StartPoint="0.5,0"
>
<GradientStop
Color="Black"
Offset="0.974"
/>
<GradientStop
Color="#FF656565"
/>
</LinearGradientBrush>
</Window.Background>
<DockPanel>
<Border
DockPanel.Dock="Bottom"
>
<StackPanel
Orientation="Horizontal"
MaxHeight="140"
>
<Grid>
<Button
Margin="2.5,0,2.5,0"
Style="{DynamicResource ColorHotTrackButton}"
local:Win7ColorHotTrackExtension.ApplyHotTracking="True"
>
<Image
Source="/ColorHotTrackButton;component/
Assets/Burning Box V2 .ico"
Width="128"
Height="128"
Margin="12.5,5,12.5,5"
/>
</Button >
</Grid>
<Grid>
<Button
Margin="2.5,0,2.5,0"
Style="{DynamicResource ColorHotTrackButton}"
local:Win7ColorHotTrackExtension.ApplyHotTracking="True"
>
<Image
Source="/ColorHotTrackButton;component/
Assets/carte graphique.ico"
Width="128"
Height="128"
Margin="12.5,5,12.5,5"
/>
</Button >
</Grid>
<Grid>
<Button
Margin="2.5,0,2.5,0"
Style="{DynamicResource ColorHotTrackButton}"
local:Win7ColorHotTrackExtension.ApplyHotTracking="True"
>
<Image
Source="/ColorHotTrackButton;component/Assets/
connection réseaux Bagg's.ico"
Width="128"
Height="128"
Margin="12.5,5,12.5,5"
/>
</Button >
</Grid>
<Grid>
<Button
Margin="2.5,0,2.5,0"
Style="{DynamicResource ColorHotTrackButton}"
local:Win7ColorHotTrackExtension.ApplyHotTracking="True"
>
<Image
Source="/ColorHotTrackButton;component/
Assets/favoris'Box.ico"
Width="128"
Height="128"
Margin="12.5,5,12.5,5"
/>
</Button >
</Grid>
<Grid>
<Button
Margin="2.5,0,2.5,0"
Style="{DynamicResource ColorHotTrackButton}"
local:Win7ColorHotTrackExtension.ApplyHotTracking="True"
>
<Image
Source="/ColorHotTrackButton;component/
Assets/lecteur box.ico"
Width="128"
Height="128"
Margin="12.5,5,12.5,5"
/>
</Button >
</Grid>
<Grid>
<Button
Margin="2.5,0,2.5,0"
Style="{DynamicResource ColorHotTrackButton}"
local:Win7ColorHotTrackExtension.ApplyHotTracking="True"
>
<Image
Source="/ColorHotTrackButton;component/
Assets/private Box.ico"
Width="128"
Height="128"
Margin="12.5,5,12.5,5"
/>
</Button >
</Grid>
</StackPanel>
</Border>
</DockPanel>
</Window>
History
- 22nd October, 2009: Initial post
- 27th October, 2009: Article updated