I'm finding WPF inscrutable at times. Given the following XAML how would one add triggers to animate (slide down, fade in) new items added to the ObservableCollection Timeline. I've seen various examples for list boxes but nothing for items control.
<Grid>
<ScrollViewer>
<ItemsControl Name="TimelineItem"
ItemsSource="{Binding Timeline}"
Style="{StaticResource TimelineStyle}"
ItemContainerStyle="{StaticResource TweetItemStyle}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid VerticalAlignment="Top"
HorizontalAlignment="Left">
<Grid.ColumnDefinitions>
<ColumnDefinition Style="{StaticResource TweetImageColumnStyle}" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Rectangle Grid.Column="0"
Style="{StaticResource TweetImageStyle}">
<Rectangle.Fill>
<ImageBrush ImageSource="{Binding ProfileImageUrl}" />
</Rectangle.Fill>
</Rectangle>
<StackPanel Grid.Column="1">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
Style="{StaticResource TweetNameStyle}"
Text="{Binding Name}" />
<TextBlock Grid.Column="1"
Style="{StaticResource TweetTimeStyle}"
Text="{Binding TimeAgo}" />
</Grid>
<Controls:TextBlockMarkup Grid.Row="1"
Grid.Column="1"
Markup="{Binding MarkupText}"
Style="{StaticResource TweetStyle}" />
</StackPanel>
<Separator Grid.Row="2"
Grid.ColumnSpan="2"
Style="{StaticResource TweetSeparatorTop}" />
<Separator Grid.Row="3"
Grid.ColumnSpan="2"
Style="{StaticResource TweetSeparatorBottom}" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
</Grid>
It's been a while since I've animated WPF, but this should work by setting a DataTrigger in the DataTemplate of the ItemsControl for the Loaded Event.
A couple of notes:
Add the following xaml to the DataTemplate of the ItemsControl
Name the <Grid> inside the DataTemplate: "MyGrid"
Add a RenderTransformOriginproperty to the MyGrid to set the Y origin at the top:
<Grid x:Name="MyGrid" RenderTransformOrigin="0.5,0">
Be sure to include the Grid.RenderTransform attached property to your grid (see sample below)
Xaml
<DataTemplate.Resources>
<Storyboard x:Key="ItemAnimation" AutoReverse="False">
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="MyGrid" Storyboard.TargetProperty="(UIElement.Opacity)">
<EasingDoubleKeyFrame KeyTime="0" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="1" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="MyGrid" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)">
<EasingDoubleKeyFrame KeyTime="0" Value="0" />
<EasingDoubleKeyFrame KeyTime="0:0:0.5" Value="1" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</DataTemplate.Resources>
<DataTemplate.Triggers>
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
<BeginStoryboard Storyboard="{StaticResource ItemAnimation}" />
</EventTrigger>
</DataTemplate.Triggers>
Add the RenderTransform groups to your Grid
<!-- Include in the Grid -->
<Grid.RenderTransform>
<TransformGroup>
<ScaleTransform/>
</TransformGroup>
</Grid.RenderTransform>
This should get you close enough so that you can customize it yourself. FWIW: I used Blend to build out the animation by editing the style of the ItemTemplate of the Timeline object.
One last note: The animation will occur when the window loads the ItemsControl for the first time, for each item in the original collection. And will occur for an individual item when it is added to the collection. This behavior is a bit wonky, so you could remove the explicit binding of the trigger in the xaml and bind the trigger in the code-behind after the ItemsControl or Window loads.
EDIT
I've updated the example so that it should work with your XAML now.
Added another animation to slide (sort of) the new item. Actually, it's growing from a size of 0% to 100%, starting from the top of the Y axis.
Revised note #3 from above to include a RenderTransformOrigin property.
Added note #4 to include the Grid.RenderTransform attached property.
Related
I just started to learn WPF and XAML and I tried to gete an animation sample to work on my machine. Basically, a label background should change when a specific value is set in the corresponding textbox.
Issue is I get the following error: Background' property does not point to a DependencyObject in path '(0).(1)
This is the XAML:
<Window x:Class="WpfDataBinding.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:loc="clr-namespace:WpfDataBinding"
Title="MainWindow" Height="350" Width="264.828">
<Window.Resources>
<DataTemplate DataType="{x:Type loc:Person}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="210"/>
</Grid.ColumnDefinitions>
<TextBlock Name="nameLabel" Grid.Row="0" Grid.Column="0" Text="Name:" FontSize="14" Margin="3,3,0,0"/>
<TextBox Name="nameTextBox" Grid.Row="0" Grid.Column="1" Width="200" Text="{Binding Name}" FontSize="14" Margin="3" />
<TextBlock Name="ageLabel" Grid.Row="1" Grid.Column="0" Text="Age:" FontSize="14" Margin="3,3,0,0"/>
<TextBox Name="ageTextBox" Grid.Row="1" Grid.Column="1" Width="200" Text="{Binding Age}" FontSize="14" Margin="3"/>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Age}" Value="21">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<ColorAnimation Storyboard.TargetName="ageLabel"
Storyboard.TargetProperty="(Label.Background).(SolidColorBrush.Color)"
To="Red" Duration="0:0:1"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Window.Resources>
<Grid Margin="0,0,0,-0.2" HorizontalAlignment="Left" Width="248">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<ListBox ItemsSource="{Binding}" />
<StackPanel Grid.Row="2" Grid.ColumnSpan="2">
<Button Content="_Show.." Click="Button_Click"/>
<Button Content="_Age" Click="Button_Click_1"/>
</StackPanel>
</Grid>
Thank you
In order to animate the Color property of a SolidColorBrush in the Background property of a UI Element, you need to first set the Background. The default value of the Background property of a TextBlock is null, so there is nothing to animate.
So, first set a Background before animating it:
<TextBlock ...>
<TextBlock.Background>
</SolidColorBrush Color="Transparent"/>
</TextBlock.Background>
</TextBlock>
Now your could write the TargetProperty path like
Storyboard.TargetProperty="Background.Color"
or
Storyboard.TargetProperty="(TextBlock.Background).Color"
or
Storyboard.TargetProperty="(TextBlock.Background).(SolidColorBrush.Color)"
or even
Storyboard.TargetProperty="Background.(SolidColorBrush.Color)"
All expressions are equivalent. The details are explained in the PropertyPath XAML Syntax article on MSDN.
As per this question you need to use:
ColorAnimation Storyboard.TargetName="ageLabel"
Storyboard.TargetProperty="(TextBlock.Background).Color"
To="Red" Duration="0:0:1"/>
rather than (TextBlock.Background).(SolidColorBrush.Color) as apparently Background and SolidColorBrush are the same object.
(Label.Background).(SolidColorBrush.Color) line is the problem. It needs to be <ColorAnimation Duration="0" To="Red" Storyboard.TargetProperty="(TextBlock.Background).(SolidColorBrush.Color)" Storyboard.TargetName="ageLabel" />
I'd like to animate the text in red in the following connection dialog, so the label content would show...
Frame 1: Connecting
Frame 2: Connecting.
Frame 3: Connecting..
Frame 4: Connecting...
Go to frame 1 if connection isn't established yet.
When connection is established: display "Connected".
I have found tutorials about animating text, but not about text content. Is it something easily doable using WPF? Any help/tutorial links would be appreciated.
Here's the WPF code that I used to generate the screenshot.
<Window x:Class="radar_test_ui.SerialConnectionWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Connection" SizeToContent="WidthAndHeight">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Content="COM:" Margin="5,0,5,0" />
<ComboBox Grid.Row="1" Margin="10,0,10,0">
<ComboBoxItem Content="COM1" IsSelected="True" />
<ComboBoxItem Content="COM2" />
<ComboBoxItem Content="COM3" />
</ComboBox>
<Label Grid.Row="2" Content="Baud rate:" Margin="5,10,5,0" />
<ComboBox Grid.Row="3" Margin="10,0,10,0">
<ComboBoxItem Content="9600" IsSelected="True" />
<ComboBoxItem Content="19200" />
</ComboBox>
<Separator Grid.Row="4" Margin="0,15,0,15" />
<Grid Grid.Row="5" Margin="10,0,10,10">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label Content="Connecting..." FontSize="10" Width="100" VerticalAlignment="Bottom" Margin="0,0,0,-5" Name="LabelStatus" />
<Button Grid.Column="1" Content="Connect" Padding="10,3,10,3" Margin="0,0,10,0" Click="ConnectClick" />
<Button Grid.Column="2" Content="Cancel" Padding="10,3,10,3" Click="CancelClick" />
</Grid>
</Grid>
</Window>
You could use ObjectAnimationUsingKeyFrames and set whatever Duration you like for the key frames.
Here's a simple example. When you click the connect button the Label animation for "Connecting.." will start and when you click it again the Label will say "Connected".
<Label Name="LabelStatus" FontSize="10" Width="100" VerticalAlignment="Bottom" Margin="0,0,0,-5">
<Label.Style>
<Style TargetType="Label">
<Setter Property="Content" Value="Connecting..."/>
<Style.Triggers>
<DataTrigger Binding="{Binding Connecting}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard Storyboard.TargetProperty="Content">
<ObjectAnimationUsingKeyFrames Duration="00:00:04"
RepeatBehavior="Forever">
<DiscreteObjectKeyFrame KeyTime="00:00:00"
Value="Connecting"/>
<DiscreteObjectKeyFrame KeyTime="00:00:01"
Value="Connecting."/>
<DiscreteObjectKeyFrame KeyTime="00:00:02"
Value="Connecting.."/>
<DiscreteObjectKeyFrame KeyTime="00:00:03"
Value="Connecting..."/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard Storyboard.TargetProperty="Content">
<ObjectAnimationUsingKeyFrames Duration="00:00:00">
<DiscreteObjectKeyFrame KeyTime="00:00:00"
Value="Connected"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>
<Button Grid.Column="1" Content="Connect" Padding="10,3,10,3" Margin="0,0,10,0"
Click="ConnectClick"/>
And in the code behind I added the boolean property Connecting and implemented INotifyPropertyChanged.
Update
PropertyChanged is null because the window doesn't have a DataContext.
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
}
private bool _connecting;
public bool Connecting
{
get { return _connecting; }
set
{
_connecting = value;
OnPropertyChanged("Connecting");
}
}
private void ConnectClick(object sender, RoutedEventArgs e)
{
Connecting = !Connecting;
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Inside custom control I'm trying to bind my ItemsControl.ItemsSource and get error. Here is how template looks:
<Style TargetType="controls:IdattFilterBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="controls:IdattFilterBox">
<ScrollViewer HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Auto">
<ItemsControl x:Name="PART_ItemsControl">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Caption}" />
<ComboBox Grid.Column="1">
<ComboBoxItem Content="Contains" />
<ComboBoxItem Content="Begins with" />
<ComboBoxItem Content="Ends with" />
</ComboBox>
<TextBox Grid.Column="2" Text="{Binding FieldFilter1, Mode=TwoWay}" />
<TextBox Grid.Column="3" Text="{Binding FieldFilter2, Mode=TwoWay}" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
<Border Grid.ColumnSpan="2" x:Name="ValidationErrorElement" BorderBrush="#FFDB000C" BorderThickness="1" CornerRadius="1" Visibility="Collapsed">
<ToolTipService.ToolTip>
<ToolTip x:Name="validationTooltip" DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}" Placement="Right" PlacementTarget="{Binding RelativeSource={RelativeSource TemplatedParent}}" Template="{StaticResource ValidationToolTipTemplate}">
<ToolTip.Triggers>
<EventTrigger RoutedEvent="Canvas.Loaded">
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="IsHitTestVisible" Storyboard.TargetName="validationTooltip">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<System:Boolean>true</System:Boolean>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</ToolTip.Triggers>
</ToolTip>
</ToolTipService.ToolTip>
<Grid Background="Transparent" HorizontalAlignment="Right" Height="12" Margin="1,-4,-4,0" VerticalAlignment="Top" Width="12">
<Path Data="M 1,0 L6,0 A 2,2 90 0 1 8,2 L8,7 z" Fill="#FFDC000C" Margin="1,3,0,0"/>
<Path Data="M 0,0 L2,0 L 8,6 L8,8" Fill="#ffffff" Margin="1,3,0,0"/>
</Grid>
</Border>
</ItemsControl>
</ScrollViewer>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
In code I' trying to set this PART's ItemSource:
this.WrapperItemsControl.ItemsSource = filterData;
On this line I get error(error message in subject). Why can't I set ItemsSource and what this error means? I want controls in DataTemplate to bind to objects that stored in filterData.
EDIT:
PART_ItemsControl is what my WrapperItemsControl
this.WrapperItemsControl = GetTemplateChild(PartItemsControl) as ItemsControl;
EDIT2:
Screenshot showing that there is one item (Border?) Where does it come from?!
EDIT3
DOH! I placed validation border in a wrong spot
You can't use ItemsSource if you manually add items to your ItemsControl. For example, you would get an error if you tried this:
<ItemsControl ItemsSource="{Binding MyItems}">
<ListBoxItem>Item1</ListBoxItem>
<ListBoxItem>Item2</ListBoxItem>
</ItemsControl>
You may think you're not doing that, but you actually are. You're adding a single item to your ItemsControl, and that item is of type DataTemplate. Look:
<ItemsControl ...>
<DataTemplate ... />
That syntax means "create a DataTemplate and add it to the ItemsControl's Items property". (Items is the default property for ItemsControl, so any elements you nest under ItemsControl, if you don't otherwise specify, get added to Items.)
I think you intended to assign that DataTemplate to the ItemsControl's ItemTemplate property, rather than adding it to Items. Try this instead:
<ItemsControl ...>
<ItemsControl.ItemTemplate>
<DataTemplate ... />
</ItemsControl.ItemTemplate>
</ItemsControl>
Kind of new to WPF and I am working on an app that has a general user input form and a "details" section that is hidden in an Expander. I am trying to get it so that if the user Tabs into the expander control it will automatically expand and put focus into the first control within that expander.
Some stripped down XAML:
<StackPanel>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="24"/>
<RowDefinition Height="24"/>
</Grid.RowDefinitions>
<Label Content="Email" Grid.Row="0" Grid.Column="0"/>
<TextBox Grid.Row="0" Grid.Column="1"/>
<Label Content="Department" Grid.Row="0" Grid.Column="2"/>
<TextBox Grid.Row="0" Grid.Column="3"/>
<Label Content="Contact Name" Grid.Row="1" Grid.Column="0"/>
<TextBox Grid.Row="1' Grid.Column="1"/>
<Label Content="Phone Number" Grid.Row="1" Grid.Column="2"/>
<TextBox Grid.Row="1" Grid.Column="3"/>
</Grid>
<Expander ExpandDirection="Down" IsExpanded="False" Header="Details">
<StackPanel Orientation="Horizontal">
<Label Content="Address"/>
<TextBox />
<Button Content="Add Another" />
</StackPanel>
</Expander>
</StackPanel>
What I would like to do is that if the user is currently entering in the Phone number and hits tab the Details Expander should expand and put focus into the Address's text box. I've tried setting TabIndex and playing with KeyboardNavigation.Tab... without any luck.
Any ideas how to do this?
Replace your Expander with following XAML using an EventTrigger and a Storyboard:
<Expander ExpandDirection="Down"
IsExpanded="False"
Header="Details">
<Expander.Style>
<Style>
<Style.Triggers>
<EventTrigger RoutedEvent="Expander.GotFocus">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<BooleanAnimationUsingKeyFrames
Storyboard.TargetProperty="(Expander.IsExpanded)">
<DiscreteBooleanKeyFrame
KeyTime="00:00:01"
Value="True" />
</BooleanAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Style.Triggers>
</Style>
</Expander.Style>
<StackPanel Orientation="Horizontal">
<Label Content="Address"/>
<TextBox />
<Button Content="Add Another" />
</StackPanel>
</Expander>
I don't think there is a pure Xaml approach for this. You may have to handle the expander's GotFocus event to 1) expand the expander, and 2) the call the Focus() method on the first control.
I have the following layout in my grid
<Grid>
<Grid.RowDefinitions >
<RowDefinition Height="50" />
<RowDefinition />
</Grid.RowDefinitions>
<Button Click="Button_Click" Grid.Row="0" Width="50" Grid.Column="2" Content="Test" />
<Grid Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="120" />
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Grid.Row="1" Background="red" />
<StackPanel Grid.Column="1" Grid.Row="1" Background="Blue" />
<GridSplitter x:Name="gs" Grid.Column="1" Grid.Row="1" Width="10" />
<StackPanel x:Name="green" MinWidth="100" Grid.Column="2" Grid.Row="1" Background="Green" />
</Grid>
</Grid>
I essentially want this layout when the button is pressed:
<StackPanel Grid.Column="0" Grid.Row="1" Background="red" />
<StackPanel Grid.Column="1" Grid.ColumnSpan="2" Grid.Row="1" Background="Blue" />
<!--<GridSplitter x:Name="gs" Grid.Column="1" Grid.Row="1" Width="10" />
<StackPanel x:Name="green" MinWidth="100" Grid.Column="2" Grid.Row="1" Background="Green" />-->
And this layout when the button is pressed again:
<StackPanel Grid.Column="0" Grid.Row="1" Background="red" />
<StackPanel Grid.Column="1" Grid.ColumnSpan="1" Grid.Row="1" Background="Blue" />
<GridSplitter x:Name="gs" Grid.Column="1" Grid.Row="1" Width="10" />
<StackPanel x:Name="green" MinWidth="100" Grid.Column="2" Grid.Row="1" Background="Green" />
How Can I make the gridsplitter and last panel disappear, and have the panel in the middle column fill it's place? (and vice-versa) Is there a way to change the column span at runtime?
Thanks!
You can use triggers or you can use a storyboard/animation the fires when you button is clicked.
you want to do something like this.....
<Window.Resources>
<Storyboard x:Key="OnClick1">
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="gs" Storyboard.TargetProperty="(FrameworkElement.Width)">
<SplineDoubleKeyFrame KeyTime="00:00:00.5000000" Value="0"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="green" Storyboard.TargetProperty="(FrameworkElement.MinWidth)">
<SplineDoubleKeyFrame KeyTime="00:00:00.5000000" Value="0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</Window.Resources>
<Window.Triggers>
<EventTrigger RoutedEvent="ButtonBase.Click" SourceName="button">
<BeginStoryboard Storyboard="{StaticResource OnClick1}"/>
</EventTrigger>
</Window.Triggers>
in order to reverse this animation, you should use something to maintain state, say use a ToggleButton and use the ToggleButton.Checked && ToggleButton.Unchecked routed events. Or, add a dependency property to your codebehind that maintains the state.
I think the main problem is remembering to change the MinWidth attribute as well. MinWidth will override actual width.