Setting Border background with a template binding - silverlight

Value="{TemplateBinding HeaderColor}"I've created my own control, and I'm wondering if I can bind a Border.Background to a template property. Currently i'm setting it with a StaticResource like the following:
<Color x:Key="ControlMouseOverColor">green</Color>
<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Border.Background).(GradientBrush.GradientStops)[1].(GradientStop.Color)" Storyboard.TargetName="headerLayout">
<EasingColorKeyFrame KeyTime="0:0:6" Value="{StaticResource ControlMouseOverColor}" />
</ColorAnimationUsingKeyFrames>
I'd like it be a property on my control, and be able to set it as a template binding
<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Border.Background).(GradientBrush.GradientStops)[1].(GradientStop.Color)" Storyboard.TargetName="headerLayout">
<EasingColorKeyFrame KeyTime="0:0:6" Value="{TemplateBinding HeaderColor}" />
</ColorAnimationUsingKeyFrames>
MainPage.xaml
<ctrl:Selection Grid.Column="0" HeaderColor="Red" HeaderText="Header Text" />
my class:
public static readonly DependencyProperty HeaderColorProperty =
DependencyProperty.Register("HeaderColor", typeof(System.Windows.Media.Color), typeof(Selection), new PropertyMetadata(System.Windows.Media.Colors.Red));
public System.Windows.Media.Color HeaderColor {
get { return (System.Windows.Media.Color)GetValue(HeaderColorProperty); }
set { SetValue(HeaderColorProperty, value); }
}
This 2nd option does not work, should I be able to do this? I don't get an error, it just does not change to the color I set.
Comment left by AngelWPF asked for more code, pasting below, I'm in the beginning stages of learning to create a control, wanted to note that, because there is a lot I have not got to yet, one piece at a time :)
generic.xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:SelectionControl.Library"
xmlns:ctrl="clr-namespace:SelectionControl.Library;assembly=SelectionControl">
<LinearGradientBrush x:Key="HeaderBackground" EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="Black" Offset="0" />
<GradientStop Color="Gray" Offset="1" />
</LinearGradientBrush>
<Color x:Key="ControlMouseEnterColor">aliceblue</Color>
<Color x:Key="ControlMouseLeaveColor">Gray</Color>
<Color x:Key="ControlLeftMouseUpColor">Red</Color>
<Style TargetType="ctrl:Selection">
<Setter Property="Width" Value="Auto" />
<Setter Property="Height" Value="Auto" />
<Setter Property="FontSize" Value="12" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="Foreground" Value="AliceBlue" />
<Setter Property="Margin" Value="2,2,2,2" />
<Setter Property="Background" Value="{StaticResource ResourceKey=HeaderBackground}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ctrl:Selection">
<Grid x:Name="RootElement" Margin="{TemplateBinding Margin}">
<!-- Visual States -->
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="MouseEnter">
<Storyboard>
<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Border.Background).(GradientBrush.GradientStops)[1].(GradientStop.Color)" Storyboard.TargetName="headerLayout">
<EasingColorKeyFrame KeyTime="0:0:.5" Value="{TemplateBinding HeaderColor}" />
</ColorAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="MouseLeave">
<Storyboard>
<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Border.Background).(GradientBrush.GradientStops)[1].(GradientStop.Color)" Storyboard.TargetName="headerLayout">
<EasingColorKeyFrame KeyTime="0:0:1" Value="{StaticResource ControlMouseLeaveColor}" />
</ColorAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="MouseLeftUp">
<Storyboard>
<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Border.Background).(GradientBrush.GradientStops)[1].(GradientStop.Color)" Storyboard.TargetName="headerLayout">
<EasingColorKeyFrame KeyTime="0:0:1" Value="{StaticResource ControlLeftMouseUpColor}" />
</ColorAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<!-- End Visual States-->
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Header -->
<Border x:Name="headerLayout" Background="{TemplateBinding Background}" Grid.Row="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" CornerRadius="2,2,2,2" BorderBrush="Black" BorderThickness="1">
<StackPanel>
<ToggleButton ></ToggleButton>
<TextBlock Foreground="{TemplateBinding Foreground}" Text="{TemplateBinding HeaderText}" FontWeight="{TemplateBinding FontWeight}" FontSize="{TemplateBinding FontSize}" HorizontalAlignment="Center" VerticalAlignment="Center" />
</StackPanel>
</Border>
<!-- Body Content -->
<ContentPresenter Grid.Row="1" Content="{TemplateBinding Content}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Selection.cs
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace SelectionControl.Library {
[TemplateVisualState(Name = Selection.MouseEnterStateName, GroupName = Selection.CommonStatesGroupName)]
[TemplateVisualState(Name = Selection.MouseLeaveStateName, GroupName = Selection.CommonStatesGroupName)]
[TemplateVisualState(Name = Selection.MouseLeftUpStateName, GroupName = Selection.CommonStatesGroupName)]
public class Selection : ContentControl {
public const string CommonStatesGroupName = "CommonStates";
public const string MouseEnterStateName = "MouseEnter";
public const string MouseLeaveStateName = "MouseLeave";
public const string MouseLeftUpStateName = "MouseLeftUp";
public Selection() {
this.DefaultStyleKey = typeof(Selection);
this.MouseEnter += new MouseEventHandler(OnMouseEnter);
this.MouseLeave += new MouseEventHandler(OnMouseLeave);
this.MouseLeftButtonUp += new MouseButtonEventHandler(OnMouseLeftButtonUp);
}
void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e) {
this.GoToState(Selection.MouseLeftUpStateName, true);
}
void OnMouseLeave(object sender, MouseEventArgs e) {
this.GoToState(Selection.MouseLeaveStateName, true);
}
void OnMouseEnter(object sender, MouseEventArgs e) {
this.GoToState(Selection.MouseEnterStateName, true);
}
private void GoToState(string stateName, bool useTransitions) {
VisualStateManager.GoToState(this, stateName, useTransitions);
}
public static readonly DependencyProperty HeaderTextProperty =
DependencyProperty.Register("HeaderText", typeof(string), typeof(Selection), new PropertyMetadata(""));
public string HeaderText {
get { return (string)GetValue(HeaderTextProperty); }
set { SetValue(HeaderTextProperty, value); }
}
public static readonly DependencyProperty HeaderColorProperty =
DependencyProperty.Register("HeaderColor", typeof(System.Windows.Media.Color), typeof(Selection), new PropertyMetadata(System.Windows.Media.Colors.Red));
public System.Windows.Media.Color HeaderColor {
get { return (System.Windows.Media.Color)GetValue(HeaderColorProperty); }
set { SetValue(HeaderColorProperty, value); }
}
}}

I have had mixed results using TemplateBinding on custom dependency properties. Because of this, I have used RelativeSource TemplatedParent which seems to work in every situation.
<EasingColorKeyFrame KeyTime="0:0:.5" Value="{Binding HeaderColor, RelativeSource={RelativeSource TemplatedParent}}" />

Related

InvalidOperationException Ellipse not Found after Binding "IsChecked"

I am using a Togglebutton Style from here after Binding the IsChecked Property to my ViewModel the Program crashes with a System.InvalidOperationException Ellipse not found in "System.Windows.Controls.ControlTemplate".
The Style looks like this:
<Style TargetType="{x:Type ToggleButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Viewbox>
<Border x:Name="Border"
CornerRadius="10"
Background="#FFE2E2E2"
Width="40"
Height="20">
<Border.Effect>
<DropShadowEffect ShadowDepth="0.5"
Direction="0"
Opacity="0.3" />
</Border.Effect>
<Ellipse x:Name="Ellipse"
Fill="#FF909090"
Stretch="Uniform"
Margin="-8 -4"
Stroke="Gray"
StrokeThickness="0.2"
HorizontalAlignment="Stretch">
<Ellipse.Effect>
<DropShadowEffect BlurRadius="10"
ShadowDepth="1"
Opacity="0.3"
Direction="260" />
</Ellipse.Effect>
</Ellipse>
</Border>
</Viewbox>
<ControlTemplate.Triggers>
<EventTrigger RoutedEvent="Checked">
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetName="Ellipse"
Storyboard.TargetProperty="(Ellipse.Fill).(SolidColorBrush.Color)"
To="#00facc"
Duration="0:0:0.05"
AccelerationRatio="0.7"
DecelerationRatio="0.3" />
<ThicknessAnimation Storyboard.TargetName="Ellipse"
Storyboard.TargetProperty="Margin"
To="20 -4 -8 -4"
Duration="0:0:0.15"
AccelerationRatio="0.7"
DecelerationRatio="0.3" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="Unchecked">
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetName="Ellipse"
Storyboard.TargetProperty="(Ellipse.Fill).(SolidColorBrush.Color)"
To="#00facc"
Duration="0:0:0.05"
AccelerationRatio="0.7"
DecelerationRatio="0.3" />
<ThicknessAnimation
Storyboard.TargetName="Ellipse"
Storyboard.TargetProperty="Margin"
To="-8 -4"
Duration="0:0:0.15"
AccelerationRatio="0.7"
DecelerationRatio="0.3" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And my usage like this:
<ToggleButton Grid.Column="0"
Grid.Row="2"
IsChecked="{Binding IsAutostop}"/>
I'd like to use the style AND be able to use the "IsChecked" Property.
EDIT
Thanks for the comments.
I open the window with a Service bound to a command in a taskbaricon.
The Service:
class WindowService
{
public void OpenOptionsView(MainViewModel mvm)
{
OptionsView optionsView = new OptionsView { DataContext = mvm };
optionsView.Show();
}
}
The important parts of the MainviewModel:
public ICommand OpenOptionsViewCommand { get; private set; }
public bool IsAutostop { get { return Settings.Default.Autostop; } set { Settings.Default.Autostop = value; Settings.Default.Save(); OnPropertyChanged(); } }
public MainViewModel()
{
OpenOptionsViewCommand = new RelayCommand(OpenOptionsView, param => true);
}
private void OpenOptionsView(object o)
{
WindowService ws = new WindowService();
ws.OpenOptionsView(this);
}
I stored my TaskbarIcon in a seperated View:
<tb:TaskbarIcon>
<tb:TaskbarIcon.ContextMenu>
<ContextMenu Background="#1e1e1e">
<MenuItem Header="Programm öffnen"
Foreground="White"
Command="{Binding OpenOptionsViewCommand}"/>
</ContextMenu>
</tb:TaskbarIcon.ContextMenu>
</tb:TaskbarIcon>
EDIT II:
I just found out, that the problem occurs when the bound value is True and the window should be opened with a checked Togglebutton.

Handle an event in code behind as well as Invoke a view model's command on some event

Here's the overview :
I am using a Custom control (CusCtrl) to show taskbar icon , it also has a Popup property. so when you click on the icon the CusCtrl shows the Popup.
I am setting the child of the pop up with a UserControl (lets say UC1).
I am setting the DataContext of CusCtrl with a ViewModel thus even the UC1 get binded with a respective ViewModel (lets say VM1)
Now the UC1 has some element - a Label, on clicking the label I need 2 things to happen:
Invoke a command on the view model VM1 -
From the command I need to pass some of the view model's properties as parameters and open some window UI.
Close the PopUp -
For this I have thought of listening the MouseUp Event in code behind of UserControl & then fire a routed event (FirePopUpClose - this event is defined in the UserControl UC1) which will be handled by the app & then from within the
handler, Custom Conntrol's ClosePopUp method will be called.
I do know how to invoke command on the MouseUp event on label using the Interactivity dll, but then how can I raise the FirePopUpClose routed Event?
Or how do apply a MouseUp event handler on label as well as bind a command to that label ?
Am I even thinking this the right way or there's some better cleaner way to do some UI action as well as close the PopUp by sticking to MVVM?
what about the next solution; try to use Popup.IsOpen property (here is the information about, and here is the example of usage #867 – Controlling Whether a Popup Is Open Using Data Binding ). Bind it to UC1 DataContext directly or via DependencyProperty of CusCtrl user control (you have to create that property in your control that encapsulate the Popup). That's all, this way you will be able to manage popup to be opened or closed without the event.
Update
Try the next:
1. Main Xaml:
<Window x:Class="PopupIsOpenDataBindingHelpAttempt.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:popupIsOpenDataBindingHelpAttempt="clr-namespace:PopupIsOpenDataBindingHelpAttempt"
xmlns:system="clr-namespace:System;assembly=mscorlib"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<popupIsOpenDataBindingHelpAttempt:DemoMainViewModel/>
</Window.DataContext>
<Window.Resources>
<DataTemplate x:Key="PopupInnerControlDataTemplateKey" DataType="{x:Type popupIsOpenDataBindingHelpAttempt:TaskBarDemoViewModel}">
<Grid Width="150" Height="85">
<Grid.RowDefinitions>
<RowDefinition></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Text="{Binding TextString}"
Margin="5" Foreground="Red"
Width="150" TextWrapping="WrapWithOverflow"
VerticalAlignment="Stretch" HorizontalAlignment="Stretch"/>
<Button Grid.Row="1" Content="Press to close" Command="{Binding PopupInnerButtonCommand}"
VerticalAlignment="Stretch" HorizontalAlignment="Stretch"/>
</Grid>
</DataTemplate>
<Image x:Key="ImageControl" Source="Pic/2015_10_16_Bing_en-US.jpg" IsHitTestVisible="False"/>
</Window.Resources>
<Grid Width="75" Height="75" HorizontalAlignment="Center" VerticalAlignment="Center">
<popupIsOpenDataBindingHelpAttempt:TaskBarIconProjectDemo
ButtonContentProperty="{StaticResource ImageControl}"
ButtonCommandProperty="{Binding ShowPopupCommand}"
PopupIsOpenProperty="{Binding IsPopupOpen, UpdateSourceTrigger=PropertyChanged}"
PopupInnerContentControlDataContext="{Binding TaskBarDemoViewModel, UpdateSourceTrigger=PropertyChanged}"
PopupInnerContentControlContentTemplate="{StaticResource PopupInnerControlDataTemplateKey}"/>
</Grid>
2. Popup encapsulating XAMl:
<UserControl x:Class="PopupIsOpenDataBindingHelpAttempt.TaskBarIconProjectDemo"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300" x:Name="This">
<Grid>
<Button Style="{StaticResource SpecialButtonStyle}" Command="{Binding ElementName=This, Path=ButtonCommandProperty}"
Content="{Binding ElementName=This, Path=ButtonContentProperty}"></Button>
<Popup IsOpen="{Binding ElementName=This, Path=PopupIsOpenProperty}">
<ContentControl Content="{Binding ElementName=This, Path=PopupInnerContentControlDataContext}"
ContentTemplate="{Binding ElementName=This, Path=PopupInnerContentControlContentTemplate}"/>
</Popup>
</Grid>
3. Popup encapsulating control code behind (dependency properties):
public partial class TaskBarIconProjectDemo : UserControl
{
public static readonly DependencyProperty ButtonCommandPropertyProperty = DependencyProperty.Register("ButtonCommandProperty", typeof (ICommand), typeof (TaskBarIconProjectDemo), new PropertyMetadata(default(ICommand)));
public static readonly DependencyProperty PopupIsOpenPropertyProperty = DependencyProperty.Register("PopupIsOpenProperty", typeof (bool), typeof (TaskBarIconProjectDemo), new PropertyMetadata(default(bool)));
public static readonly DependencyProperty PopupInnerContentControlDataContextProperty = DependencyProperty.Register("PopupInnerContentControlDataContext", typeof (object), typeof (TaskBarIconProjectDemo), new PropertyMetadata(default(object)));
public static readonly DependencyProperty PopupInnerContentControlContentTemplateProperty = DependencyProperty.Register("PopupInnerContentControlContentTemplate", typeof (DataTemplate), typeof (TaskBarIconProjectDemo), new PropertyMetadata(default(DataTemplate)));
public static readonly DependencyProperty ButtonContentPropertyProperty = DependencyProperty.Register("ButtonContentProperty", typeof (object), typeof (TaskBarIconProjectDemo), new PropertyMetadata(default(object)));
public TaskBarIconProjectDemo()
{
InitializeComponent();
}
public ICommand ButtonCommandProperty
{
get { return (ICommand) GetValue(ButtonCommandPropertyProperty); }
set { SetValue(ButtonCommandPropertyProperty, value); }
}
public bool PopupIsOpenProperty
{
get { return (bool) GetValue(PopupIsOpenPropertyProperty); }
set { SetValue(PopupIsOpenPropertyProperty, value); }
}
public object PopupInnerContentControlDataContext
{
get { return (object) GetValue(PopupInnerContentControlDataContextProperty); }
set { SetValue(PopupInnerContentControlDataContextProperty, value); }
}
public DataTemplate PopupInnerContentControlContentTemplate
{
get { return (DataTemplate) GetValue(PopupInnerContentControlContentTemplateProperty); }
set { SetValue(PopupInnerContentControlContentTemplateProperty, value); }
}
public object ButtonContentProperty
{
get { return (object) GetValue(ButtonContentPropertyProperty); }
set { SetValue(ButtonContentPropertyProperty, value); }
}
}
4. View Models:
public class DemoMainViewModel:BaseObservableObject
{
private bool _isOpen;
private TaskBarDemoViewModel _taskBarDemoViewModel;
private ICommand _showPopupCommnad;
public DemoMainViewModel()
{
TaskBarDemoViewModel = new TaskBarDemoViewModel(ClosePopup, "Here you can put your content. Go for it...");
}
private void ClosePopup()
{
IsPopupOpen = false;
}
public bool IsPopupOpen
{
get { return _isOpen; }
set
{
_isOpen = value;
OnPropertyChanged();
}
}
public TaskBarDemoViewModel TaskBarDemoViewModel
{
get { return _taskBarDemoViewModel; }
set
{
_taskBarDemoViewModel = value;
OnPropertyChanged();
}
}
public ICommand ShowPopupCommand
{
get { return _showPopupCommnad ?? (_showPopupCommnad = new RelayCommand(ShowPopup)); }
}
private void ShowPopup()
{
IsPopupOpen = true;
}
}
public class TaskBarDemoViewModel:BaseObservableObject
{
private readonly Action _closePopupCommand;
private ICommand _command;
private string _textString;
public TaskBarDemoViewModel(Action closePopupCommand, string content)
{
_closePopupCommand = closePopupCommand;
TextString = content;
}
public ICommand PopupInnerButtonCommand
{
get { return _command ?? (_command = new RelayCommand(TargetMethod)); }
}
private void TargetMethod()
{
//add your logic here
if(_closePopupCommand == null) return;
_closePopupCommand();
}
public string TextString
{
get { return _textString; }
set
{
_textString = value;
OnPropertyChanged();
}
}
}
5. Button style (change this as your need):
<Color x:Key="ButtonLowerPartKey">#FFD5E0EE</Color>
<Color x:Key="ButtonUpperPartKey">#FFEAF1F8</Color>
<Color x:Key="PressedColorButtonLowerPartKey">#FFF4C661</Color>
<Color x:Key="PressedButtonUpperPartKey">#FFF4CC87</Color>
<Color x:Key="HooveredButtonLowerPartKey">#FFFFD06D</Color>
<Color x:Key="HooveredButtonUpperPartKey">#FFFFF0DF</Color>
<Style x:Key="SpecialButtonStyle" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Padding" Value="5">
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid x:Name="Grid">
<Ellipse x:Name="ButtonControlBorder" Stroke="{TemplateBinding BorderBrush}"
StrokeThickness="{TemplateBinding BorderThickness}"
Width="{TemplateBinding Width}" Height="{TemplateBinding Height}">
<Ellipse.Fill>
<LinearGradientBrush x:Name="BrushKey" MappingMode="RelativeToBoundingBox" SpreadMethod="Repeat" StartPoint="0.5,0" EndPoint="0.5,1">
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0.5" Color="{StaticResource ButtonUpperPartKey}" />
<GradientStop Offset="0.5" Color="{StaticResource ButtonUpperPartKey}" />
<GradientStop Offset="0.5" Color="{StaticResource ButtonLowerPartKey}" />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Ellipse.Fill>
</Ellipse>
<Ellipse x:Name="Pressed" Width="{TemplateBinding Width}" Height="{TemplateBinding Height}" Opacity="0">
<Ellipse.Fill>
<LinearGradientBrush x:Name="PressedBrushKey" MappingMode="RelativeToBoundingBox" SpreadMethod="Repeat" StartPoint="0.5,0" EndPoint="0.5,1">
<LinearGradientBrush.GradientStops>
<GradientStop Offset="0.5" Color="{StaticResource PressedButtonUpperPartKey}" />
<GradientStop Offset="0.5" Color="{StaticResource PressedButtonUpperPartKey}" />
<GradientStop Offset="0.5" Color="{StaticResource PressedColorButtonLowerPartKey}" />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Ellipse.Fill>
</Ellipse>
<Ellipse x:Name="InnerPressed"
Width="{Binding ElementName=Pressed, Path=Width}" Height="{Binding ElementName=Pressed, Path=Height}"
Stroke="DarkOrange" Opacity="0" StrokeThickness="1" SnapsToDevicePixels="True" Fill="Transparent"/>
<ContentPresenter Content="{TemplateBinding Button.Content}" HorizontalAlignment="Center" VerticalAlignment="Center">
<ContentPresenter.OpacityMask>
<VisualBrush Visual="{Binding ElementName=ButtonControlBorder}" />
</ContentPresenter.OpacityMask>
</ContentPresenter>
<Grid.Triggers>
<EventTrigger RoutedEvent="Mouse.MouseEnter">
<BeginStoryboard x:Name="MouseEnterStoryboard">
<Storyboard>
<ColorAnimation Storyboard.TargetName="BrushKey" Storyboard.TargetProperty="GradientStops[0].Color" From="{StaticResource ButtonUpperPartKey}" To="{StaticResource HooveredButtonUpperPartKey}" Duration="0:0:0.3" AutoReverse="False" />
<ColorAnimation Storyboard.TargetName="BrushKey" Storyboard.TargetProperty="GradientStops[2].Color" From="{StaticResource ButtonLowerPartKey}" To="{StaticResource HooveredButtonLowerPartKey}" Duration="0:0:0.3" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="Mouse.MouseLeave">
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetName="BrushKey" Storyboard.TargetProperty="GradientStops[0].Color" From="{StaticResource HooveredButtonUpperPartKey}" To="{StaticResource ButtonUpperPartKey}" Duration="0:0:1" AutoReverse="False" />
<ColorAnimation Storyboard.TargetName="BrushKey" Storyboard.TargetProperty="GradientStops[2].Color" From="{StaticResource HooveredButtonLowerPartKey}" To="{StaticResource ButtonLowerPartKey}" Duration="0:0:1" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Grid.Triggers>
</Grid>
<ControlTemplate.Resources>
<Storyboard x:Key="MouseUpTimeLine">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="Pressed" Storyboard.TargetProperty="Opacity">
<SplineDoubleKeyFrame KeyTime="00:00:00.25" Value="0" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="MouseDownTimeLine">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="Pressed" Storyboard.TargetProperty="Opacity">
<SplineDoubleKeyFrame KeyTime="00:00:00.05" Value="0.8" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="InnerPressedMouseUpTimeLine">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="InnerPressed" Storyboard.TargetProperty="Opacity">
<SplineDoubleKeyFrame KeyTime="00:00:00.25" Value="0" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="InnerPressedMouseDownTimeLine">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="InnerPressed" Storyboard.TargetProperty="Opacity">
<SplineDoubleKeyFrame KeyTime="00:00:00.05" Value="1" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</ControlTemplate.Resources>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" SourceName="Grid" Value="True">
<Setter Property="Stroke" TargetName="ButtonControlBorder">
<Setter.Value>
<SolidColorBrush Color="{StaticResource HooveredButtonLowerPartKey}">
</SolidColorBrush>
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="ButtonBase.IsPressed" Value="True">
<Trigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource MouseDownTimeLine}" />
<BeginStoryboard Storyboard="{StaticResource InnerPressedMouseDownTimeLine}">
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard Storyboard="{StaticResource MouseUpTimeLine}" />
<BeginStoryboard Storyboard="{StaticResource InnerPressedMouseUpTimeLine}">
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
BaseObservableObject is the simple implementation of INCP.
RelayCommand is the simple implementation of ICommand interface.
I'll glad to help if you will have the problem with the code.
Regards,

Silverlight: access control inside the default template of a child control (derived from a templated control)

I am trying to implement the following hierarchy of controls:
RoundedHandleBase:
- this will have a round border around a content presenter that will host a path defined in the template of the child controls.
- it will also provide 2 states: MouseOver and Normal (it displays a rectangle over the path when mouse is over)
public class RoundedHandleBase : ContentControl
{
public RoundedHandleBase()
: base()
{
base.DefaultStyleKey = typeof(RoundedHandleBase);
this.MouseEnter += new MouseEventHandler(RoundedHandleBase_MouseEnter);
this.MouseLeave += new MouseEventHandler(RoundedHandleBase_MouseLeave);
}
void RoundedHandleBase_MouseEnter(object sender, MouseEventArgs e)
{
bool bRet = VisualStateManager.GoToState((Control)sender, "MouseOver", true);
}
void RoundedHandleBase_MouseLeave(object sender, MouseEventArgs e)
{
bool bRet = VisualStateManager.GoToState((Control)sender, "Normal", true);
}
}
FlipHandle:
- this defines a Path as the template (that is the actual icon of the handle):
public class FlipHandle : RoundedHandleBase
{
private bool bFlipPath;
public bool FlipPath
{
get { return bFlipPath; }
set
{
bFlipPath = value;
}
}
public FlipHandle()
: base()
{
base.DefaultStyleKey = typeof(FlipHandle);
bFlipPath = false;
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
if (bFlipPath)
{
ContentPresenter content = this.GetTemplateChild("content") as ContentPresenter;
Path arrowPath = this.GetTemplateChild("Path_FlipHandle") as Path;
}
}
}
generic.xaml
*generic.xaml*
<!--ROUNDED HANDLES -->
<ControlTemplate x:Key="RoundedHandleBaseTemplate" TargetType="local:RoundedHandleBase">
<Viewbox>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="MouseOver">
<Storyboard>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="rectangleOver" Storyboard.TargetProperty="(UIElement.Opacity)">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="0.6"/>
<SplineDoubleKeyFrame KeyTime="00:00:00.2000000" Value="0.3"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed">
<Storyboard>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="rectangleOver" Storyboard.TargetProperty="(UIElement.Opacity)">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="0.15"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border CornerRadius="2,2,2,2">
<Grid Height="Auto" Width="Auto">
<Border Margin="0,0,0,0" BorderBrush="#FFFFFFFF" BorderThickness="2,2,2,2" CornerRadius="1,1,1,1" Width="20" Height="20">
<Grid>
<Viewbox Width="14" Height="14">
<ContentPresenter x:Name="content" Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}" HorizontalAlignment="Center" VerticalAlignment="Center"></ContentPresenter>
</Viewbox>
<Rectangle x:Name="rectangleOver" Fill="#FFFFFFFF" RadiusX="2" RadiusY="2" Margin="1,1,1,1" Opacity="0"/>
</Grid>
</Border>
</Grid>
</Border>
</Viewbox>
</ControlTemplate>
<Style TargetType="local:RoundedHandleBase" x:Key="RoundedHandleStyle">
<Setter Property="Template" Value="{StaticResource RoundedHandleBaseTemplate}">
</Setter>
</Style>
<Style TargetType="local:FlipHandle" BasedOn="{StaticResource RoundedHandleStyle}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Path x:Name="Path_FlipHandle" Width="47.5646" Height="49.2324" Canvas.Left="472.184" Canvas.Top="584.534" Stretch="Fill" Fill="#FFFFFFFF" Data="M 494.718,633.76C 508.77,634.067 520.133,622.527 519.738,608.563C 519.534,601.327 516.378,595.502 512.286,591.527C 508.354,587.711 502.225,583.955 493.832,584.608C 482.414,585.496 475.704,591.912 472.184,601.111L 501.638,601.111C 501.668,599.118 501.62,595.698 501.638,593.124C 505.862,596.766 509.822,601.13 513.884,605.191C 514.376,605.684 517.992,608.638 517.965,609.272C 517.962,609.336 516.478,610.758 516.013,611.224C 513.753,613.483 511.846,615.214 509.802,617.258C 506.906,620.154 504.43,623.172 501.638,625.066C 501.618,622.551 501.669,619.192 501.638,617.258L 472.184,617.258C 475.668,626.822 483.53,633.515 494.718,633.76 Z ">
<Path.RenderTransform>
<ScaleTransform CenterX="23.7823" CenterY="24.6162" ScaleX="1" ScaleY="1"></ScaleTransform>
</Path.RenderTransform>
</Path>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
<!--/ROUNDED HANDLES -->
My problem is:
- I cannot access the Path in the FlipHandle in order to modify its ScaleTransform in OnApplyTemplate.
Path arrowPath = this.GetTemplateChild("Path_FlipHandle") as Path;
-> arrowPath is allways null.
On the other hand, i can access the "content" content presenter:
ContentPresenter content = this.GetTemplateChild("content") as ContentPresenter;
-> the above works.
Please advice.
Thank you.
You can't access path with GetTemplateChild if it is not part of the template. You could make it part of your template or you could define some visual states on your flip handle and use GoToState method.

Silverlight TreeView ScrollViewer Issue

Hey SO, got a question about the TreeView control in Silverlight.
I have an application which dynamically adds elements to a treeview. Some of the elements are long enough to require horizontal scrolling. When they are added to the treeview, my treeview remains correctly all the way scrolled left so you have to scroll to see the end of the item. However, if I click on one of my items (which the hides the treeview), and then use the 'back to results' button I implemented (note this only deals with visibility changes) the treeview becomes visible and it is automatically scrolled to the center.
Does anyone know how I can get the treeview to scroll all the way left when I hit back to results?
I've tried messing with the treeview template:
<Style TargetType="controls:TreeView" x:Name="SCREW">
<Setter Property="Background" Value="#FFFFFFFF" />
<Setter Property="Foreground" Value="#FF000000" />
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalContentAlignment" Value="Top" />
<Setter Property="Cursor" Value="Arrow" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="Padding" Value="1" />
<Setter Property="BorderBrush" Value="#FF000000" />
<Setter Property="IsTabStop" Value="True" />
<Setter Property="TabNavigation" Value="Once" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="controls:TreeView" x:Name="SCREWTEMPLATE">
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="MouseOver" />
<VisualState x:Name="Pressed" />
<VisualState x:Name="Disabled" />
</VisualStateGroup>
<VisualStateGroup x:Name="FocusStates">
<VisualState x:Name="Unfocused" />
<VisualState x:Name="Focused" />
</VisualStateGroup>
<VisualStateGroup x:Name="ValidationStates">
<VisualState x:Name="Valid" />
<VisualState x:Name="InvalidUnfocused">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Validation" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Visible" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="InvalidFocused">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Validation" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Visible" />
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ValidationToolTip" Storyboard.TargetProperty="IsOpen">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<System:Boolean>True</System:Boolean>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="2">
<Border Padding="{TemplateBinding Padding}" Background="{TemplateBinding Background}" Margin="1">
<ScrollViewer x:Name="ScrollViewer" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" Background="{x:Null}" BorderBrush="Transparent" BorderThickness="0" IsTabStop="False" TabNavigation="Once" Loaded="ScrollViewer_Loaded">
<ItemsPresenter Margin="5" />
</ScrollViewer>
</Border>
</Border>
<Border x:Name="Validation" Grid.Column="1" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="#FFDB000C" CornerRadius="2" Visibility="Collapsed">
<ToolTipService.ToolTip>
<ToolTip x:Name="ValidationToolTip" Placement="Right" PlacementTarget="{Binding RelativeSource={RelativeSource TemplatedParent}}" DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}" IsHitTestVisible="True" />
</ToolTipService.ToolTip>
<Grid Width="10" Height="10" HorizontalAlignment="Right" Margin="0,-4,-4,0" VerticalAlignment="Top" Background="Transparent">
<Path Margin="-1,3,0,0" Fill="#FFDC000C" Data="M 1,0 L6,0 A 2,2 90 0 1 8,2 L8,7 Z" />
<Path Margin="-1,3,0,0" Fill="#FFFFFFFF" Data="M 0,0 L2,0 L 8,6 L8,8" />
</Grid>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
But the problem with that is I don't know how to access the ScrollViewer from the code behind... so I can't call ScrollView.setScrollOffset(0d,0d) or anything like that.
Any ideas? Thanks a million.
One last thing, I'd like to try to avoid implementing a new control that extends treeview. I'm really hoping there is a way to access/modify and use functions associated with the control template from c# codebehind.
I'd just set up an attached property for this and create the logic you want in there. Then you would decorate your treeview with the attached property. We do something similar with other controls that contain scrollviewers:
public class ScrollResetService
{
public static DependencyProperty IsScrollResetProperty = DependencyProperty.RegisterAttached("IsScrollReset",
typeof(bool),
typeof(ScrollResetService),
new PropertyMetadata(false,
OnIsScrollResetChanged));
public static void SetIsScrollReset(DependencyObject d, bool value)
{
d.SetValue(IsScrollResetProperty, value);
}
public static bool GetIsScrollReset(DependencyObject d)
{
return d.GetValue(IsScrollResetProperty) == null ? false : (bool)d.GetValue(IsScrollResetProperty);
}
private static void OnIsScrollResetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var treeView = d as TreeView;
bool isScrollReset;
if (e.NewValue!= null && bool.TryParse(e.NewValue.ToString(), out isScrollReset) && treeView != null)
{
treeView.SelectedItemChanged += (sender, args) =>
{
var scrolls =
treeView.GetAllLogicalChildrenOfType<IScrollInfo>();
scrolls.ForEach(i => i.SetVerticalOffset(0));
};
}
}
}
public static class Extensions
{
public static IEnumerable<T> GetAllLogicalChildrenOfType<T>(this FrameworkElement parent)
{
Debug.Assert(parent != null, "The parent cannot be null.");
return parent.GetVisualChildren().Flatten(item => item.GetVisualChildren()).OfType<T>();
}
public static IEnumerable<T> Flatten<T>(this IEnumerable<T> items, Func<T, IEnumerable<T>> childSelector)
{
if (items == null) return Enumerable.Empty<T>();
return items.Concat(items.SelectMany(i => childSelector(i).Flatten(childSelector)));
}
internal static IEnumerable<DependencyObject> GetVisualChildren(this DependencyObject parent)
{
int childCount = VisualTreeHelper.GetChildrenCount(parent);
for (int counter = 0; counter < childCount; counter++)
{
yield return VisualTreeHelper.GetChild(parent, counter);
}
}
}
I put this code into a behavior (would also work in code behind) after handling the SelectedItemChanged event of the TreeView:
var offset = this.HorizontalScrollOffsetAfterSelect; // would be 0 if you don't want to make that adjustable
if (!Double.IsNaN(offset))
{
var scrollViewer = trv.GetVisualDescendants().OfType<ScrollViewer>().FirstOrDefault();
if (scrollViewer != null)
{
scrollViewer.ScrollToHorizontalOffset(offset);
// and because that wasn't enough because of timing issues:
var scrollBar = scrollViewer.GetVisualDescendants().OfType<ScrollBar>().Where(cur => cur.Orientation == Orientation.Horizontal).FirstOrDefault();
if (scrollBar != null)
{
RoutedPropertyChangedEventHandler<double> handler = null;
handler = (sender, e) =>
{
scrollBar.ValueChanged -= handler;
scrollViewer.ScrollToHorizontalOffset(offset);
};
scrollBar.ValueChanged += handler;
}
}
}
Requires using System.Linq; and using System.Windows.Controls.Primitives; and the Toolkit ofc.
Works well enough for me so far.

WPF animation: binding to the "To" attribute of storyboard animation

I'm trying to create a button that behaves similarly to the "slide" button on the iPhone. I have an animation that adjusts the position and width of the button, but I want these values to be based on the text used in the control. Currently, they're hardcoded.
Here's my working XAML, so far:
<CheckBox x:Class="Smt.Controls.SlideCheckBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Smt.Controls"
xmlns:System.Windows="clr-namespace:System.Windows;assembly=PresentationCore"
Name="SliderCheckBox"
mc:Ignorable="d">
<CheckBox.Resources>
<System.Windows:Duration x:Key="AnimationTime">0:0:0.2</System.Windows:Duration>
<Storyboard x:Key="OnChecking">
<DoubleAnimation Storyboard.TargetName="CheckButton"
Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(TranslateTransform.X)"
Duration="{StaticResource AnimationTime}"
To="40" />
<DoubleAnimation Storyboard.TargetName="CheckButton"
Storyboard.TargetProperty="(Button.Width)"
Duration="{StaticResource AnimationTime}"
To="41" />
</Storyboard>
<Storyboard x:Key="OnUnchecking">
<DoubleAnimation Storyboard.TargetName="CheckButton"
Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(TranslateTransform.X)"
Duration="{StaticResource AnimationTime}"
To="0" />
<DoubleAnimation Storyboard.TargetName="CheckButton"
Storyboard.TargetProperty="(Button.Width)"
Duration="{StaticResource AnimationTime}"
To="40" />
</Storyboard>
<Style x:Key="SlideCheckBoxStyle"
TargetType="{x:Type local:SlideCheckBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:SlideCheckBox}">
<Canvas>
<ContentPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
RecognizesAccessKey="True"
VerticalAlignment="Center"
HorizontalAlignment="Center" />
<Canvas>
<!--Background-->
<Rectangle Width="{Binding ElementName=ButtonText, Path=ActualWidth}"
Height="{Binding ElementName=ButtonText, Path=ActualHeight}"
Fill="LightBlue" />
</Canvas>
<Canvas>
<!--Button-->
<Button Width="{Binding ElementName=CheckedText, Path=ActualWidth}"
Height="{Binding ElementName=ButtonText, Path=ActualHeight}"
Name="CheckButton"
Command="{x:Static local:SlideCheckBox.SlideCheckBoxClicked}">
<Button.RenderTransform>
<TransformGroup>
<TranslateTransform />
</TransformGroup>
</Button.RenderTransform>
</Button>
</Canvas>
<Canvas>
<!--Text-->
<StackPanel Name="ButtonText"
Orientation="Horizontal"
IsHitTestVisible="False">
<Grid Name="CheckedText">
<Label Margin="7 0"
Content="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:SlideCheckBox}}, Path=CheckedText}" />
</Grid>
<Grid Name="UncheckedText"
HorizontalAlignment="Right">
<Label Margin="7 0"
Content="{Binding RelativeSource={RelativeSource AncestorType={x:Type local:SlideCheckBox}}, Path=UncheckedText}" />
</Grid>
</StackPanel>
</Canvas>
</Canvas>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked"
Value="True">
<Trigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource OnChecking}" />
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard Storyboard="{StaticResource OnUnchecking}" />
</Trigger.ExitActions>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</CheckBox.Resources>
<CheckBox.CommandBindings>
<CommandBinding Command="{x:Static local:SlideCheckBox.SlideCheckBoxClicked}"
Executed="OnSlideCheckBoxClicked" />
</CheckBox.CommandBindings>
</CheckBox>
And the code behind:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace Smt.Controls
{
public partial class SlideCheckBox : CheckBox
{
public SlideCheckBox()
{
InitializeComponent();
Loaded += OnLoaded;
}
public static readonly DependencyProperty CheckedTextProperty = DependencyProperty.Register("CheckedText", typeof(string), typeof(SlideCheckBox), new PropertyMetadata("Checked Text"));
public string CheckedText
{
get { return (string)GetValue(CheckedTextProperty); }
set { SetValue(CheckedTextProperty, value); }
}
public static readonly DependencyProperty UncheckedTextProperty = DependencyProperty.Register("UncheckedText", typeof(string), typeof(SlideCheckBox), new PropertyMetadata("Unchecked Text"));
public string UncheckedText
{
get { return (string)GetValue(UncheckedTextProperty); }
set { SetValue(UncheckedTextProperty, value); }
}
public static readonly RoutedCommand SlideCheckBoxClicked = new RoutedCommand();
void OnLoaded(object sender, RoutedEventArgs e)
{
Style style = TryFindResource("SlideCheckBoxStyle") as Style;
if (!ReferenceEquals(style, null))
{
Style = style;
}
}
void OnSlideCheckBoxClicked(object sender, ExecutedRoutedEventArgs e)
{
IsChecked = !IsChecked;
}
}
}
The problem comes when I try to bind the "To" attribute in the DoubleAnimations to the actual width of the text, the same as I'm doing in the ControlTemplate. If I bind the values to an ActualWidth of an element in the ControlTemplate, the control comes up as a blank checkbox (my base class). However, I'm binding to the same ActualWidths in the ControlTemplate itself without any problems. Just seems to be the CheckBox.Resources that have a problem with it.
For instance, the following will break it:
<DoubleAnimation Storyboard.TargetName="CheckButton"
Storyboard.TargetProperty="(Button.Width)"
Duration="{StaticResource AnimationTime}"
To="{Binding ElementName=CheckedText, Path=ActualWidth}" />
I don't know whether this is because it's trying to bind to a value that doesn't exist until a render pass is done, or if it's something else. Anyone have any experience with this sort of animation binding?
I've had similar situations in ControlTemplates where I've wanted to bind the "To" attribute to a value (rather than hard-coding it), and I finally found a solution.
Quick side note: If you dig around on the web you'll find examples of people being able to use data binding for the "From" or "To" properties. However, in those examples the Storyboards are not in a Style or ControlTemplate. If your Storyboard is in a Style or ControlTemplate, you'll have to use a different approach, such as this solution.
This solution gets around the freezable issue because it simply animates a double value from 0 to 1. It works with a clever use of the Tag property and a Multiply converter. You use a multibinding to bind to both a desired property and your "scale" (the Tag), which get multiplied together. Basically the idea is that your Tag value is what you animate, and its value acts like a "scale" (from 0 to 1) bringing your desired attribute value to "full scale" once you've animated the Tag to 1.
You can see this in action here. The crux of it is this:
<local:MultiplyConverter x:Key="multiplyConverter" />
<ControlTemplate x:Key="RevealExpanderTemp" TargetType="{x:Type Expander}">
<!-- (other stuff here...) -->
<ScrollViewer x:Name="ExpanderContentScrollView">
<!-- ** BEGIN IMPORTANT PART #1 ... -->
<ScrollViewer.Tag>
<sys:Double>0.0</sys:Double>
</ScrollViewer.Tag>
<ScrollViewer.Height>
<MultiBinding Converter="{StaticResource multiplyConverter}">
<Binding Path="ActualHeight" ElementName="ExpanderContent"/>
<Binding Path="Tag" RelativeSource="{RelativeSource Self}" />
</MultiBinding>
</ScrollViewer.Height>
<!-- ...end important part #1. -->
<ContentPresenter x:Name="ExpanderContent" ContentSource="Content"/>
</ScrollViewer>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<!-- ** BEGIN IMPORTANT PART #2 (make TargetProperty 'Tag') ... -->
<DoubleAnimation Storyboard.TargetName="ExpanderContentScrollView"
Storyboard.TargetProperty="Tag"
To="1"
Duration="0:0:0.4"/>
<!-- ...end important part #2 -->
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
With this value converter:
public class MultiplyConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
double result = 1.0;
for (int i = 0; i < values.Length; i++)
{
if (values[i] is double)
result *= (double)values[i];
}
return result;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new Exception("Not implemented");
}
}
As far as I know, you can't bind the animation to/from because the animation has to be freezable.
I like #Jason Frank's solution. However it is even easier and less error-prone if you don't use the Tag, but instead e.g. the Width property of an empty dummy Border element. It is a native double property, so no need for <sys:Double> syntax and you can name the Border just like you would do with a variable like so:
<!-- THIS IS JUST USED FOR SLIDING ANIMATION MATH -->
<!-- animated Border.Width From 0 to 1 -->
<Border x:Name="Var_Animation_0to1" Width="0"/>
<!-- animated Border.Width From 0 to (TotalWidth-SliderWidth) -->
<Border x:Name="Var_Slide_Length">
<Border.Width>
<MultiBinding Converter="{mvvm:MathConverter}" ConverterParameter="a * (b-c)">
<Binding ElementName="Var_Animation_0to1" Path="Width"/>
<Binding ElementName="BackBorder" Path="ActualWidth"/>
<Binding ElementName="Slider" Path="ActualWidth"/>
</MultiBinding>
</Border.Width>
</Border>
That makes the bindings much more readable.
The Animation is always 0..1, as Jason pointed out:
<BeginStoryboard Name="checkedSB">
<Storyboard Storyboard.TargetProperty="Width" Storyboard.TargetName="Var_Animation_0to1">
<DoubleAnimation To="1" Duration="00:00:00.2"/>
</Storyboard>
</BeginStoryboard>
Then bind whatever you want to animate to the Width of the dummy border. This way you can even chain converters to each other like so:
<Border x:Name="Slider" HorizontalAlignment="Left"
Margin="{Binding ElementName=Var_Slide_Length, Path=Width, Converter={StaticResource DoubleToThickness}, ConverterParameter=x 0 0 0}"/>
Combined with the MathConverter you can do almost anything in styles:
https://www.codeproject.com/Articles/239251/MathConverter-How-to-Do-Math-in-XAML
I implemented this exact thing.
<UserControl x:Class="YOURNAMESPACE.UserControls.SliderControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:YOURNAMESPACE.UserControls"
xmlns:converter="clr-namespace:YOURNAMESPACE.ValueConverters"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
SizeChanged="UserControl_SizeChanged">
<UserControl.Resources>
<converter:MathConverter x:Key="mathConverter" />
<LinearGradientBrush x:Key="CheckedBlue" StartPoint="0,0" EndPoint="0,1">
<GradientStop Color="#e4f5fc" Offset="0" />
<GradientStop Color="#e4f5fc" Offset="0.1" />
<GradientStop Color="#e4f5fc" Offset="0.1" />
<GradientStop Color="#9fd8ef" Offset="0.5" />
<GradientStop Color="#9fd8ef" Offset="0.5" />
<GradientStop Color="#bfe8f9" Offset="1" />
</LinearGradientBrush>
<LinearGradientBrush x:Key="CheckedOrange" StartPoint="0,0" EndPoint="0,1">
<GradientStop Color="#FFCA6A13" Offset="0" />
<GradientStop Color="#FFF67D0C" Offset="0.1" />
<GradientStop Color="#FFFE7F0C" Offset="0.1" />
<GradientStop Color="#FFFA8E12" Offset="0.5" />
<GradientStop Color="#FFFF981D" Offset="0.5" />
<GradientStop Color="#FFFCBC5A" Offset="1" />
</LinearGradientBrush>
<SolidColorBrush x:Key="CheckedOrangeBorder" Color="#FF8E4A1B" />
<SolidColorBrush x:Key="CheckedBlueBorder" Color="#FF143874" />
<Style x:Key="CheckBoxSlider" TargetType="{x:Type CheckBox}">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}" />
<Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CheckBox}" >
<DockPanel x:Name="dockPanel"
Width="{TemplateBinding ActualWidth}"
Height="{TemplateBinding Height}" >
<DockPanel.Resources>
<Storyboard x:Key="ShowRightStoryboard">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="slider" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)">
<SplineDoubleKeyFrame KeyTime="00:00:00.1000000" Value="0" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="ShowLeftStoryboard" >
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00"
Storyboard.TargetName="slider"
Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)"
>
<SplineDoubleKeyFrame x:Name="RightHalfKeyFrame" KeyTime="00:00:00.1000000" Value="300" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</DockPanel.Resources>
<ContentPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
Content="{TemplateBinding Content}"
ContentStringFormat="{TemplateBinding ContentStringFormat}"
ContentTemplate="{TemplateBinding ContentTemplate}"
RecognizesAccessKey="True"
VerticalAlignment="Center" />
<Grid>
<Border x:Name="BackgroundBorder" BorderBrush="#FF939393" BorderThickness="1" CornerRadius="3"
Width="{TemplateBinding ActualWidth}"
Height="{TemplateBinding Height}" >
<Border.Background>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop Color="#FFB5B5B5" Offset="0" />
<GradientStop Color="#FFDEDEDE" Offset="0.1" />
<GradientStop Color="#FFEEEEEE" Offset="0.5" />
<GradientStop Color="#FFFAFAFA" Offset="0.5" />
<GradientStop Color="#FFFEFEFE" Offset="1" />
</LinearGradientBrush>
</Border.Background>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBlock x:Name="LeftTextBlock" Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:SliderControl}}, Path=LeftText, Mode=TwoWay}" Grid.Column="0" HorizontalAlignment="Center" VerticalAlignment="Center" />
<TextBlock x:Name="RightTextBlock" Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:SliderControl}}, Path=RightText, Mode=TwoWay}" Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</Border>
<Border x:Name="slider"
BorderBrush="#FF939393"
HorizontalAlignment="Left"
Width="{TemplateBinding ActualWidth, Converter={StaticResource mathConverter}, ConverterParameter=/2}"
Height="{TemplateBinding Height}"
BorderThickness="1"
CornerRadius="3"
RenderTransformOrigin="0.5,0.5" Margin="0"
>
<Border.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="1" ScaleY="1" />
<SkewTransform AngleX="0" AngleY="0" />
<RotateTransform Angle="0" />
<TranslateTransform X="{TemplateBinding ActualWidth, Converter={StaticResource mathConverter}, ConverterParameter=/2}" Y="0" />
</TransformGroup>
</Border.RenderTransform>
<Border.Background>
<LinearGradientBrush EndPoint="0,1" StartPoint="0,0">
<GradientStop Color="#FFF0F0F0" Offset="0" />
<GradientStop Color="#FFCDCDCD" Offset="0.1" />
<GradientStop Color="#FFFBFBFB" Offset="1" />
</LinearGradientBrush>
</Border.Background>
<DockPanel Background="Transparent" LastChildFill="False">
<Viewbox x:Name="SlideRight" Stretch="Uniform" Width="28" Height="28" DockPanel.Dock="Right" Margin="0,0,50,0" >
<Path Stretch="Fill" Fill="{DynamicResource TextBrush}">
<Path.Data>
<PathGeometry Figures="m 27.773437 48.874779 -8.818359 9.902343 -4.833984 0 8.847656 -9.902343 -8.847656 -10.019532 4.833984 0 z m -11.396484 0 -8.7597655 9.902343 -4.9804687 0 9.0234372 -9.902343 -9.0234372 -10.019532 4.9804687 0 z" FillRule="NonZero"/>
</Path.Data>
</Path>
</Viewbox>
<Viewbox x:Name="SlideLeft" Stretch="Uniform" Width="28" Height="28" DockPanel.Dock="Left" Margin="50,0,0,0" >
<Path Stretch="Fill" Fill="{DynamicResource TextBrush}">
<Path.LayoutTransform>
<TransformGroup>
<ScaleTransform ScaleX="-1"/>
</TransformGroup>
</Path.LayoutTransform>
<Path.Data>
<PathGeometry Figures="m 27.773437 48.874779 -8.818359 9.902343 -4.833984 0 8.847656 -9.902343 -8.847656 -10.019532 4.833984 0 z m -11.396484 0 -8.7597655 9.902343 -4.9804687 0 9.0234372 -9.902343 -9.0234372 -10.019532 4.9804687 0 z" FillRule="NonZero"/>
</Path.Data>
</Path>
</Viewbox>
</DockPanel>
</Border>
</Grid>
</DockPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="BackgroundBorder" Property="Background" Value="{StaticResource CheckedOrange}" />
<Setter TargetName="BackgroundBorder" Property="BorderBrush" Value="{StaticResource CheckedOrangeBorder}" />
<Setter TargetName="SlideRight" Property="Visibility" Value="Collapsed" />
<Setter TargetName="SlideLeft" Property="Visibility" Value="Visible" />
</Trigger>
<Trigger Property="IsChecked" Value="False">
<Setter TargetName="BackgroundBorder" Property="Background" Value="{StaticResource CheckedBlue}" />
<Setter TargetName="BackgroundBorder" Property="BorderBrush" Value="{StaticResource CheckedBlueBorder}" />
<Setter TargetName="SlideRight" Property="Visibility" Value="Visible" />
<Setter TargetName="SlideLeft" Property="Visibility" Value="Collapsed" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<CheckBox x:Name="checkBox"
Style="{StaticResource CheckBoxSlider}"
HorizontalAlignment="Stretch"
DockPanel.Dock="Top"
Height="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:SliderControl}}, Path=Height, Mode=TwoWay}"
IsChecked="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:SliderControl}}, Path=IsLeftVisible, Mode=TwoWay}"
Checked="CheckBox_Checked"
Unchecked="CheckBox_Unchecked"
/>
</Grid>
</UserControl>
code behind.
namespace YOURNAMESPACE.UserControls
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
/// <summary>
/// Interaction logic for SliderControl.xaml
/// </summary>
public partial class SliderControl : UserControl
{
public static readonly DependencyProperty IsLeftVisibleProperty =
DependencyProperty.RegisterAttached(
"IsLeftVisible",
typeof(bool),
typeof(SliderControl),
new UIPropertyMetadata(true, IsLeftVisibleChanged));
public static readonly DependencyProperty LeftTextProperty =
DependencyProperty.RegisterAttached(
"LeftText",
typeof(string),
typeof(SliderControl),
new UIPropertyMetadata(null, LeftTextChanged));
public static readonly DependencyProperty RightTextProperty =
DependencyProperty.RegisterAttached(
"RightText",
typeof(string),
typeof(SliderControl),
new UIPropertyMetadata(null, RightTextChanged));
/// <summary>
/// Initializes a new instance of the <see cref="SliderControl"/> class.
/// </summary>
public SliderControl()
{
this.InitializeComponent();
}
public string LeftText { get; set; }
public string RightText { get; set; }
[AttachedPropertyBrowsableForType(typeof(SliderControl))]
public static bool GetIsLeftVisible(SliderControl sliderControl)
{
return (bool)sliderControl.GetValue(IsLeftVisibleProperty);
}
[AttachedPropertyBrowsableForType(typeof(SliderControl))]
public static string GetLeftText(SliderControl sliderControl)
{
return (string)sliderControl.GetValue(LeftTextProperty);
}
public static void SetIsLeftVisible(SliderControl sliderControl, bool value)
{
sliderControl.SetValue(IsLeftVisibleProperty, value);
}
public static void IsLeftVisibleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
SliderControl slider = d as SliderControl;
if ((bool)e.NewValue == true)
{
slider.RunAnimation("ShowLeftStoryboard");
}
else
{
slider.RunAnimation("ShowRightStoryboard");
}
}
[AttachedPropertyBrowsableForType(typeof(SliderControl))]
public static string GetRightText(SliderControl sliderControl)
{
return (string)sliderControl.GetValue(RightTextProperty);
}
public static void SetLeftText(SliderControl sliderControl, string value)
{
sliderControl.SetValue(LeftTextProperty, value);
}
public static void SetRightText(SliderControl sliderControl, string value)
{
sliderControl.SetValue(RightTextProperty, value);
}
private static void LeftTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
SliderControl slider = d as SliderControl;
slider.LeftText = e.NewValue as string;
}
private static void RightTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
SliderControl slider = d as SliderControl;
slider.RightText = e.NewValue as string;
}
private void UserControl_SizeChanged(object sender, SizeChangedEventArgs e)
{
this.checkBox.Width = e.NewSize.Width;
CheckBox cb = this.checkBox;
var controlTemplate = cb.Template;
DockPanel dockPanel = controlTemplate.FindName("dockPanel", cb) as DockPanel;
Storyboard story = dockPanel.Resources["ShowLeftStoryboard"] as Storyboard;
DoubleAnimationUsingKeyFrames dk = story.Children[0] as DoubleAnimationUsingKeyFrames;
SplineDoubleKeyFrame sk = dk.KeyFrames[0] as SplineDoubleKeyFrame;
// must manipulate this in code behind, binding does not work,
// also it cannot be inside of a control template
// because storyboards in control templates become frozen and cannot be modified
sk.Value = cb.Width / 2;
if (cb.IsChecked == true)
{
story.Begin(cb, cb.Template);
}
}
private void CheckBox_Checked(object sender, RoutedEventArgs e)
{
this.RunAnimation("ShowLeftStoryboard");
}
private void CheckBox_Unchecked(object sender, RoutedEventArgs e)
{
this.RunAnimation("ShowRightStoryboard");
}
private void RunAnimation(string storyboard)
{
CheckBox cb = this.checkBox;
var controlTemplate = cb.Template;
DockPanel dockPanel = controlTemplate.FindName("dockPanel", cb) as DockPanel;
if (dockPanel != null)
{
Storyboard story = dockPanel.Resources[storyboard] as Storyboard;
DoubleAnimationUsingKeyFrames dk = story.Children[0] as DoubleAnimationUsingKeyFrames;
SplineDoubleKeyFrame sk = dk.KeyFrames[0] as SplineDoubleKeyFrame;
story.Begin(cb, cb.Template);
}
}
}
}
IValueConverter
namespace YOURNAMESPACE.ValueConverters
{
using System;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Data;
/// <summary>
/// Does basic math operations, eg. value = 60 parameter = "*2 + 1", result = 121
/// </summary>
/// <seealso cref="System.Windows.Data.IValueConverter" />
[ValueConversion(typeof(double), typeof(double))]
public class MathConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
double d = (double)value;
string mathExpression = d.ToString() + parameter;
if (mathExpression.Contains("^"))
{
throw new Exception("Doesn't handle powers or square roots");
}
DataTable table = new DataTable();
table.Columns.Add("expression", typeof(string), mathExpression);
DataRow row = table.NewRow();
table.Rows.Add(row);
double ret = double.Parse((string)row["expression"]);
if (ret <= 0)
{
return 1d;
}
return ret;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
// not implemented
return null;
}
}
}
example usage:
<chart:SliderControl x:Name="sliderControl"
LeftText="{Binding Path=LeftTextProperty}"
RightText="{Binding Path=RightTextProperty}"
Grid.Row="0"
IsLeftVisible="{Binding Path=IsLeftVisible, Mode=TwoWay}"
Height="35" />

Resources