Animate DependencyProperty on a UserControl displayed through a ContentPresenter - wpf

In WPF, I have a ListBox with the list made up of UserControls. The controls are meant to navigate to different screens in the application. Each UserControl (called NavigationButton) has an icon and text. The icons are mostly combinations of multiple Path objects, so each icon is it's own UserControl, and they are being displayed using a ContentPresenter. I want to be able to animate the color of the icon depending on different states of the screen, but have tried a lot of options and have been unable to do this.
Here is a stripped down version of NavigationButton:
<DockPanel Margin="12,0,12,0">
<!-- Icon -->
<ContentPresenter x:Name="Content_Icon" Content="{Binding}" Width="20"/>
<!-- Text -->
<Grid Margin="9,0,0,0">
<TextBlock x:Name="TextBlock_Text" Text="{Binding ScreenName, Converter={StaticResource StringToStringUpperConverter}}" VerticalAlignment="Center"
FontSize="15" Foreground="#FFF2F2F2" />
</Grid>
Basically, I need to animate a property on the ContentPresenter, but don't know how to access it.
Here is the ListBox hosting the NavigationButtons:
<ListBox DockPanel.Dock="Top" ItemsSource="{Binding ScreenViewModels}"
SelectedItem="{Binding SelectedScreenViewModel}">
<ListBox.ItemTemplate>
<DataTemplate>
<my:NavigationButton/>
</DataTemplate>
</ListBox.ItemTemplate>
I have created a base UserControl (called IconBaseControl) that all of these icon UserConrols can inherit. The base control has a Brush DependencyProperty, called IconFill. The parts of the paths on the icon that can change are bound to this property:
<Path Data="<data>" Fill="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type my:IconBaseControl}}, Path=IconFill}"
I know the binding is working correctly because the colors change when I change the default color on the UserControl. Ideally I want to use a VisualStateManager, because there will be many different states. So, I have a VisualStateManager on NavigationButton, the UserControl containing the ContentPresenter that hosts the icon (all UserControls that inherit IconBaseControl), called Content_Icon. I tried something like this in one of the states:
<VisualState x:Name="Deselected">
<Storyboard>
<ColorAnimation Storyboard.TargetName="TextBlock_Text" Storyboard.TargetProperty="Foreground.Color"
To="#FF5e5e5e" Duration="0"/>
<ColorAnimation Storyboard.TargetName="Content_Icon" Storyboard.TargetProperty="IconFill"
To="#FF5e5e5e" Duration="0"/>
</Storyboard>
</VisualState>
But I get the following error:
InvalidOperationException: Cannot resolve all property references in the property path 'IconFill'. Verify that applicable objects support the properties.
I also tried binding the property of the storyboard with something like this:
Storyboard.TargetProperty="(IconBaseControl.IconFill)
But get this error:
IconBaseControl is not supported in a Windows Presentation Foundation (WPF) project.
I have also tried messing around in code behind but cannot figure out how to convert the ContentPresenter to an IconBaseControl. I figured the ContentTemplate property would be the way to go but it's Nothing.
Any suggestions on how to animate this property? Open to pretty much anything :) I'm coding in VB.Net but any C# suggestions are fine too.
Thanks in advance.
EDIT: Included code for NavigationButton

I find that creating sub-classes of WPF controls can get messy and isn't necessary unless it is a very advanced problem. In my opinion creating the IconBaseControl as a child of UserControl is overkill in your scenario.
Here's my suggestion assuming you are using MVVM: create the IconBaseControl as a normal UserControl. Just create a IconControl.xaml with IconControl.xaml.cs code behind file just like you would any other view.
Here is an example of what you would have inside IconControl:
<UserControl>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetProperty="(Background).(SolidColorBrush.Color)" To="#FF5e5e5e" Duration="0:0:0" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetProperty="(Background).(SolidColorBrush.Color)" To="White" Duration="0:0:0" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<Image Source="Icon.jpeg" />
<TextBlock Text="{Binding PageName}" Grid.Column="1" />
</Grid>
</UserControl>`
Notice that the background of the surrounding grid will change based on a binding to a value called IsSelected on the DataContext. So at this point you need to create a ViewModel called IconControlViewModel.cs that has the IsSelected boolean exposed as a dependency property.
Finally the view that contains these navigation buttons:
<UserControl>
<ItemsControl ItemsSource="{Binding ListOf_IconControlViewModels}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type IconControlViewModel}">
<local:IconView />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</UserControl>
Notice the DataTemplate that tells the ItemsControl what to render when it sees a IconControlViewModel in the ItemsSource list. This is how I would design it using the MVVM pattern. I hope this helps and let me know if you need clarification on my answer, or it's way off.
Cheers,
Eric

Related

Is there a way to preview animation in xaml editor?

I am coding some basic animation and would like to see it's render without building/deploying the entire project. If I remember correctly, I already managed to see such preview when creating animation in codebehind. I am now using the xaml and am trying to find out how to get the same result.
I know there's the "Blend" solution, but i'd like to avoid it.
The "Turn ON render effects" and "Enable project code" buttons are active in xaml designer.
<Image
Name="CompressorGreySmokeNormalWay"
Source="/VirtualTrain.Setrag.GT46AC;component/Resources/Images/Common/SmokeIconGrey.png"
Width="{Binding ActualWidth, Mode=OneWay, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Canvas}}}"
Height="{Binding ActualHeight, Mode=OneWay, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Canvas}}}" >
<Image.Triggers>
<EventTrigger
RoutedEvent="UserControl.Loaded">
<BeginStoryboard>
<Storyboard
AutoReverse="True"
Storyboard.TargetProperty="Opacity">
<DoubleAnimation
RepeatBehavior="Forever"
BeginTime="0:0:00.00"
From="1.0"
To="0.0"
Duration="0:0:01.00"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Image.Triggers>
</Image>
I expected the designer to preview the Opacity variations, but it actually doesn't. Am I missing something ?
I finally found the solution !
You actually need to create a separate userControl on which the animation is applied.
Once you load your userControl inside another one, the animation is playing.
My guess is that the userControl is (pre-)compiled and enables the xaml designer to play the animation.

WPF TextBlock animation not firing when source is updated

I am simply trying to have some text flash red momentarily when the source for this TextBlock is updated. The text that the TextBlock is bound to works just fine, but for some reason the animation won't fire. I'm at a bit of a loss.
Any ideas?
<Border BorderBrush="{StaticResource Button.BackgroundBrush}"
Background="{StaticResource Screener.Background}"
BorderThickness="0,1,0,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<TextBlock Text="{Binding AlertBoxMessage, Mode=OneWay, NotifyOnSourceUpdated=True}"
Name="AlertBox"
MinHeight="55"
FontWeight="Normal"
FontSize="16"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="Black">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<EventTrigger RoutedEvent="Binding.SourceUpdated" >
<BeginStoryboard>
<Storyboard Storyboard.TargetProperty="Foreground">
<ColorAnimation From="Black"
To="Red"
AutoReverse="True"
RepeatBehavior="3"
Duration="0:0:2"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Border>
First of all I think you misinterpreted the Binding.SourceUpdated event.
Each binding consists of two endpoints - a source and a target. The target is the object on which the binding is set, target property is the DependencyProperty whose value will be bound, the source is the object against which Binding.Path will be resolved, and source property is the property to which the target property is bound. In your case the target is the TextBlock, target property is Text, source is the (inherited) data context (a view-model I presume), and source property is AlertBoxMessage.
As you probably know bindings can work in several modes. TwoWay will allow updating in both source-to-target and target-to-source directions, whereas OneWay will only do source-to-target updates.
Key information here is that the Binding.SourceUpdated will be raised whenever a target-to-source transfer occurs (and Binding.TargetUpdated for source-to-target transfer, see MSDN). In your case however the value is always updated in source-to-target direction, because you set the binding mode to OneWay, so Binding.SourceUpdated is never raised. To achieve your goal you should use Binding.TargetUpdated event instead (with Binding.NotifyOnTargetUpdated set to true).
There are however several other issues with your code that will prevent it from working, so let's go over them:
You're trying to animate the TextBlock.Foreground property (of type Brush) with a ColorAnimation, which can only be used to animate a property of type Color, and you'll get an InvalidOperationException. You should set StoryBoard.TargetProperty="Foreground.Color"
It may seem a bit counter-intuitive but setting RepeatBehavior="3" will make the animation repeat for 3 days, and not 3 times (see this question). You should use RepeatBehavior="3x"
To sum things up, here's the code that should do what you expect:
<TextBlock Text="{Binding AlertBoxMessage, Mode=OneWay, NotifyOnTargetUpdated=True}"
Name="AlertBox"
MinHeight="55"
FontWeight="Normal"
FontSize="16"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Foreground="Black">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<EventTrigger RoutedEvent="Binding.TargetUpdated">
<BeginStoryboard>
<Storyboard Storyboard.TargetProperty="Foreground.Color">
<ColorAnimation From="Black"
To="Red"
AutoReverse="True"
RepeatBehavior="3x"
Duration="0:0:2"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>

WPF smooth transition for databound property updates

I have a control that has its margin bound to a property of my view model:
<Grid Margin="{Binding Path=Property1, Converter={StaticResource Converter1}}"></Grid>
How do I get a smooth animation between successive updates to the Margin property? I want the margin to slide for a short amount of time instead of a discrete jump. Preferably a xaml solution.
Edit:
This is different than the other questions on this site, because I would need the "From" in a thickness animation to be bound to the previous value, and "To" to be bound to the updated value. It seems like a hack to just add another property to the view model for this.
Found the solution; the animation only needs to bind to the "From" and it will animate the way I want.
<Grid Margin="{Binding Path=Property1,
NotifyOnTargetUpdated=True,
Converter={StaticResource Converter1}}">
<Grid.Triggers><EventTrigger RoutedEvent="Binding.TargetUpdated">
<BeginStoryboard><StoryBoard>
<ThicknessAnimation Storyboard.TargetProperty="Margin"
Duration="00:00:00.5"
From="{Binding Path="Property1" Converter={StaticResource Converter1}}"/>
</StoryBoard></BeginStoryboard>
</EventTrigger></Grid.Triggers>
</Grid>
You can use ThicknessAnimation:
<BeginStoryboard>
<Storyboard>
<!-- BorderThickness animates from left=1, right=1, top=1, and bottom=1 to
left=28, right=28, top=14, and bottom=14 over one second. -->
<ThicknessAnimation
Storyboard.TargetProperty="Margin"
Duration="0:0:1.5" FillBehavior="HoldEnd" From="1,1,1,1" To="28,14,28,14" />
</Storyboard>
</BeginStoryboard>
You just need to bind the properties From and To

WPF rolldown animation

I want to create a container in WPF, which will display variable number of items. These items will be attached to the container via a Collection through a DependencyProperty.
I would like to display that container rolled up as long as items are not set. Then, when the items are set, I would like to animate rolling the container down to the size, when all items will be available.
I'm novice in WPF animations, but they seem quite straightforward for me. The thing I don't know is how to specify start and end size of the container without entering precise sizes in pixels. I need to animate from (size without additional controls) to (size with additional controls) and I quite have no idea, where to start.
How can I specify such relative sizes in WPF animation storyboard?
You can do something like this using the ActualHeight property:
<Grid Name="LayoutRoot">
<Grid Name="Container" ClipToBounds="True">
<ListBox ItemsSource="{Binding YourCollection}" ... >
<ListBox.Triggers>
<EventTrigger RoutedEvent="ListBox.SizeChanged">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="Container"
Storyboard.TargetProperty="Height" To="{Binding ActualHeight, ElementName=LayoutRoot,
FallbackValue=0, Mode=OneWay}" Duration="00:00:0.3" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</ListBox.Triggers>
</ListBox>
</Grid>
</Grid>
Note that I didn't use the From property of the DoubleAnimation... this will enable the animation to 'grow' as each item is added.

WPF Control Template bind to Child Control

In a TreeViewItem control template I need to bind a visual state to a child control but I cannot figure out the binding syntax.
In the TreeView's HierarchicalDataTemplate I have:
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<StackPanel>
<Image Name="imgPicture" Source=".."/>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
I would like to bind a visual state animation to imgPicture in the control template.
In the TreeViewItem's control template I have:
<VisualState Name="Selected">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Header}"
Storyboard.TargetProperty="MaxHeight"
To="100"
Duration="0"/>
</Storyboard>
</VisualState>
But the Storyboard.TargetName binds to the StackPanel (since that is the header of the TreeViewItem), and I need it to bind to the control inside the StackPanel (the imgPicture) so that I can change the control's property. Is there a way that I can do this by using the xaml binding syntax? Thank you very much for your help.
You can create a Binding by using the ElementName property (imgPicture) or set the Storyboard.TargetName to imgPicture.

Resources