I have a Tabcontrol that I'm trying to add actions based on triggers.
Using Routed Events I can get my storyboard to trigger and the my actions respond as intended. Now I'm trying to prevent my storyboard actions from triggering for a specific tab header if that header is selected.
This has forced me to move away from routed events since I can't use a property condition and a routed event to determine if my storyboard will execute.
Fast forward and now I have finally been able to get IsMouseOver to respond as I wish to the mouse. (My background properties changes and changes back as the mouse enters and leaves the tabitem header. I'm almost there I think but as soon as I add the exact same storyboard as before my code decides to be lazy and skip it for some reason. The background setter is still being triggered but the storyboard remains silent.
I tried removing the setter but the storyboard still does not trigger.
End of the Day: I looking to set styles that transition nicely between each other for all possible combinations of Is Mouse Over and Is Selected.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Tenant_Tool_Analytics_Module.Resources.Components"
xmlns:controls="clr-namespace:Tenant_Tool_Analytics_Module.Resources.Components">
<Style TargetType="{x:Type controls:NavigationElement}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:NavigationElement}">
<Border BorderBrush="Black" BorderThickness=".7" x:Name="Bd">
<Grid Width="150" Height="50" Opacity="0.75" x:Name="NavigationElementGrid">
<Grid.Background >
<RadialGradientBrush GradientOrigin="0.5,0.5" Center="0.5,0.5" RadiusX="0.5" RadiusY="2.0">
<GradientStop Color="#006A4D" Offset="1.0"/>
<GradientStop Color="#56be88" Offset=".2"/>
</RadialGradientBrush >
</Grid.Background>
<ContentControl Grid.Column="0" Content="{TemplateBinding Icon}"/>
<TextBlock x:Name="NavigationElementText" Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Left" Text="{TemplateBinding LabelText}" FontFamily="Futura XBlkIt BT" FontSize="12"/>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="40"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="50" />
</Grid.RowDefinitions>
</Grid>
</Border>
<!--
Trigger related to when the mouse is over the header
I would like it to Execute the doubleanimations
-->
<ControlTemplate.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="True"/>
<!-- Missing condition: If selected = false -->
</MultiTrigger.Conditions>
<!-- Start BUG the bellow code does not execute -->
<MultiTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="NavigationElementGrid" Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:0.3"/>
<DoubleAnimation Storyboard.TargetName="NavigationElementText" Storyboard.TargetProperty="FontSize" To="14" Duration="0:0:0.3"/>
</Storyboard>
</BeginStoryboard>
</MultiTrigger.EnterActions>
<!-- End BUG -->
<!-- The triggers are fireing becuase this is being set. -->
<Setter Property="Background" TargetName="Bd" Value="Blue"/>
</MultiTrigger>
<!--
When the mouse leaves I want it to return to it's original
state.
-->
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="False"/>
<!-- Missing condition: If selected = false -->
</MultiTrigger.Conditions>
<!-- Start BUG the bellow code does not execute -->
<MultiTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="NavigationElementGrid" Storyboard.TargetProperty="Opacity" To=".75" Duration="0:0:0.3"/>
<DoubleAnimation Storyboard.TargetName="NavigationElementText" Storyboard.TargetProperty="FontSize" To="12" Duration="0:0:0.3"/>
</Storyboard>
</BeginStoryboard>
</MultiTrigger.EnterActions>
<!-- End BUG -->
<!-- The triggers are fireing becuase this is being set. -->
<Setter Property="Background" TargetName="Bd" Value="Red"/>
</MultiTrigger>
<!-- Missing two more MultiTriggers (very similar to above) for the cases of if the tab is selected.-->
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Code behind
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace Tenant_Tool_Analytics_Module.Resources.Components
{
public class NavigationElement : Control
{
public string LabelText
{
get
{
return (string)GetValue(LabelTextProperty);
}
set
{
SetValue(LabelTextProperty, value);
}
}
public static readonly DependencyProperty LabelTextProperty =
DependencyProperty.Register("LabelText", typeof(string), typeof(NavigationElement), new PropertyMetadata(string.Empty));
public object Icon
{
get
{
return (object)GetValue(IconProperty);
}
set
{
SetValue(IconProperty, value);
}
}
public static readonly DependencyProperty IconProperty =
DependencyProperty.Register("Icon", typeof(object), typeof(NavigationElement), new PropertyMetadata(string.Empty));
public System.Windows.Media.Brush BackgroundColour
{
get
{
return (System.Windows.Media.Brush)GetValue(BackgroundColourProperty);
}
set
{
SetValue(BackgroundColourProperty, value);
}
}
public static readonly DependencyProperty BackgroundColourProperty =
DependencyProperty.Register("BackgroundColour", typeof(System.Windows.Media.Brush), typeof(NavigationElement), new PropertyMetadata(Brushes.Black));
}
}
Implementation code
<Window x:Class="Tenant_Tool_Analytics_Module.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Tenant_Tool_Analytics_Module"
xmlns:views="clr-namespace:Tenant_Tool_Analytics_Module.Views"
xmlns:controls="clr-namespace:Tenant_Tool_Analytics_Module.Resources.Components"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<DockPanel LastChildFill="True">
<views:HeaderView x:Name="HeaderView" DockPanel.Dock="Top"/>
<TabControl DockPanel.Dock="Left" TabStripPlacement="Left" Margin="0, 0, 0, 10">
<TabItem Padding="0">
<TabItem.Header>
<controls:NavigationElement LabelText="Stacking Plan" Icon="{StaticResource StackPlanIcon}"/>
</TabItem.Header>
<Button Content="HI"/>
</TabItem>
<TabItem Padding="0">
<TabItem.Header>
<controls:NavigationElement LabelText="Tenant Profile" Icon="{StaticResource TenantProfileIcon}"/>
</TabItem.Header>
<Button Content="HI"/>
</TabItem>
<TabItem Padding="0">
<TabItem.Header>
<controls:NavigationElement LabelText="Submarket" Icon="{StaticResource SubmarketIcon}"/>
</TabItem.Header>
<Button Content="HI"/>
</TabItem>
<TabItem Padding="0">
<TabItem.Header>
<controls:NavigationElement LabelText="Industry" Icon="{StaticResource IndustryIcon}"/>
</TabItem.Header>
<Button Content="HI"/>
</TabItem>
</TabControl>
</DockPanel>
</Window>
I think you weren't aware of the fact that MultiDataTrigger objects have both EnterActions and ExitActions. Instead of having one MultiDataTrigger triggering on true with only EnterActions and another MultiDataTrigger triggering on false with only EnterActions, you can use only one MultiDataTrigger triggering on true with both EnterActions (to change the object to an abnormal state) and ExitActions to transition it back to its normal state.
The Triggers collection now works as expected and becomes, as a bonus, easier to read:
<ControlTemplate.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="True"/>
<!-- Missing condition: If selected = false -->
</MultiTrigger.Conditions>
<MultiTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="NavigationElementGrid"
Storyboard.TargetProperty="Opacity"
To="1"
Duration="0:0:0.3"/>
<DoubleAnimation Storyboard.TargetName="NavigationElementText"
Storyboard.TargetProperty="FontSize"
To="14" Duration="0:0:0.3"/>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="BorderBackgroundBrush"
Storyboard.TargetProperty="Color">
<DiscreteColorKeyFrame KeyTime="0" Value="Blue"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</MultiTrigger.EnterActions>
<MultiTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="NavigationElementGrid"
Storyboard.TargetProperty="Opacity"
To=".75"
Duration="0:0:0.3"/>
<DoubleAnimation Storyboard.TargetName="NavigationElementText"
Storyboard.TargetProperty="FontSize"
To="12"
Duration="0:0:0.3"/>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="BorderBackgroundBrush"
Storyboard.TargetProperty="Color">
<DiscreteColorKeyFrame KeyTime="0" Value="Red"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</MultiTrigger.ExitActions>
</MultiTrigger>
<!-- Missing two more MultiTriggers (very similar to above) for the cases of if the tab is selected.-->
</ControlTemplate.Triggers>
Also notice how I've used a ColorAnimationUsingKeyFrames to change the Border.Background property without needing a Setter in another Trigger. This way, all changes are performed in the same Storyboard. For this to work, you just need to assign a named SolidColorBrush to your "Bd" Border:
<Border.Background>
<SolidColorBrush x:Name="BorderBackgroundBrush" Color="Red"></SolidColorBrush>
</Border.Background>
To prevent the Storyboard to play if the ancestor TabItem is selected, I suggest you add a boolean IsSelected DependencyProperty to your NavigationElement, so that you can bind this property to its TabItem ancestor by adding a Setter in your Style like this:
<Setter Property="IsSelected" Value="{Binding IsSelected, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=TabItem}}"/>
And you just have to add the condition in your MultiDataTrigger (but you already figured that out):
<Condition Property="IsMouseOver" Value="True"/>
<Condition Property="IsSelected" Value="False"/>
Sidenote: I recommend you wrap and indent your XAML attributes to avoid long XAML lines that force you to scroll. Besides the increased readability, having each XAML attribute on a new line is more version control-friendly because one attribute change only impacts one line.
Related
I've made a click event/method that alters the opacity and IsEnabled properties of a textbox.
private void EditButton(object sender, RoutedEventArgs e)
{
religionTB.IsEnabled = true;
DoubleAnimation fade = new
DoubleAnimation(1,TimeSpan.FromSeconds(0.2));
religionTB.BeginAnimation(OpacityProperty, fade);
}
In my WPF project, there are multiple textboxes, I'd like to apply this method to all these textboxes without having to list all of them in the method. How would I go by this?
You can achieve this by using a Style. To do so, go to the context of your event handler (Control or Window) and add a DependencyProperty, to flag the enabled mode and bind a ToggleButton (the edit button) to it that sets this property to enable/ disable the controls and to trigger a fade in and fade out animation:
In your control:
public static readonly DependencyProperty IsEditEnabledProperty = DependencyProperty.Register(
"IsEditEnabled",
typeof(bool),
typeof(MainWindow),
new PropertyMetadata(default(bool)));
public bool IsEditEnabled { get { return (bool) GetValue(MainWindow.IsEditEnabledProperty); } set { SetValue(MainWindow.IsEditEnabledProperty, value); } }
In your XAML add the TextBox style and link a ToggleButton to IsEditEnabled:
<Window.Resources>
<Style x:Key="OpacityStyle" TargetType="TextBox">
<Setter Property="Opacity" Value="0" />
<Setter Property="IsEnabled"
Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=Window}, Path=IsEditEnabled}" />
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=Window}, Path=IsEditEnabled}"
Value="True">
<! Fade in animation -->
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity"
BeginTime="0:0:0"
From="0"
To="1"
Duration="0:0:0.2" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<! Fade out animation -->
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity"
BeginTime="0:0:0"
From="1"
To="0"
Duration="0:0:0.2" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<StackPanel>
<ToggleButton x:Name="EditButton" IsChecked="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=Window}, Path=IsEditEnabled, Mode=TwoWay}" />
<TextBox x:Name="AnimatedTextBox" Style="{StaticResource TextBoxAnimationStyle}" >
<TextBox x:Name="AnotherAnimatedTextBox" Style="{StaticResource TextBoxAnimationStyle}" >
<TextBox x:Name="NonanimatedTextBox" >
</StackPanel>
</Grid>
If you make the Style implicit by removing the x:Key attribute, it will apply to all TextBox elements within the scope
I have a WPF application which contains a UserControl, whose border is animated:
<UserControl x:Class="CarSystem.CustomControls.AlarmItem"
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:cs="clr-namespace:CarSystem.CustomControls"
mc:Ignorable="d"
DataContext="{Binding Path=Alarm, RelativeSource={RelativeSource Self}}">
<UserControl.Resources>
<Style TargetType="{x:Type cs:AlarmItem}">
<Setter Property="IsFlashing" Value="False" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsExpired}" Value="True">
<Setter Property="IsFlashing" Value="True" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=IsPending}" Value="True">
<Setter Property="IsFlashing" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<Border HorizontalAlignment="Center"
Margin="5"
Height="100"
Name="Border"
VerticalAlignment="Center"
Width="100">
<Border.Resources>
<Storyboard x:Key="FlashingStoryboard"
AutoReverse="True"
RepeatBehavior="Forever">
<ColorAnimationUsingKeyFrames BeginTime="00:00:00"
Duration="00:00:00.5"
Storyboard.TargetName="Border"
Storyboard.TargetProperty="(Border.BorderBrush).(SolidColorBrush.Color)">
<DiscreteColorKeyFrame KeyTime="00:00:00.25" Value="Black" />
</ColorAnimationUsingKeyFrames>
</Storyboard>
</Border.Resources>
<Border.Style>
<Style TargetType="Border">
<Setter Property="BorderBrush" Value="Black" />
<Setter Property="BorderThickness" Value="2" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsExpired}" Value="True">
<Setter Property="BorderThickness" Value="4" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=IsPending}" Value="True">
<Setter Property="BorderThickness" Value="4" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="FlashStates">
<VisualState x:Name="FlashingOn"
Storyboard="{StaticResource ResourceKey=FlashingStoryboard}" />
<VisualState x:Name="FlashingOff" />
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Image Grid.Row="0"
Name="AlarmImage"
Source="{Binding Path=Image, RelativeSource={RelativeSource AncestorType={x:Type cs:AlarmItem}}}"
Stretch="Fill" />
<cs:ResponseTimer Expired="Timer_Expired"
Grid.Row="1"
HideIfExpired="True"
IsTabStop="False"
MinHeight="10"
x:Name="TheTimer"
TimeoutPeriod="00:02:30"
VerticalAlignment="Bottom" />
</Grid>
</Border>
</UserControl>
The application receives data from a proprietary device my company manufactures. The objects are received and loaded into a View Model class instance. A new instance of this control is created for each object received, a reference to the view model is put into the new control instance's DataContext property, and the new control is added to a ListBox in a Window.
The animation is supposed to run while the object's status property is a particular value. There's a second status value where the animation is also supposed to run, and the status changes to that value after a fixed time interval in which the user has not responded to that item. The animation is only supposed to stop when the status takes on a value that can only be set by user interaction.
When the first object is received & displayed, the animation works fine. If no further objects are received, the animation keeps running & the border color changes as intended when the timer expires.
However, the animation just stops, on its own, after 2 or more objects are received. This is both before the timer that changes the status that causes the border color to change expires and before any user action is taken. Note that it doesn't always stop on the second object, it sometimes takes receiving 3 or 4 objects before the animation stops.
Does anyone have any idea why the animation stops? How do I keep each one running to the end? Is there a better way to get the same effect that doesn't have this problem?
The closest scenario I've had to this is triggering an animation based upon a value in the view model. I'm taking the 'better way' option here so apologies if this doesn't fit with your solution, I'm just basing it on what I see.
Now you mention a new control is created for each alert that comes from your external device? So I'm going to assume this control is bound to a single instance and presented in something like a listbox.
If you're still with me, your control instance has it's own view model instance and by the looks of it, your triggering from an IsPending and IsExpired property such that when either is true, make it flash.
First thing I'd do is simplify that binding from the ViewModel adding as IsAlertRequired property which you set when you require the alert - can be updated in the setter for you existing properties. The reason here is that a single trigger is way easier than a multibinding.
Then use DataTrigger to start your storyboard. In your app add a reference to Microsoft.Expression.Interactions and then import them in your XAML:
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
Then add a trigger before your layoutroot element:
<i:Interaction.Triggers>
<ei:DataTrigger Binding="{Binding ViewModel.IsAlertRequired}" Value="True">
<ei:ControlStoryboardAction ControlStoryboardOption="Play" Storyboard="{StaticResource FlashingAnimation}"/>
</ei:DataTrigger>
That should start your animation based upon a view model data binding.
Assuming you get that far and it works for starting your animation, if it still stops then I would look into whether something within your viewmodel or external component is blocking the UI thread.
Hope this helps.
Thanks to information from kidshaw, I was able to figure out a solution that works. Essentially, the animation is supposed to switch the Border control's BorderBrush back and forth between two colors, black and a color that depends upon the properties of the object in the DataContext. This logic is in the code behind because the logic depends on two different properties of the data context object and I couldn't come up with a XAML trigger that would work properly. It wasn't included in the original question because it didn't seem pertinent.
As mentioned in the comments to kidshaw's answer, the problem was that the animation was losing track of what color the BorderBrush was supposed to be when additional alerts came into the window. Instead of switching back & forth between Red & Black, for example, it would think it had to switch between black & black or red & red. So it looked like no animation was taking place, when it actually was.
To fix the problem, and because of the need to choose the color based on properties of the object in the data context, I ended up adding a second DiscreteColorKeyFrame to the animation for the other color. I tried to use a bind the Value property of the DiscreteColorKeyFrame to a DependencyProperty I added to the control which would be set by the code behind logic, but that didn't work. The animation kept switching back & forth between black & transparent, while the Output window in VS kept recording errors about CanFreeze being false.
So I ended up making one animation for each different color and adding one VisualState to the VisualStateManager for each color. There were changes in the code behind, too, bu tI got it all working.
Here's the XAML:
<UserControl x:Class="CarSystem.CustomControls.AlarmItem"
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:cs="clr-namespace:CarSystem.CustomControls"
mc:Ignorable="d"
DataContext="{Binding Path=Alarm, RelativeSource={RelativeSource Self}}">
<UserControl.Resources>
<Style TargetType="{x:Type cs:AlarmItem}">
<Setter Property="IsFlashing" Value="False" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsExpired}" Value="True">
<Setter Property="IsFlashing" Value="True" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=IsPending}" Value="True">
<Setter Property="IsFlashing" Value="True" />
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Resources>
<Border HorizontalAlignment="Center"
Margin="5"
Height="100"
Name="Border"
VerticalAlignment="Center"
Width="100">
<Border.BorderBrush>
<SolidColorBrush x:Name="AnimatedBrush" Color="Black" />
</Border.BorderBrush>
<Border.Resources>
<Storyboard x:Key="ExpiredAnimation"
AutoReverse="False"
RepeatBehavior="Forever">
<ColorAnimationUsingKeyFrames BeginTime="00:00:00"
Duration="00:00:01"
Storyboard.TargetName="AnimatedBrush"
Storyboard.TargetProperty="Color">
<DiscreteColorKeyFrame KeyTime="00:00:00" Value="#FFFFFF78" />
<DiscreteColorKeyFrame KeyTime="00:00:00.5" Value="Black" />
</ColorAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="HistoricalAnimation"
AutoReverse="False"
RepeatBehavior="Forever">
<ColorAnimationUsingKeyFrames BeginTime="00:00:00"
Duration="00:00:01"
Storyboard.TargetName="AnimatedBrush"
Storyboard.TargetProperty="Color">
<DiscreteColorKeyFrame KeyTime="00:00:00" Value="#FFFFFF78" />
<DiscreteColorKeyFrame KeyTime="00:00:00.5" Value="Black" />
</ColorAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="PendingAnimation"
AutoReverse="False"
RepeatBehavior="Forever">
<ColorAnimationUsingKeyFrames BeginTime="00:00:00"
Duration="00:00:01"
Storyboard.TargetName="AnimatedBrush"
Storyboard.TargetProperty="Color">
<DiscreteColorKeyFrame KeyTime="00:00:00" Value="Red" />
<DiscreteColorKeyFrame KeyTime="00:00:00.5" Value="Black" />
</ColorAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="WhiteListAnimation"
AutoReverse="False"
RepeatBehavior="Forever">
<ColorAnimationUsingKeyFrames BeginTime="00:00:00"
Duration="00:00:01"
Storyboard.TargetName="AnimatedBrush"
Storyboard.TargetProperty="Color">
<DiscreteColorKeyFrame KeyTime="00:00:00" Value="#FF5819" />
<DiscreteColorKeyFrame KeyTime="00:00:00.5" Value="Black" />
</ColorAnimationUsingKeyFrames>
</Storyboard>
</Border.Resources>
<Border.Style>
<Style TargetType="Border">
<Setter Property="BorderThickness" Value="2" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsExpired}" Value="True">
<Setter Property="BorderThickness" Value="4" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=IsPending}" Value="True">
<Setter Property="BorderThickness" Value="4" />
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="FlashStates">
<VisualState x:Name="ExpiredState" Storyboard="{StaticResource ResourceKey=ExpiredAnimation}" />
<VisualState x:Name="HistoricalState" Storyboard="{StaticResource ResourceKey=HistoricalAnimation}" />
<VisualState x:Name="PendingState" Storyboard="{StaticResource ResourceKey=PendingAnimation}" />
<VisualState x:Name="WhiteListState" Storyboard="{StaticResource ResourceKey=WhiteListAnimation}" />
<VisualState x:Name="FlashingOff" />
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Image Grid.Row="0"
Name="AlarmImage"
Source="{Binding Path=Image, RelativeSource={RelativeSource AncestorType={x:Type cs:AlarmItem}}}"
Stretch="Fill" />
<cs:ResponseTimer Expired="Timer_Expired"
Grid.Row="1"
HideIfExpired="True"
IsTabStop="False"
MinHeight="10"
x:Name="TheTimer"
TimeoutPeriod="00:02:30"
VerticalAlignment="Bottom" />
</Grid>
</Border>
</UserControl>
And here's the code from the backend that picks & starts the correct animation.
private void StartStatusAnimation() {
if ( condition1 ) {
// It is. Display the WhiteListAnimation.
if ( !VisualStateManager.GoToElementState( Border, "WhiteListState", true ) ) {
// Log error
}
} else if ( condition2 ) {
if ( !VisualStateManager.GoToElementState( Border, "ExpiredState", true ) ) {
// Log error
}
} else if ( condition3 ) {
if ( !VisualStateManager.GoToElementState( Border, "HistoricalState", true ) ) {
// Log error
}
} else if ( condition4 ) {
if ( !VisualStateManager.GoToElementState( Border, "PendingState", true ) ) {
// Log error
}
} else {
// We don't know what state this is. Stop flashing now
if ( !VisualStateManager.GoToElementState( Border, "FlashingOff", true ) ) {
// Log error
}
}
}
Note that the values of the data context objects properties can change due to user interaction, or a timer expiring, which would either stop the flashing all together, in the first case, or cause the current animation to stop and another one to start, in the second. In the second case, the VisualSTateManager is just set to the FlashingOff state when the trigger in the Style sets the control's IsFlashing to false, and in the second, the above method is called again when the timer expires.
I'm looking for the way to code-behind launch an animation on a control contained in an ControlTemplate.
In my app I have custom templated buttons (playing menu role) created from an ObservableCollection :
MainMenuViewModel :
/// <summary>
/// Menu items list
/// </summary>
private ObservableCollection<MenuItem> _items;
....
/// <summary>
/// Menu items list property
/// </summary>
public ObservableCollection<MenuItem> Items
{
get { return _items; }
set { _items = value; }
}
MainMenuView :
<UserControl x:Class="OfficeTourismeBrantome.Views.MainMenuView"
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="800" d:DesignWidth="300">
<UserControl.Resources>
<Style x:Key="MenuItemButtonStyle" TargetType="Button">
<Setter Property="FontSize" Value="60" />
<Setter Property="FontFamily" Value="Segoe" />
<Setter Property="FontWeight" Value="UltraLight" />
<Setter Property="Foreground" Value="#FFEBEDEA" />
<!--<Setter Property="Height" Value="{Binding MenuLayout.MenuItemSize.Height}" />-->
<Setter Property="HorizontalContentAlignment" Value="Right" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<ControlTemplate.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard Name="themeSelectionAnimation">
<DoubleAnimation
Storyboard.TargetName="coloredRectangle"
Storyboard.TargetProperty="Width"
From="30.0"
To="250.0"
Duration="0:0:0.3" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</ControlTemplate.Triggers>
<Canvas HorizontalAlignment="Stretch" ClipToBounds="False" >
<ContentPresenter Canvas.Left="{Binding MenuLayout.MenuItemLeftMargin}" HorizontalAlignment="Center"
VerticalAlignment="Center" Canvas.ZIndex="1"/>
<TextBlock
Text="{Binding SecondaryText}"
Canvas.Top="50"
Canvas.Left="10"
FontSize="30"
FontWeight="ExtraLight"
FontStyle="Italic"
Canvas.ZIndex="1"
/>
<Rectangle
Canvas.Top="30"
Canvas.Left="10"
Name="coloredRectangle"
Width="30"
Height="10"
Canvas.ZIndex="0"
Fill="{Binding Color}"/>
</Canvas>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Storyboard x:Key="themeUnselectionAnimation">
<DoubleAnimation
Storyboard.TargetProperty="Width"
From="250.0"
To="30.0"
Duration="0:0:0.15" />
</Storyboard>
</UserControl.Resources>
<ItemsControl Name="menuButtonContainer" ItemsSource="{Binding Items}" Margin="{Binding MenuLayout.MenuMargin}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button
Style="{StaticResource ResourceKey=MenuItemButtonStyle}"
Margin="{Binding ElementName=menuButtonContainer,
Path=DataContext.MenuLayout.MenuItemMargin}"
Height="{Binding ElementName=menuButtonContainer,
Path=DataContext.MenuLayout.MenuItemSize.Height}"
Content="{Binding Text}"
Command="{Binding ElementName=menuButtonContainer,
Path=DataContext.ChangeThemeCommand}"
CommandParameter="{Binding Id}"
/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</UserControl>
As you can see in the code above, I got an animation automatically triggered on button click. I want to play it reverse when another button in the collection is clicked (another menu entry is selected). The animation to play is the one called themeUnselectionAnimation.
First question : is there a way to do it only in XAML? I'm not sure as another button need to be pressed to trigger it.
Below is what I thought :
In the Button (which is my menu item) command action, send a message to let subscribers that menu item is changing.
Register to it in MainMenuView code-behind.
Launch animation from there.
My problem so far is to set the target control for the animation. To do that, I need to find the Rectangle named coloredRectangle in ControlTemplate. How to do that ?
Here below is my code corresponding to above steps :
Step 1 : send message (I'm using MVVM Light framework)
/// <summary>
/// Delegates that handles theme change process and tasks
/// </summary>
/// <param name="themeId">the new active theme</param>
private void ChangeTheme(int themeId)
{
// Set current active theme as inactive, if one is selected.
// Exception use because of Single implementation that throw an InvalidOperationException if not item is found
try
{
MenuItem currentTheme = Items.Single(x => x.IsActive == true);
// Check if this is current theme. If it is, we do nothing.
if(currentTheme.Id == themeId)
return;
// If current theme is set and new theme id is not the same, disable the old one
currentTheme.IsActive = false;
// Set new theme as active
Items.Single(x => x.Id == themeId).IsActive = true;
// Finally, launch unselection animation
// Send message and register to it in view code behind
// Create inner message
ThemeChangeNotification innerMessage = new ThemeChangeNotification();
innerMessage.NewThemeId = themeId;
innerMessage.OldThemeId = currentTheme.Id;
NotificationMessage<ThemeChangeNotification> message =
new NotificationMessage<ThemeChangeNotification>(innerMessage, "");
// Send message
Messenger.Default.Send(message);
}
catch (InvalidOperationException exception)
{
// Set first theme selection as active
Items.Single(x => x.Id == themeId).IsActive = true;
}
}
Step 2 : Register to message
Messenger.Default.Register<NotificationMessage<ThemeChangeNotification>>(this, ChangeThemeAnimation);
Step 3 : reach Button from index/id and launch animation (not working)
/// <summary>
/// Theme change message delegate
/// </summary>
/// <param name="e">The ThemeChangeNotification message</param>
private void ChangeThemeAnimation(NotificationMessage<ThemeChangeNotification> message)
{
var buttonTheme = menuButtonContainer.ItemContainerGenerator.ContainerFromIndex(message.Content.OldThemeId) as FrameworkElement;
var rectangle = buttonTheme.FindName("coloredRectangle") as Rectangle;
Storyboard sb = this.FindResource("themeUnselectionAnimation") as Storyboard;
Storyboard.SetTarget(sb, rectangle);
sb.Begin();
}
Thank you very much for your answers !
Surely, you can just create another Style based on the first one that uses the other Storyboard instead... then you could just apply the reverse Style on whichever Button(s) that you want to start that Storyboard:
<Style x:Key="ReverseMenuItemButtonStyle" TargetType="Button">
<Setter Property="FontSize" Value="60" />
<Setter Property="FontFamily" Value="Segoe" />
<Setter Property="FontWeight" Value="UltraLight" />
<Setter Property="Foreground" Value="#FFEBEDEA" />
<Setter Property="HorizontalContentAlignment" Value="Right" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<ControlTemplate.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard Name="themeUnselectionAnimation">
<DoubleAnimation
Storyboard.TargetName="coloredRectangle"
Storyboard.TargetProperty="Width"
From="30.0"
To="250.0"
Duration="0:0:0.3" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</ControlTemplate.Triggers>
...
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I've got to be fully honest... I didn't totally understand your question, so if that didn't answer it, it seems as though you also want to know how to start a Storyboard from a view model. In this case, you just need a bool property which will start the animation when set to true in the view model. You can do that using the DataTrigger.EnterActions:
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding SomeBooleanPropertyInViewModel}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard ... />
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
UPDATE >>>
Again... I still don't really know what you're after... I'd suggest working on your question asking skills before posting another one. However, this much I could work out:
You're getting an TargetName cannot be used on Style Setter error and you want to target your coloredRectangle element.
The usual fix for this error is simply to move your Trigger to the element that you are trying to target. So try this instead:
<Rectangle Canvas.Top="30" Canvas.Left="10" Name="coloredRectangle" ... >
<Rectangle.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding SomeBooleanProperty}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard ... />
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Rectangle.Style>
</Rectangle>
So I am trying to create a simple animation that takes the background from the initial color to the new color and back.
The original issue I had is that I created a trigger on the MouseDownEvent that triggered the animation, but the user could trigger another animation before the first one finished. This new animation would animate from the current shade it was at to the new color and back. By progressively restarting the animation while the animation is going, the original color is lost.
The easiest way to solve this is probably if i use the completed event for the animation. However, I don't like this solution because I want my animations to be in a custom style in resource dictionary and not part of the control itself. If the animation is in a custom style in a resource dictionary, then it won't have access to the code behind of the control itself. Is there a good way to get an animation working while maintaining separation between the style and the control?
I then had a different idea. The error is caused because I was animating from the border.background.color to a new color and back, thus if i started a new animation while the old one was going, the new animation started from whatever color value the prior animation was in. But if I set the animation to go back to some saved property value of the original background color then I won't have the issue even if the user restarts the animation.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Components="clr-namespace:DaedalusGraphViewer.Components"
xmlns:Converters="clr-namespace:DaedalusGraphViewer.Components.Converters"
>
<Converters:ThicknessToLeftThicknessConverter x:Key="ThicknessToLeftThicknessConverter" />
<Style x:Key="SearchBoxListViewItemStyle" TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Left"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="LightBlue" />
</Trigger>
</Style.Triggers>
</Style>
<Style x:Key="{x:Type Components:SearchBox}" TargetType="{x:Type Components:SearchBox}">
<Style.Resources>
</Style.Resources>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Components:SearchBox}">
<Border x:Name="Border"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid x:Name="LayoutGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ScrollViewer
x:Name="PART_ContentHost"
Grid.Column="0"
VerticalAlignment="Center"
/>
<Label
x:Name="DefaultTextLabel"
Grid.Column="0"
Foreground="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TextColor}"
Content="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=LabelText}"
VerticalAlignment="Center"
FontStyle="Italic"
/>
<Popup x:Name="RecentSearchesPopup"
IsOpen="False"
>
<ListView
x:Name="PreviousSearchesListView"
ListView.ItemContainerStyle="{StaticResource SearchBoxListViewItemStyle}"
>
</ListView>
</Popup>
<Border
x:Name="PreviousSearchesBorder"
Grid.Column="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="LightGray"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=BorderThickness,
Converter={StaticResource ThicknessToLeftThicknessConverter}}"
>
<Image
x:Name="PreviousSearchesIcon"
ToolTip="Previous Searches"
Width="15"
Height="15"
Stretch="Uniform"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Source="pack://application:,,,/DaedalusGraphViewer;component/Images/Previous.png"
/>
</Border>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="HasText" Value="True">
<Setter Property="Visibility" TargetName="DefaultTextLabel" Value="Hidden" />
</Trigger>
<Trigger
SourceName="DefaultTextLabel"
Property="IsMouseOver"
Value="True"
>
<Setter Property="Cursor" Value="IBeam" />
</Trigger>
<!--<EventTrigger RoutedEvent="Mouse.MouseDown" SourceName="PreviousSearchesBorder">
<BeginStoryboard>
<Storyboard>
<ColorAnimation
AutoReverse="True"
Duration="0:0:0.2"
Storyboard.TargetName="PreviousSearchesBorder"
Storyboard.TargetProperty="(Border.Background).Color"
To="Black"
/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>-->
<Trigger Property="IsPopupOpening" Value="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimationUsingKeyFrames
Storyboard.TargetName="PreviousSearchesBorder"
Storyboard.TargetProperty="(Border.Background).Color"
>
<LinearColorKeyFrame KeyTime="0:0:0.0" Value="{x:Static Components:SearchBox.DefaultRecentSearchesButtonColor}" />
<LinearColorKeyFrame KeyTime="0:0:0.2" Value="Black" />
<LinearColorKeyFrame KeyTime="0:0:0.4" Value="{x:Static Components:SearchBox.DefaultRecentSearchesButtonColor}" />
</ColorAnimationUsingKeyFrames>
<!--<ColorAnimation
AutoReverse="True"
Duration="0:0:0.2"
Storyboard.TargetName="PreviousSearchesBorder"
Storyboard.TargetProperty="(Border.Background).Color"
To="Black"
/>-->
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
</Trigger>
However, in order to do this I need to store the original background property and I have yet to get that working. I cannot using binding because properties in Animations must be freezeable, so I tried creating a static property on the control that gets set to the original value on the control's loaded event.
I set the color to the background color in the code behind, but the style does not reflect that property.
Is my static reference in the xaml correct? if so, then isn't onapplytemplate when the style should load the color from the static reference?
Well, you can't use a DynamicResource without getting a Freeze error.
What if you loaded the Xaml file at runtime and added the StaticResource to the Xaml file?
Here is an example. (I use some quick and dirty parsing of the Xaml file, but it works for the proof of concept.)
Change the properties of the StoryBoardResourceDictionary.xaml to Content and Copy if newer and remove the MSBuild:Compile setting.
StoryBoardResourceDictionary.xaml
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style x:Key="WindowWithTrigger" TargetType="Window">
<Style.Triggers>
<EventTrigger RoutedEvent="Mouse.MouseDown">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)" >
<EasingColorKeyFrame KeyTime="0:0:2" Value="White"/>
<!-- OriginalBackground is added at runtime -->
<EasingColorKeyFrame KeyTime="0:0:4" Value="{StaticResource OriginalBackground}"/> <!-- Load at runtime -->
</ColorAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
MainWindow.xaml
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="BackgroundAnimationBlend.MainWindow"
x:Name="Window"
Title="MainWindow"
Width="640" Height="480"
Style="{DynamicResource WindowWithTrigger}"
Background="DarkBlue"> <!--Change background to whatever color you want -->
</Window>
MainWindow.xaml.cs
using System.IO;
using System.Text;
using System.Windows;
using System.Windows.Markup;
namespace BackgroundAnimationBlend
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var rd2 = LoadFromFile();
this.Resources.MergedDictionaries.Add(rd2);
}
public ResourceDictionary LoadFromFile()
{
const string file = "Styles/StoryBoardResourceDictionary.xaml";
if (!File.Exists(file))
return null;
using (var fs = new StreamReader(file))
{
string xaml = string.Empty;
string line;
bool replaced = false;
while ((line = fs.ReadLine()) != null)
{
if (!replaced)
{
if (line.Contains("OriginalBackground"))
{
xaml += string.Format("<Color x:Key=\"OriginalBackground\">{0}</Color>", Background);
replaced = true;
continue;
}
}
xaml += line;
}
// Read in an EnhancedResourceDictionary File or preferably an GlobalizationResourceDictionary file
return XamlReader.Load(new MemoryStream(Encoding.UTF8.GetBytes(xaml))) as ResourceDictionary;
}
}
}
}
I have no idea how to scale this right now. So maybe it is a crazy idea. But loading the style at runtime and injecting the current background as a xaml string into the style before loading it is the only idea I had that worked.
I have made a small example to illustrate how you can solve your problem (by what i have understood).
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Color x:Key="background">yellow</Color>
<SolidColorBrush x:Key="backgroundBrush" Color="{StaticResource background}"/>
<Storyboard x:Key="Storyboard1">
<ColorAnimationUsingKeyFrames Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)" Storyboard.TargetName="grid">
<SplineColorKeyFrame KeyTime="0:0:0.3" Value="Black"/>
<SplineColorKeyFrame KeyTime="0:0:0.6" Value="{StaticResource background}"/>
</ColorAnimationUsingKeyFrames>
</Storyboard>
</Window.Resources>
<Grid x:Name="grid" Background="{StaticResource backgroundBrush}">
<Button x:Name="button" Content="Button" HorizontalAlignment="Left" VerticalAlignment="Top" Width="75" RenderTransformOrigin="0.5,0.5">
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard Storyboard="{StaticResource Storyboard1}" Name="Storyboard1"/>
</EventTrigger>
</Button.Triggers>
</Button>
</Grid>
I have defined a Color background as StaticResource to use it as Background-Color for the Grid and as Value for the Animation.
In the example above the Grid has a yellow background, that gets animated to black and then back to yellow when the button is clicked.
So all I ended up doing was using FillBehavior = Stop. I was new to animations so I didn't know how to prevent the reactivation of the animation easily without access to the completed event (which would have to be attached in the control, thus defeating the purpose of having a style with animations in a separate resource file). Regardless, I'm giving the bounty to rhyous because his answer actually does solve my problem as well, though in a more difficult manner. I knew that what i wanted to do had to be simple because there was no way that no one had wanted to animate a color and back from a style, yet I couldn't find any Stack Overflow questions on the issue
I'm using the following Class and DependencyProperty to allow a style to set an image for a Button:
public static class ImageButton
{
public static readonly DependencyProperty ImageProperty =
DependencyProperty.RegisterAttached("Image", typeof(ImageSource), typeof(ImageButton),
new FrameworkPropertyMetadata((ImageSource)null));
public static ImageSource GetImage(DependencyObject obj)
{
return (ImageSource)obj.GetValue(ImageProperty);
}
public static void SetImage(DependencyObject obj, ImageSource value)
{
obj.SetValue(ImageProperty, value);
}
}
I've defined the following Style (in ImageButton.xaml):
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vcontrols="clr-namespace:Vialis.Led.LedControl5.Controls">
<ControlTemplate x:Key="ImageButtonTemplate" TargetType="Button">
<Image Source="{Binding Path=(vcontrols:ImageButton.Image),
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Button}}}"
Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}"
Stretch="Fill"
RenderTransformOrigin="0.5, 0.5">
<Image.Resources>
<Storyboard x:Key="ShrinkStoryboard">
<DoubleAnimation Storyboard.TargetName="ImageScale"
Storyboard.TargetProperty="(ScaleTransform.ScaleX)"
To="0.8"
Duration="0:0:0.15"
AutoReverse="False"/>
<DoubleAnimation Storyboard.TargetName="ImageScale"
Storyboard.TargetProperty="(ScaleTransform.ScaleY)"
To="0.8"
Duration="0:0:0.15"
AutoReverse="False"/>
</Storyboard>
<Storyboard x:Key="GrowStoryboard">
<DoubleAnimation Storyboard.TargetName="ImageScale"
Storyboard.TargetProperty="(ScaleTransform.ScaleX)"
To="1.0"
Duration="0:0:0.15"
AutoReverse="False"/>
<DoubleAnimation Storyboard.TargetName="ImageScale"
Storyboard.TargetProperty="(ScaleTransform.ScaleY)"
To="1.0"
Duration="0:0:0.15"
AutoReverse="False"/>
</Storyboard>
</Image.Resources>
<Image.RenderTransform>
<ScaleTransform x:Name="ImageScale" ScaleX="1" ScaleY="1" CenterX="1" CenterY="1"/>
</Image.RenderTransform>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Pressed" Storyboard="{StaticResource ShrinkStoryboard}"/>
<VisualState x:Name="MouseOver" Storyboard="{StaticResource GrowStoryboard}"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Image>
</ControlTemplate>
<Style x:Key="ImageButtonStyle" TargetType="Button">
<Setter Property="Opacity" Value="0.5"/>
<Setter Property="Template" Value="{StaticResource ImageButtonTemplate}"/>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Opacity" Value="1"/>
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
And finally in order to use it I have something like this:
<Button Width="32"
Height="32"
Style="{StaticResource ImageButtonStyle}"
vcontrols:ImageButton.Image="/Images/someimage.png"/>
When I compile and execute the application everything works just fine.
The button gets an image and uses the animations defined in the Style.
At design time however, Visual Studio cannot seem to visualize it and
the XAML editor shows squiggly lines under the entire button definition.
The error information says:
Prefix 'vcontrols' does not map to a namespace.
It's refering to the use of vcontrols in the Style.
If you change the name there, the error will change as well,
so it's not related to the name chosen in the Window/UserControl that is using the Button.
What might be causing this and is there a way to fix it so it works at design time as well?
Update 2:
This issue was only with VS2012 designer (in VS2010 it works fine) and it is already fixed in Visual Studio 2012 Update 3 patch.