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)
{
if (!(sender is ContentControl))
throw new NotSupportedException("This attached property " +
"is just supported by an ContentControl");
var control = sender as ContentControl;
if (control.GetValue(FrameworkElement.TagProperty) == null)
{
VisualBrush b = new VisualBrush();
b.SetValue(VisualBrush.VisualProperty, control.Content);
control.InvalidateVisual();
if ((control as FrameworkElement).ActualHeight <= 0)
return;
RenderTargetBitmap RenderBmp = new RenderTargetBitmap(
(int)(control.Content as FrameworkElement).Width,
(int)(control.Content as FrameworkElement).Height,
96,
96,
PixelFormats.Pbgra32);
RenderBmp.Render(b.Visual);
control.SetValue(FrameworkElement.TagProperty, RenderBmp);
Binding bindBG = new Binding("Tag");
bindBG.Source = control;
bindBG.Converter = new IconToAvgColorBrushConverter();
control.SetBinding(ContentControl.BackgroundProperty, bindBG);
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)
{
if (!(sender is ContentControl))
return;
ContentControl element = sender as ContentControl;
if (!(element.GetValue(ContentControl.BackgroundProperty)
is LinearGradientBrush))
return;
LinearGradientBrush b = element.GetValue(ContentControl.BackgroundProperty)
as LinearGradientBrush;
Double refZeroX = (double)element.GetValue(ContentControl.ActualWidthProperty);
System.Windows.Point p =
new System.Windows.Point(e.GetPosition(element).X / refZeroX, 1);
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