WPF Animation: can't bind From property to the ActualWidth - wpf

I'm trying to change a selected TabItem's width in the following way:
<Style TargetType="{x:Type TabItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Grid x:Name="Root">
<Border x:Name="Border" Background="{TemplateBinding Background}">
<ContentPresenter x:Name="ContentSite" VerticalAlignment="Center" HorizontalAlignment="Stretch" ContentSource="Header" Margin="12,2,12,2"/>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Width"
From="{Binding RelativeSource={RelativeSource AncestorType={x:Type TabItem}, Mode=FindAncestor}, Path=ActualWidth}"
To="200"
Duration="0:0:1"/>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
</Trigger>
</Style.Triggers>
</Style>
I'm forced to specify the "From" property for the animation because I don't specify the "Width" property for the TabItem explicitly, it is calculated by the custom layout container.
But when I try to bind "From" property of the animation to the "ActualWidth" property of the TabItem, the initialization of 'System.Windows.Controls.TabItem' threws an exception. I tried to move the "Triggers" section inside the "ControlTemplate" section, but with no effect. So I have 2 questions:
1) Why can't the "From" property be bound to the ActualWidth of the TabItem in the way I did it?
2) How can I achieve the desired behaviour?
Any hints would be appreciated.

This cannot be done because WPF framework will freeze the things which would used for the Animation by initially and So if you are trying to Bind on a Freezable, it doest accept that. That's why it was prevented from changing and says,
Cannot freeze this Storyboard timeline tree for use across threads

Related

How to avoid that a Coloranimation in a ControlTemplate gets applied to every object using the template

I declared ColorAnimations inside a ControlTemplate of a Style.
What it should do:
Whenever the mouse hovers over the object the color of this specific object should be animated.
What it does instead:
Animating the color of EVERY object the style is applied to whenever I hover over one of them, even though the property activating the animation is not changing on all objects.
What I tried before:
I tried using an Eventrigger instead of a normal trigger but the problem persists.
I also tried using the "Name" property not "x:Name" but this didn't help either.
Also not using Storyboard.TargetName but Storyboard.Target and using a binding with RelativeSource to let it find the object.. and still every object using this style gets animated whenever I hover over any of them
It works as intended if I use Setters to change the background instead of Storyboards and ColorAnimations.
The Style
<Style x:Key="Fraction_ScrollViewer_ScrollBar_Thumb" TargetType="{x:Type Thumb}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Thumb}">
<Border
x:Name="Border"
CornerRadius="5"
Background="{TemplateBinding Background}"
BorderThickness="0" />
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Trigger.EnterActions>
<BeginStoryboard
Name="IsMouseOver_True"
HandoffBehavior="Compose">
<Storyboard>
<ColorAnimation
Storyboard.TargetName="Border"
Storyboard.TargetProperty="Background.(SolidColorBrush.Color)"
To="{StaticResource 'Color_CoolGrey'}"
Duration="0:0:0.2" />
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
<Trigger.ExitActions>
<BeginStoryboard
Name="IsMouseOver_False">
<Storyboard>
<ColorAnimation
Storyboard.TargetName="Border"
Storyboard.TargetProperty="Background.(SolidColorBrush.Color)"
To="{StaticResource 'Color_MidGrey'}"
Duration="0:0:0.2" />
</Storyboard>
</BeginStoryboard>
</Trigger.ExitActions>
</Trigger>
The Thumb style is used in a Scrollbar style which is used in a ScrollViewer.
The Scrollviewer Style is then used in 2 locations:
1:
<Style x:Key="LabelTreeView" TargetType="{x:Type TreeView}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TreeView}">
<ScrollViewer
Style="{StaticResource ScrollViewer}"
Focusable="False"
CanContentScroll="False"
Padding="4">
<ItemsPresenter />
</ScrollViewer>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
2:
<ScrollViewer
Style="{StaticResource ScrollViewer}"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto">
<ItemsControl
BorderThickness="0"
Background="{StaticResource Brush_Transparent}"
ItemTemplate="{StaticResource CharacterSequenceChar}"
ItemsSource="{Binding DisplayedCharacterSequenceCharacters}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ScrollViewer>
What is causing this behaviour and how to avoid it with still using animations?
Apparently all Buttons share the same Brush instance in their Background property.
You could explicitly assign a separate Brush for each Border in the Template:
<Border x:Name="Border" ...>
<Border.Background>
<SolidColorBrush Color="{Binding Background.Color,
RelativeSource={RelativeSource TemplatedParent}}"/>
</Border.Background>
</Border>

Handling Button states in WPF

I have a WPF application with a menu bar with some buttons to navigate to the different sections.
All the buttons have an style with a VisualState property, so all of them have a Normal, OnMouseOver and Pressed state different styles. And all the buttons, once pressed, execute a Command on the ModelView.
What i want is that each time a button is pressed, first it keeps the Pressed state until another button is pressed and second, it is possible to change the button state from the Command event executed at the ModelView side.
The Code:
<Style x:Key="MainButtonStyle" TargetType="Button">
<Setter Property="Width" Value="120"/>
<Setter Property="Height" Value="60"/>
<Setter Property="Margin" Value="5"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Grid Background="White" x:Name="ButtonGrid">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="MouseOver">
<Storyboard>
<ColorAnimation Duration="0:0:0.3" Storyboard.TargetName="ButtonGrid" Storyboard.TargetProperty="Background.Color" To="Cyan"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed">
<Storyboard>
<ColorAnimation Duration="0:0:0.2" Storyboard.TargetName="ButtonGrid" Storyboard.TargetProperty="Background.Color" To="DarkSalmon"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The buttons using the style:
<ItemsControl Margin="10" Grid.Column="1" Grid.RowSpan="2" ItemsSource="{Binding HeaderButtons}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Style="{StaticResource MainButtonStyle}" Content="{Binding content}"
Command="{Binding DataContext.ChangePageContainerCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
CommandParameter="{Binding containerType}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
And i think that the ModelView is not relevant since it does nothing on the Button VisualState since i don't know how to pass it a reference to the Button caller (as parameter or something?).
I have seen in other posts that people recommend to use a ToggleButton in place of a Button and set the IsChecked to true, but i don´t know where should i set that property, i mean, should i set it at the XAML or at code? and how?
What i want is that each time a button is pressed, first it keeps the Pressed state until another button is pressed
What you want is a group of RadioButtons, but probably ones that are styled to look like normal Buttons. Here is a small example of some RadioButtons that look somewhat like Buttons:
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
<Style TargetType="{x:Type RadioButton}">
<Setter Property="BorderBrush" Value="Blue" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type RadioButton}">
<Border Name="Border" CornerRadius="4" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="2" Padding="10, 5" VerticalAlignment="Top" Margin="0,0,5,0">
<ContentPresenter />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="Border" Property="Border.Background" Value="LightBlue" />
</Trigger>
<Trigger Property="IsChecked" Value="True">
<Setter TargetName="Border" Property="Border.Background" Value="LightGreen" />
<Setter TargetName="Border" Property="Border.BorderBrush" Value="Green" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</StackPanel.Resources>
<RadioButton Content="Button 1" />
<RadioButton Content="Button 2" />
<RadioButton Content="Button 3" />
</StackPanel>
Note that I have added some basic state functionality using basic Triggers... using the VisualStateManager is simply unnecessary here and seems to be causing extra problems for you. The VisualStateManager really comes into its own when we write complex UserControls or CustomControls that require definite separate states. For your simple purposes, you should just use Triggers instead.
is it possible to change the button state from the Command event executed at the ModelView side?
The answer to your second question has been answered so many times before that UI won't go into it all again here. Suffice it to say that you can find details in the How to bind RadioButtons to an enum? question (or in many other online sources). In short though, you need to define an enum, declare an EnunmToBoolConverter to enable you to data bind your enum to the bool IsChecked property of each RadioButton. You will then be able to set each Button to be selected or not from the view model.

DataGridColumnHeader Style

<Style TargetType="DataGridColumnHeader">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridColumnHeader}">
<Border x:Name="buttonBorderOuter"
BorderBrush="#DBDBDB"
BorderThickness="1"
Background="#00ECECEC"
Padding="2">
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
<Border.Triggers>
<EventTrigger RoutedEvent="MouseEnter">
<BeginStoryboard>
<Storyboard>
...
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="MouseLeave">
<BeginStoryboard>
<Storyboard>
...
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Border.Triggers>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The above code shows my Style for the DataGridColumnHeader. It's working fine with the MouseEnter and MouseLeave effect but there are some small things I don't like. There is what I have right now below here.
The problem here is that each Cell in the Header has the rounded border. I want that between 2 Cells in the Header is 1 single straight line. Also, when I click on one of the Cells in the Header, there is no arrow showing for the sorting and also no highlight that it is that column that's sorted.
Does somebody has a template I could edit myself to achieve what I want? Or what are the parts I have to edit?
By default the DataGridColumnHeadersPresenter draws an additional column Header of the full width in the Background of the DataGrid Header. By just leaving out the dummy Header you get what you want. Add that style to your styles:
<Style TargetType="{x:Type DataGridColumnHeadersPresenter}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridColumnHeadersPresenter}">
<Grid>
<ItemsPresenter />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

XAML styles - Nested tabcontrols triggering eachother's animations

I've invented a metro-style tab control which is built to contain two levels of tabs just like the Zune media library does; one large and one smaller below that. When you change tab for the first level, it works fine and animates correctly. Each of the two tabs contains another TabControl using the same template, but when you change the tabs there even the tabstrip animates; like the whole ContentPresenter of the container TabControl animates rather than the ContentPresenter of the child TabControl. If that makes sense :P
Here's the style:
<Style x:Key="MetroTabControl" TargetType="{x:Type TabControl}">
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="Background" Value="White" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabControl}">
<ControlTemplate.Resources>
<Storyboard x:Key="TabSelectionChangedStoryboard">
<DoubleAnimation Storyboard.TargetName="TabControlContent"
Storyboard.TargetProperty="Opacity"
To="100"
From="0"
FillBehavior="HoldEnd"
Duration="0:0:45.0" />
<ThicknessAnimation Storyboard.TargetName="TabControlContent"
Storyboard.TargetProperty="Margin"
From="0,25,0,-25"
To="0,0,0,0"
FillBehavior="HoldEnd"
Duration="0:0:0.3">
</ThicknessAnimation>
</Storyboard>
</ControlTemplate.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Border>
<TabPanel
IsItemsHost="True">
</TabPanel>
</Border>
<Border x:Name="BorderPresenter" BorderThickness="0"
Grid.Row="1"
BorderBrush="White"
Background="White">
<ContentPresenter x:Name="TabControlContent" ContentSource="SelectedContent" Margin="0" >
</ContentPresenter>
</Border>
</Grid>
<ControlTemplate.Triggers>
<EventTrigger RoutedEvent="SelectionChanged">
<BeginStoryboard Storyboard="{StaticResource TabSelectionChangedStoryboard}" />
</EventTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
That happens because SelectionChanged event bubbles up to parent TabControl and triggers its animation. As one possible easy solution you can add SelectionChanged event handler to parent TabControl and check if it is the original source for this event:
<TabControl SelectionChanged="RootTabControl_SelectionChanged">
<TabItem>
<TabControl>
<!-- TabItems here -->
</TabControl>
</TabItem>
</TabControl>
And here`s the code:
private void RootTabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (sender != e.OriginalSource)
e.Handled = true;
}

Wpf: Storyboard.TargetName works, but Setter TargetName doesn't

Let's say we have a XAML code like this:
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Border HorizontalAlignment="Center" VerticalAlignment="Center">
<Border.LayoutTransform>
<!--We are rotating randomly each image. Selected one will be rotated to 45°.-->
<RotateTransform Angle="{Binding RandomAngle}" x:Name="globalRotation"/>
</Border.LayoutTransform>
<Grid>
<Image Source="{Binding ImageLocation}" Stretch="None" />
<TextBlock x:Name="title" Text="{Binding Title}" />
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="title" Property="Visibility" Value="Visible"/>
<!--The next line will not compile.-->
<Setter TargetName="globalRotation" Property="Angle" Value="45"/>
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<!--This compiles well.-->
<DoubleAnimation Storyboard.TargetName="globalRotation" Storyboard.TargetProperty="Angle" To="45" Duration="00:00:03"/>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
This code is intended to display a set of images in a listbox. Each image has a random rotation, but when selected, rotates to 45 degrees.
Rotating selected image through a storyboard works well. I just specify Storyboard.TargetName and it rotates the image when selected (Trigger.ExitActions is omitted to make the code shorter).
Now, if I want, instead of using a storyboard, assign 45 degrees value directly, I can't do that, because <Setter TargetName="globalRotation" Property="Angle" Value="45"/>: it compiles with
"Cannot find the Trigger target 'globalRotation'. (The target must appear before any Setters, Triggers, or Conditions that use it.)"
error. What happens? I suppose that Storyboard.TargetName is evaluated during runtime, so let me compile it. Is it right?
How to make it work with just a setter, without using a storyboard?
The thing is that Trigger Setter can target only FrameworkElement or FrameworkContentElement objects, whereas Storyboard works with any DependencyProperty.
Here is a workaround:
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Border x:Name="brd" HorizontalAlignment="Center" VerticalAlignment="Center" Tag="{Binding RandomAngle}">
<Border.LayoutTransform>
<!--We are rotating randomly each image. Selected one will be rotated to 45°.-->
<RotateTransform Angle="{Binding ElementName=brd, Path=Tag}" x:Name="globalRotation"/>
</Border.LayoutTransform>
<Grid>
<Image Source="{Binding ImageLocation}" Stretch="None" />
<TextBlock x:Name="title" Text="{Binding Title}" />
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="title" Property="Visibility" Value="Visible"/>
<!--The next line will not compile.-->
<Setter TargetName="brd" Property="Tag" Value="45"/>
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<!--This compiles well.-->
<DoubleAnimation Storyboard.TargetName="globalRotation" Storyboard.TargetProperty="Angle" To="45" Duration="00:00:03"/>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

Resources