GoToState not working on controltemplated usercontrol based on combobox - silverlight

I'm trying to create a custom combobox that shows a loading animation when a bool property is set on it (e.g. IsLoading).
I've created a control template in Blend based on the combobox, and added a textblock inside the togglebutton template. In the code behind I'm calling VisualStateManager.GoToState but it always returns false. I was trying to move to a custom state initially, but I can't even move to states such as Disabled or MouseOver.
I have a usercontrol that just contains a combobox and the style is set to my combobox style containing the control template. I presume GoToState fails because the state is not on the control itself, but inside the combobox. How can I access this?
I'm at a loss as to how to debug this as there are no errors.
thanks

I had a similar problem! I had the Visual States defined within a grid in the ControlTemplate.
<Style x:Key="Image3TextRowButtonStyle" TargetType="Button">
<Setter Property="Foreground" Value="{StaticResource ForegroundBrush}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid x:Name="RootGrid" Background="{StaticResource BackgroundBrush}">
...
<VisualStateManager.VisualStateGroups>
...
<VisualStateGroup x:Name="ActiveStates">
<VisualState x:Name="Active">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Panel.Background)" Storyboard.TargetName="RootGrid">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource BackgroundActiveBrush}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="NotActive" />
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And the user control:
<UserControl x:Class="Griesser.Presentation.ContactCenterClient.Client.Control.Image3TextRowButtonUC"
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"
x:Name="Image3TextRowButton">
<Grid>
<Button x:Name="btn" Command="{Binding Command, ElementName=Image3TextRowButton}" Style="{StaticResource Image3TextRowButtonStyle}" />
</Grid>
In the code behind I am looking first for the RootGrid and then use GoToElementState:
private void ChangeVisualActiveState(bool useTransitions)
{
// Search RootGrid, because the Visual States are defined in the ControlTemplate!
FrameworkElement dt = btn.Template.FindName("RootGrid", btn) as FrameworkElement;
if (IsActive)
{
VisualStateManager.GoToElementState(dt, "Active", useTransitions);
}
else
{
VisualStateManager.GoToElementState(dt, "NotActive", useTransitions);
}
}

Related

Thickness animation changes brush color Textbox | WPF .NET Framework 8

I'm working on a style library for my own on a MVVM WPF App.
When i use a custom style in XAML on my textbox which contains a Trigger.EnterActions with a ThicknessAnimation on my border, my animation will append but it changes the initial BorderBrush of my textbox by the default blue color.
Here is my XAML Dictionnary
<Style x:Key="TEST1"
TargetType="TextBox">
<!--SETTERS-->
<Style.Setters>
<Setter Property="VerticalContentAlignment"
Value="Center" />
</Style.Setters>
<!--TRIGGERS-->
<Style.Triggers>
<Trigger Property="IsKeyboardFocused"
Value="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ThicknessAnimation Duration="0:0:0.100" Storyboard.TargetProperty="BorderThickness" To="1 1 1 3" />
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<ThicknessAnimation Duration="0:0:0.300" To="1" Storyboard.TargetProperty="BorderThickness" />
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
</Style.Triggers>
</Style>
Here is my textbox on my window :
<Window x:Class="Design.View.Textboxes"
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:Design.View"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
Title="Textboxes"
WindowStyle="SingleBorderWindow">
<Border Width="500"
Height="Auto">
<StackPanel Orientation="Horizontal">
<TextBox Width="100"
Height="30"
Margin="4"/>
<TextBox Width="100"
Height="30"
Margin="4"
BorderBrush="{StaticResource Primary}"
Style="{StaticResource TEST1}" />
</StackPanel>
</Border>
</Window>
I was expecting the border to fit the color already set in the BorderBrush field of the Textbox Template during the animation but it doesn't.
I tried to use ColorAnimation and didn't know how to make it functional and i do believe that it is a weird way to solve this problem and not clean at all.
I thought about creating a custom textbox inherating of TextBox class with a new dependency containing the color and set it in every Storyboard...
But i want to know if there is a better way to make it work easily and let the code as clean as possible in pure XAML without any code behind.

WPF ControlTemplate with VisualStates

I want to create a ControlTemplate with predefined VisualStates. I want to use them with GoToStateActions and DataTriggers.
I don't know what exactly is going wrong here. It seems to me, that the Binding isn't established in that way I suppose it is.
namespace ControlTemplateVisualState
{
using System.Windows.Controls;
public class MyControl : ContentControl
{
public MyControl()
{
this.DefaultStyleKey = typeof(MyControl);
}
}
}
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ControlTemplateVisualState="clr-namespace:ControlTemplateVisualState"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions">
<ControlTemplate x:Key="MyControlTemplate" TargetType="{x:Type ControlTemplateVisualState:MyControl}">
<Grid x:Name="grid" Background="Red">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="VisualStateGroup">
<VisualState x:Name="IsDisabledState">
<Storyboard>
<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)" Storyboard.TargetName="grid">
<EasingColorKeyFrame KeyTime="0" Value="#FF2BFF00"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<i:Interaction.Triggers>
<ei:DataTrigger Binding="{Binding IsEnabled, ElementName=grid}" Value="False">
<ei:GoToStateAction StateName="IsDisabledState"/>
</ei:DataTrigger>
</i:Interaction.Triggers>
</Grid>
</ControlTemplate>
<Style TargetType="{x:Type ControlTemplateVisualState:MyControl}">
<Setter Property="Background" Value="#FF0010FF"/>
<Setter Property="Template" Value="{StaticResource MyControlTemplate}"/>
</Style>
</ResourceDictionary>
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlTemplateVisualState.MainWindow"
x:Name="Window"
xmlns:local="clr-namespace:ControlTemplateVisualState"
Title="MainWindow"
Width="640" Height="480">
<Grid x:Name="LayoutRoot">
<local:MyControl IsEnabled="False"/>
</Grid>
</Window>
You could give this a go:
<i:Interaction.Behaviors>
<si:DataStateBehavior Binding='{Binding IsLoggedIn}' Value='True'
TrueState='LoggedInState' FalseState='LoggedOutState'/>
</i:Interaction.Behaviors>
It's slightly different, but works even with Silverlight, see: http://expressionblend.codeplex.com/wikipage?title=Behaviors%20and%20Effects&referringTitle=Documentation
Just make sure to get the fixed version if you use WPF4: http://expressionblend.codeplex.com/workitem/8148
I don't think Blend SDK triggers and behaviors can be used in control templates -- only in UserControls: there's no concrete object to which the trigger can be attached when the XAML is parsed. (I'm not 100% certain of this explanation, but I do know that if you have multiple control templates, you can't put Blend behaviors in all of them.) You'll need code behind to invoke the VSM -- that's just how custom controls work. You'll use something like this code from the Silverlight Toolkit:
public static void GoToState(Control control, bool useTransitions, params string[] stateNames)
{
foreach (var name in stateNames)
{
if (VisualStateManager.GoToState(control, name, useTransitions))
break;
}
}

Animate 'Style' property of Control using ObjectAnimationUsingKeyFrames in WPF

I am trying to animate 'Style' property using ObjectAnimationUsingKeyFrames. When I run the sample below, I just see empty window and there are no any exceptions.
Almost the same sample works in Silverlight. In WPF it works too, if I assign 'Style' property of the control directly. Does anyone know if it is possible to animate 'Style' property in WPF?
Many thanks.
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:this="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525"
>
<Window.Resources>
<ResourceDictionary>
<Style x:Key="TestStyle" TargetType="Control">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Canvas x:Name="Rectangle">
<Rectangle Width="200" Height="150" Fill="Red"/>
</Canvas>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
</Window.Resources>
<Canvas>
<Canvas.Triggers>
<EventTrigger RoutedEvent="Canvas.Loaded">
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Target" Storyboard.TargetProperty="Style" >
<DiscreteObjectKeyFrame KeyTime="0:0:0.0" Value="{StaticResource ResourceKey=TestStyle}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Canvas.Triggers>
<Canvas.Children>
<ContentControl x:Name="Target"/>
</Canvas.Children>
</Canvas>
When ObjectAnimationUsingKeyFrames tries to animate to a value that is derived from DependencyObject, it attempts to freeze the object first. If the object can't be frozen, it throws an exception and the animation does not run.
If you are animating a value of a custom type that you wrote, it appears you need to either derive from Freezable or NOT derive from DependencyObject.
For properties that already exist that derive from DependencyObject and not Freezable, you can't animate them (StyleProperty or TemplateProperty are cases in point). Try using a property setter inside of a style:
<Style.Triggers>
<Trigger Property="IsEnabled" Value="True">
<Setter Property="Template" Value="{StaticResource TestTemplate}"/>
</Trigger>
</Style.Triggers>
Build all of the transition logic into the style instead of switching between different styles. A challenge that you may have with this is that the target property has to be a dependency property so you can't use IsLoaded.
I hope you find this useful.
One final thought: It is possible to define custom animations, although I have not done this myself. There's an outside chance that you could write your own custom "ObjectAnimation" that would not be restricted to Freezable or non-DependencyObject classes.

How to bind Opacity value based on opacity of the object from another ControlTemplate?

I’d like to find out how to bind the opacity of the object that is part of the ControlTemplate to the object that is part of another ControlTemplate.
I tried this but it is not doing anything.
Image x:Name="PART_IconHover" Source="{Binding IconHover}" Opacity="{Binding Opacity, ElementName=border, Mode=OneWay}" />
Below is the code of two ControlTemplates:
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border x:Name="border" BorderBrush="#FF6E6E6E" BorderThickness="0.5" Opacity="0" Background="#00000000">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="MouseOver">
<Storyboard>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="border">
<EasingDoubleKeyFrame KeyTime="0" Value="1"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed"/>
<VisualState x:Name="Disabled"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsKeyboardFocused" Value="true"/>
<Trigger Property="ToggleButton.IsChecked" Value="true"/>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="#ADADAD"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
I want to bind the opacity to the image in the ControlTemplate below
<ControlTemplate x:Key="ThumbnailContainerTemplate" TargetType="{x:Type ContentControl}">
<Border x:Name="PART_Border" BorderThickness="1" BorderBrush="#FFd9d9d9" Opacity="0" />
<Grid Margin="10">
<Image x:Name="PART_IconHover" Source="{Binding IconHover}" Opacity="{Binding Opacity, ElementName=border, Mode=OneWay}" />
</Grid>
Any ideas are highly appreciated. Thank you in advance!
I don't think that you can bind to elements inside templates like that. The binding system isn't able to find them.
If you just need a numeric value somewhere in your xaml that you want everything to use, you can just add one like this:
<sys:Double x:Key="Opacity">.5</sys:Double>
Then just have everything bind to that. You'll need to add the sys namespace
xmlns:sys="clr-namespace:System;assembly=mscorlib"
As mdm20 said, you can't bind to elements inside templates from outside the template since a template is just used to build up a control. For instance, several Buttons could use the Template in your example so which Button would the ContentControl bind to?
I can't see a re-usable solution to this but one thing that comes to mind is to set the Binding in code behind once the Controls have finished loading like this
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Border border = myButton.Template.FindName("border", myButton) as Border;
Image PART_IconHover = contentControl.Template.FindName("PART_IconHover", contentControl) as Image;
Binding opacityBinding = new Binding("Opacity");
opacityBinding.Mode = BindingMode.OneWay;
opacityBinding.Source = border;
PART_IconHover.SetBinding(Image.OpacityProperty, opacityBinding);
}
Update
Two Controls binding to the border in a Button template. The binding is made in the Control_Loaded event handler.
<ContentControl ...
Loaded="Control_Loaded">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Setter Property="Template" Value="{StaticResource contentTemplate}"/>
</Style>
</ContentControl.Style>
</ContentControl>
<ContentControl ...
Loaded="Control_Loaded">
<ContentControl.Style>
<Style TargetType="ContentControl">
<Setter Property="Template" Value="{StaticResource contentTemplate}"/>
</Style>
</ContentControl.Style>
</ContentControl>
private void Control_Loaded(object sender, RoutedEventArgs e)
{
Border border = myButton.Template.FindName("border", myButton) as Border;
Control control = sender as Control;
Image PART_IconHover = control.Template.FindName("PART_IconHover", control) as Image;
Binding opacityBinding = new Binding("Opacity");
opacityBinding.Mode = BindingMode.OneWay;
opacityBinding.Source = border;
PART_IconHover.SetBinding(Image.OpacityProperty, opacityBinding);
}

Binding storyboard animation values

I have an enum on a viewmodel which represents one of 5 colors, and I am using a ValueConverter to convert these values into actual colors.
Specifically it's for the color of each listbox item's background to change to as it is hovered over.
I have a custom control with a visual state manager and a mouseover group which uses a SplineColorKeyFrame to animate the hover color (This is set in the xaml of control template). The custom control just has a dependency property on it for the hover color.
This works great if the Value of the SplineColorKeyFrame is from a resource, or set in the xaml as a fixed color. However it just animates to transparent when I set the Value to "{TemplateBinding HoverColor}"
Even setting the DependencyProperty in the xaml to a color, and trying to use the TemplateBinding in the control to read the color causes the problem, the animation won't use the right color if I tell it to get it from a binding or template binding.
I've snooped the app and can see that the dependency property has the correct value, but it doesn't seem to be picking up that value in the animation.
Can anyone suggest how to get around this?
Here's my control template:
<Style TargetType="{x:Type local:MyCustomControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:MyCustomControl}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}" x:Name="border">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="MouseOverGroup">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="0:0:0.2"/>
</VisualStateGroup.Transitions>
<VisualState x:Name="MouseOver">
<Storyboard>
<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)" Storyboard.TargetName="border">
<SplineColorKeyFrame KeyTime="0" Value="{TemplateBinding HoverColor}" />
</ColorAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Normal"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<VisualStateManager.CustomVisualStateManager>
<ic:ExtendedVisualStateManager/>
</VisualStateManager.CustomVisualStateManager>
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseEnter">
<ic:GoToStateAction x:Name="MouseOverAction" StateName="MouseOver"/>
</i:EventTrigger>
<i:EventTrigger EventName="MouseLeave">
<ic:GoToStateAction x:Name="NormalAction" StateName="Normal"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Here's my custom control code:
public class MyCustomControl : Control
{
public static DependencyProperty HoverColorProperty = DependencyProperty.Register("HoverColor", typeof (Color),
typeof (MyCustomControl));
public Color HoverColor
{
get
{
return (Color)GetValue(HoverColorProperty);
}
set
{
SetValue(HoverColorProperty, value);
}
}
static MyCustomControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyCustomControl), new FrameworkPropertyMetadata(typeof(MyCustomControl)));
}
}
Here's the main window xaml:
<Window x:Class="Test.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Test="clr-namespace:Test" Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<Test:ViewModel/>
</Window.DataContext>
<Grid>
<Test:MyCustomControl HoverColor="Yellow"
HorizontalAlignment="Center" VerticalAlignment="Center" Width="150" Height="150"
Background="Bisque">
</Test:MyCustomControl>
</Grid>
</Window>
I found the answer to this problem. It turns out that, for some reason, the binding doesn't work if the StoryBoard is inline in the Xaml. If you put the Storyboard in a resource and refer to the resource in the VisualStateManager, then everything works.

Resources