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.
Related
In an wpf application I have a ListView with Drag and Drop functionality, which inserts the dropped element at the position, where it was dropped instead of the end of the list. Thats working fine. What i now want to achieve seemed to be the simple part :) I want to make clear for the user at which position in the list the currently dragged element will be dropped. There should be a gap where the new element will be inserted.
My approach was to change the Padding of the ListViewItem at the current mousePosition.
<ControlTemplate TargetType="ListViewItem">
<Border x:Name="BorderA">
<ContentPresenter />
<Border.Triggers>
<!--padding for gap when mouse hovers element-->
<EventTrigger RoutedEvent="DragEnter" SourceName="BorderA">
<BeginStoryboard>
<Storyboard>
<ThicknessAnimation
Storyboard.TargetProperty="Padding"
From="0,0,0,0" To="0,25,0,0"
BeginTime="0:0:0" Duration="0:0:0.5"
AutoReverse="False" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<!--back to normal padding-->
<EventTrigger RoutedEvent="DragLeave" SourceName="BorderA">
<BeginStoryboard>
<Storyboard>
<ThicknessAnimation
Storyboard.TargetProperty="Padding"
From="0,25,0,0" To="0,0,0,0"
BeginTime="0:0:0" Duration="0:0:0.5"
AutoReverse="False" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Border.Triggers>
</Border>
</ControlTemplate>
I also need a DataTemplate to display my listItems correctly. I simplified that here, which might make it appear nearly redundand, but in reality i need to display a complex viewModel and it seems to be part of the problem.
<DataTemplate x:Key="SortableListBoxItemTemplate" DataType="item:SortableListItemViewModel">
<Border Background="{StaticResource CallToActionBrush}"
BorderThickness="{StaticResource BorderThickness}"
BorderBrush="{StaticResource PassiveBorderBrush_Strong}">
</Border>
</DataTemplate>
That seemd to work. When i hover an element while dragging another, a gap between that element and the previous list element appears. But sometimes when the gap grows the mouse position will be within this gap. That triggeres the DragLeave Event, the gap starts closing until the listItem has reached the mouse position again and so on. That causes an ugly flickering and i am not sure how to avoid that. Everything is fine as long as the DataTemplate hasn't a Background color. But thats not the visual effect i want. Is there a way to trigger that DragEnter and DragLeave events only if they were fired from my listviewItem (or BorderA)? Or is there a complete other way to achieve the effect i want?
Is your problem the unexpected DragLeave event causing the flickering?
In this case, I guess setting a transparent background to your BorderA element would solve the problem.
<ControlTemplate TargetType="ListViewItem">
<Border x:Name="BorderA" Background="Transparent">
<ContentPresenter />
<Border.Triggers>
The transparent background will enable the hit test for your whole ListViewItem including the padding you are creating as placeholder.
I found a solution.
The problem wasn't the DragLeave event, but the DragEnter event when the cursor enters the Item (coming from the gap). This triggers the animation to start again and someone stupid has told this animation to start From="0,0,0,0"... I removed that part and everything worked just fine.
For me there is still a question. Maybe in some cases removing the From-part is not an option. Is there a way to add a condition to the event trigger. Something like a Multitrigger?
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
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
If I attach a callback to the loaded event of my main user control and check the ActualWidth of a grid column, that value is still 0. I find it strange, given the fact that the documentation states: "The Loaded event is raised before the final rendering, but after the layout system has calculated all necessary values for rendering."
Basically I need the ActualWidth so I can set the 'to' value of an animation (so something animates from a width of 0 to the actual width).
The ActualWidth and ActualHeight values are available during the SizeChanged event. Which works out nice, so you can update your calculations when your applications resizes if needed.
I posted about this recently using the event to randomly postion elements on the screen
I think you're probably trying to solve the problem in the wrong way.
Your best bet is to animate a ScaleTransform that's added to the Grids' RenderTransform property. Here's an example that does just that:
<Grid x:Name="LayoutRoot">
<Grid.Triggers>
<EventTrigger RoutedEvent="Grid.Loaded">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="MyScaleTransform" Storyboard.TargetProperty="ScaleX" From="0" To="1" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Grid.Triggers>
<Grid Background="Blue" >
<Grid.RenderTransform>
<ScaleTransform x:Name="MyScaleTransform" />
</Grid.RenderTransform>
</Grid>
</Grid>
Can you hook or override the LayoutUpdated event in your control, rather than the Loaded event? You'd have to add some additional logic to make sure that your animation only ran once, at the appropriate time, but it might give you the additional flexibility you need.
I'm starting to kick Silverlight around (although it currently feels the other way around) by rewritting an existing ASP.NET application - as good a place to start as any I thought.
I have 'mastered' pulling data from a database, through a service and into a datagrid and also populating image elements in the rows. So far so good.
Now i'm stuck and have a headache!
I want to add a simple animation (a green rectangle moving over a red rectangle to display a % progress) to the last column of each row, but can't get it wired up.
My xaml looks like this:
<data:DataGrid.Columns>
.
.
.
.
<data:DataGridTemplateColumn>
<data:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel x:Name="AnimationPanel">
<StackPanel.Resources>
<Storyboard x:Key="ShowProgress">
<DoubleAnimation Storyboard.TargetProperty="Width"
Storyboard.TargetName="goalProgressBar_Complete"
From="0" To="50"
Duration="0:0:5">
</DoubleAnimation>
</Storyboard>
</StackPanel.Resources>
<Rectangle x:Name="goalProgressBar_Complete"
Width="1" Height="100"
Fill="#0aa60e"></Rectangle>
</StackPanel>
</DataTemplate>
</data:DataGridTemplateColumn.CellTemplate>
</data:DataGridTemplateColumn>
</data:DataGrid.Columns>
which is quite obviously wrong (because it doesn't work) and I am starting to think that it just is not possible to add animation to a Datagrid in this way. I've Googled hard looking for a piece of code and while there have been some tantilising results they have not amounted to anything.
Am I trying to do something that Silverlight simply does not support or am I missing something really straightforward?
Thanks in advance.
Update:
Although I'm not out of the woods yet I have managed to get the animation running with the appropriate column.
The answer provided by MasterMax led me to look at EventTriggers and the revised xaml looks like this:
<data:DataGridTemplateColumn>
<data:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Rectangle x:Name="GoalProgress" Height="100" Width="1" Fill="Green">
<Rectangle.Triggers>
<EventTrigger RoutedEvent="Rectangle.Loaded">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Width"
Storyboard.TargetName="GoalProgress"
From="0" To="50" Duration="0:0:5">
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Rectangle.Triggers>
</Rectangle>
</DataTemplate>
</data:DataGridTemplateColumn.CellTemplate>
</data:DataGridTemplateColumn>
All I need to do now is bind in the % value in the 'To' property and add my underlying Red rectangle. No doubt this will not prove as easy as I'm currently hoping but then it wouldn't be fun otherwise would it ;-)
Try attaching it to the EventTrigger for the Datagrid. Something similar to this:
<Window.Triggers>
<EventTrigger RoutedEvent="Window.Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Name="EntranceAnimation"
Storyboard.TargetName="MainWindow"
Storyboard.TargetProperty="Opacity"
From="0.0"
To="1.0"
Duration="0:0:3">
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Window.Triggers>
In your case you'd be looking for the DataGrid.Loaded event (or something similar). This works for me, and I'm fading the whole window in on page loaded.
It looks to me like you aren't actually playing the storyboard. You need to add mouse enter and mouse leave handlers for your rectangle and then play and stop your storyboard accordingly. Make sure to both stop and then play the storyboard in the enter command (otherwise you get some interesting side-effects).
Update
Okay, having spent my day neck deep in Silverlight, I'm not much closer to helping. I was looking at the generic.xaml file inside the Silverlight assembly containing the DataGrid. You might consider taking a look in there. From what I can see, they achieve some of this for the row details transition with control templating though I haven't looked at the corresponding code yet to determine exactly how.
for that type of application, an animation may not be the best solution. you may want to try binding the width property of the moving rectangle to a variable in your application, or adjust the width with each tick as the progress changes.