WPF Bind UserControl to resource - c#

At the moment, I'm doing some experimentation. My current scenario: I have a StoryBoard to transition between 2 UserControls (for example, it shrinks the current UserControl then grows the other).
What I want to do is to have 2 UserControls defined as part of the XAML, with keys of "Current" and "Next". The Current should be bound to the currently viewed UserControl. The Next should be bound before transition, so the StoryBoard knows which element to transition to. Here's where I'm stuck: ENTIRELY using XAML, how would one go about this?
I have a simple StoryBoard that is a Resource of the ItemsControl, as well as two UserControl items:
<ItemsControl.Resources>
<Storyboard x:Key="TransitionStoryboard">
<!-- Shrink this one. -->
<DoubleAnimation Storyboard.Target="{Binding Current}" Storyboard.TargetProperty="Width" To="0" Duration="0:0:1"/>
<!--Grow the next.-->
<DoubleAnimation Storyboard.Target="{Binding Next}" Storyboard.TargetProperty="Width" To="100" BeginTime="0:0:1" Duration="0:0:1"/>
</Storyboard>
<UserControl x:Key="Current"/>
<UserControl x:Key="Next" Width="0"/>
</ItemsControl.Resources>
So, when I define a new UserControl that belongs to the ItemsControl (like this):
<my:Control1 x:Name="ControlOne"/>
How do I set the "Current" UserControl top be ControlOne? Then, when I want to transition, how do I set one as "Next"? And how can I change these after a trigger?
Thanks

This is a total mess. You seem to completely misunderstand on how the static resources are used.
To achieve what you are trying to do, you should first decide on what will trigger the animation. Ideally it should be some DependencyProperty on your controls, or a property on your view model (which implements INotifyPropertyChanged). For example, you can declare an IsSelected property. Then you should create a style which triggers "grow" animation when control is selected and triggers "shrink" animation, when control loses selection.
For example:
<Style TargetType="Control" x:Key="FancyStyle">
<Style.Triggers>
<DataTrigger Binding={Binding IsSelected} Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard Storyboard="{StaticResource YourGrowAnim}"/>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard Storyboard="{StaticResource YourShrinkAnim}"/>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
Then you should assign that style to every control, you want animated this way and set up transitions between IsSelected properties. You can also use EventTrigger instead and bind animations to events (for example you can trigger those animations when control gets/loses focus).
You should also fix your "grow" animation, it wont work, most likely.

Related

How to animate Adding & Removal in ListView/Box?

I am trying to understand how animation works in WPF with StoryBoards.
So far I have managed the following :
creating a ListView with custom items and resources binding ;
using ItemContainerStyle in order to make sure each item takes all the width of the ListView ;
using EventTrigger to trigger specific animation
My problem is simple.
First I tried the Loaded trigger to animate an item whenever it is added to the ListView's binded ObservableCollection. It works fine with opacityproperty but the problem is that this event is triggered everytime I scroll, meaning Windows loads and unloads items on-the-go to save up memory (which is understable but uses a lot more CPU with complex item).
So it's a no go.
Furthermore, the Unloaded trigger "does not really work" and was solved here : https://stackoverflow.com/a/14619637/3535408
So, using the aforementioned solution, I would like to fire the animation manually.
To be more precise, whenever a specific property of my items (much like Removing) is changed, I'd like a StoryBoard to be started.
How to accomplish this for sliding animation ? Because I would really like to have my items to slide, on the X axis, in and out (entering via the left, and leaving via the left) of my ListView.
These sliding animations must be triggered only when an item is added to the binded ObservableCollection and when it is removed.
The following XAML code does not work :
Within a ListView > ListView.ItemTemplate > DataTemplate > DataTemplate.Triggers
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=Removing}" Value="true" >
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard >
<DoubleAnimation Storyboard.TargetProperty="Opacity" From="1" To="0" Duration="0:0:0.5"/>
<DoubleAnimation Storyboard.TargetProperty="LayoutTransform.ScaleY" From="0" Duration="0:0:.2"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</DataTemplate.Triggers>
The opacity animation really works just fine. However the LayoutTransform produces the following System.InvalidOperationException : Cannot resolve all property references in the property path 'LayoutTransform.ScaleY'. Verify that applicable objects support the properties.
Moreover, I just do not know how to do a sliding animation on the X axis :/
Here is the content of my ListView'sItemContainerStyle :
<Setter Property="HorizontalAlignment" Value="Stretch"></Setter>
<Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter>
<Setter Property="Margin" Value="0, -2, 0, -2" ></Setter>
<Setter Property="LayoutTransform">
<Setter.Value>
<ScaleTransform x:Name="transform" />
</Setter.Value>
</Setter>
<Setter Property="RenderTransform">
<Setter.Value>
<ScaleTransform/>
</Setter.Value>
</Setter>
Thanks for the help

WPF: How can I animate the visibility of a dock panel and bind it so its state gets saved?

I'm relatively new to WPF and data binding, animation, etc., so bear that in mind there.
I have the following XAML:
<DockPanel HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<DockPanel x:Name="MagnificationBar" Margin="20,10" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" DockPanel.Dock="Top">
<DockPanel.Style>
<Style TargetType="DockPanel">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsChecked, ElementName=MagCheckBox}" Value="True">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</DockPanel.Style>
<StackPanel Margin="10,0,0,0" Orientation="Horizontal" DockPanel.Dock="Bottom">
<CheckBox x:Name="MagCheckBox" VerticalAlignment="Center" IsChecked="{Binding UserData.HideMagnificationBar, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<CheckBox.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation AccelerationRatio=".8" Storyboard.TargetName="MagnificationBar" Storyboard.TargetProperty="Height" From="{Binding ElementName=MagnificationBar, Path=ActualHeight}" To="0" Duration="0:0:0.3"/>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="MagnificationBar" >
<DiscreteObjectKeyFrame KeyTime="0:0:0.3" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</CheckBox.Triggers>
</CheckBox>
....
So here is my question... I want the "MagnificationBar" dock panel at the top to be visible depending on whether a checkbox is checked, and I want that checkbox's value is bound to a model called UserData. All of that works just fine.
But I also want the dock panel to animate when it dissapears (when the checkbox is checked).
The problem is that if I bind the panel's visibility directly to the checkbox (so that the panel is not visible when the app loads the UserData from disk and populates the checkbox), then that overrides the animation, and makes the panel disappear instantly when the checkbox is checked, rather than "animating away".
I tried moving the checkbox binding down into the animation, but WPF doesn't seem to like that.
Any suggestions for how to both animate an element's visibility AND bind its visibility so it is persistent?
Thanks!
-Jesse
Don't bind the Visibility property to the Checkbox.IsChecked property. Instead, bind it to a bool property using a BooleanToVisibilityConverter and bind the Checkbox.IsChecked property to another bool property.
As the Checkbox is checked, the first property will change and at that point, you can start your closing animation. By adding a Timeline.Completed event handler to your closing Storyboard, you can then set your second bool variable to false to set the Visibility to Collapsed as soon as the animation ends.
Remove the dockpanel's Style for I believe they are conflicting. It doesn't need to have a style setting its visibility to Collapsed if that is already done by the check box. Start the control out as Collapsed and unhide if necessary after the data load.
So when the check box is selected it will disappear and at the end the storyboard will make it hidden.

Applying style to control programmatically

I'm really new to WPF, struggling to get my head around some stuff to do with styles. Firstly, the style doesn't throw any errors anywhere, although i'm still not sure if it's completely correct...so here's my XAML for the style;
edit: The style should allow my custom usercontrol to fade in using a Storyboard and DoubleAnimation
<Application.Resources>
<Style TargetType="{x:Type UserControl}" x:Key="UCStyle">
<Style.Triggers>
<DataTrigger Binding="{Binding Visibility}" Value="Visible">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="UserControl.Opacity" From="0.0" To="1.0" Duration="0:0:3">
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Application.Resources>
From here, i'm using a checkbox to add a usercontrol to a canvas' children, as so;
private void CheckBox_Checked(object sender, RoutedEventArgs e)
{
UserControlLibrary.UserControl1 userControl = new UserControlLibrary.UserControl1();
userControl.Style = Resources["UCStyle"] as Style;
canvas1.Children.Add(userControl);
}
I read a around on SO, and found a few answers suggesting this is how you apply a style programatically, referencing the key as a style. However, when i run this, the usercontrol doesn't appear to fade in (as the styling would suggest).
Am i missing something vital here? or just being a little silly?
edit 2:
i've modified my checkbox checked event to set visibility to hidden after adding it to canvas, and a new button with a click event that sets visibility to visible, but sadly it didn't fix my problem.
You're using a DataTrigger that is binding to UserControl.DataContext.Visibility, however I'm fairly sure you want to bind to UserControl.Visibility instead, so you need to use a different binding type.
All bindings by default reference the DataContext, so to reference something other than the DataContext, you need to specify a different source for your binding, such as a RelativeSource binding to Self
<DataTrigger Binding="{Binding Visibility, RelativeSource={RelativeSource Self}}" Value="Visible">
As an alternative, you can use a regular Trigger instead of a DataTrigger, which should base the trigger off a Property of the UserControl instead of trying to find the property through a binding.
<Trigger Property="Visibility" Value="Visible">

Animating WPF element in XAML using attached property?

I got my animation to work triggered by a property in my ViewModel. If I set my TargetProperty to Width, the below code actually works in growing the image.
Next, I wanted to actually move the image up and down. To do this, I added a Canvas component around my image, to be able to animate based on Canvas.Top property. Setting Canvas.Top on the image moves it where I want.
However, if I set my StoryBoard.TargetProperty to Canvas.Top, I get an error:
Cannot resolve all property references in the property path
Canvas.Top.
<Style x:Key="LoadingImageAnimation" TargetType="{x:Type Image}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsLoading}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation From="10" To="250" AutoReverse="True" Duration="0:0:30"
Storyboard.TargetProperty="Canvas.Top"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
Is my approach totally off, or just a matter of finding the Attached Property?
Did some digging around on Property Path Syntax, and the solution was actually simple. Needed to add parentheses:
Storyboard.TargetProperty="(Canvas.Top)"
The animation is not as smooth as i would like.. but at least it works now.

Controlling a UserControl's Storyboard from the parent Window in WPF using only XAML

I have created a very simple test app to try and solve this problem described to me by a co-worker. He was able to trigger it in C#, but I believe he needed the solution to be more generic and wanted it to be done strictly in the XAML. The problem was: How do you trigger the Storyboard inside of a UserControl to begin in response to an event on a control in the containing Window (the parent doesn't have to be a Window, it can be a Page or another UserControl...I use a Window for simplicity). So, my best example is that you have a Window with a Button and an instance of your UserControl. You want the Click event of the Button to trigger a Storyboard inside of the UserControl to begin.
Inside of the UserControl itself, the Storyboard is declared as a Resource with the key of MyStoryboard. I created a Button with an EventTrigger to show that I could trigger the Storyboard by referencing it with a Binding expression. Here is the complete XAML for my UserControl:
<UserControl x:Class="UserControlStoryboard.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300" Width="300">
<UserControl.Resources>
<Storyboard x:Key="MyStoryboard">
<ColorAnimation Storyboard.TargetName="grid" Storyboard.TargetProperty="(Grid.Background).Color" From="CadetBlue" To="White" Duration="0:0:2" />
</Storyboard>
</UserControl.Resources>
<Grid Background="CadetBlue" x:Name="grid">
<Button Content="Press Me" Height="50">
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<BeginStoryboard.Storyboard>
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type UserControl}}" Path="Resources[MyStoryboard]" />
</BeginStoryboard.Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>
</Grid>
I was hoping that since I was able to tell a Storyboard to begin by referencing it with a Binding expression that I should be able to do the exact same thing from a Window that contains this UserControl as a child element. But I can't seem to figure out how to get a reference to the Storyboard inside of the parent Window. Is this impossible to accomplish in XAML only?
The parent window in my test project has nothing but a button and an instance of this UserControl. If it's even possible, which element would let me add an EventTrigger where the source of the event is the Button and the trigger action tells the UserControl's Storyboard to begin?
Thanks.
I think the idea of responding to external events within UserControl is not a very safe one. Within UserControl, you'd want to react only to events that are born within the control.
Can you flip the situation around and do the following: ?
Expose the UC property that needs to be animated as a dependency property (background in this case)
Create both EventTrigger and Storyboard on the parent window
You can access the storyboard by using the StaticResource. You need only assign the resource to the Storyboard on the BeginStroyboard.
<BeginStoryboard Storyboard="{StaticResource MyStoryboard}">
</BeginStoryboard>
I changed the code to the following and when you press the button, it will start the storyboard for you. Hope this helps.
<UserControl.Resources>
<Storyboard x:Key="MyStoryboard">
<ColorAnimation Storyboard.TargetName="grid" Storyboard.TargetProperty="(Grid.Background).Color" From="CadetBlue" To="White" Duration="0:0:2" />
</Storyboard>
</UserControl.Resources>
<Grid Background="CadetBlue" x:Name="grid">
<Button Content="Press Me" Height="50">
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard Storyboard="{StaticResource MyStoryboard}">
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>
</Grid>
There probably isn't a good way to do it with pure Xaml.
You can:
Make a dependency property on your UserControl that takes a Button, then you can bind to it.
Make some kind of crazy converter that traverses your visual tree looking for something in particular.
Both require some C#, so I don't know if any of the above is what you are looking for.

Categories

Resources