In the ListView, adding and removing items is done with a nice animation. However when I move an item (or sort the collection) there is no nice animation, it seems to just reset.
Does anyone know how I can animate the items move to their new location? I've seen this behaviour in ios apps, surely it is possible in UWP?
You can see in this demo the remove animation is nice, the reorder is not.
Simple code example:
Xaml
<Page
x:Class="ExampleApp.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:ExampleApp"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid>
<Button Name="btnReorder" Content="Reorder" Click="btnReorder_Click" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Height="32" />
<Button Name="btnRemove" Content="Remove" Click="btnRemove_Click" HorizontalAlignment="Left" Margin="100,10,0,0" VerticalAlignment="Top" Height="32" />
<ListView Name="list" Margin="0,200,0,0">
<ListView.ItemTemplate>
<DataTemplate x:DataType="local:MyData">
<Rectangle Width="100" Height="20" Fill="{x:Bind Path=Brush}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
</Page>
Code
using System.Collections.ObjectModel;
using Windows.UI;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
namespace ExampleApp
{
public sealed partial class MainPage : Page
{
ObservableCollection<MyData> myCollection = new ObservableCollection<MyData>();
public MainPage()
{
InitializeComponent();
myCollection.Add(new MyData() { Brush = new SolidColorBrush(Colors.Red) });
myCollection.Add(new MyData() { Brush = new SolidColorBrush(Colors.Blue) });
myCollection.Add(new MyData() { Brush = new SolidColorBrush(Colors.Orange) });
myCollection.Add(new MyData() { Brush = new SolidColorBrush(Colors.CornflowerBlue) });
myCollection.Add(new MyData() { Brush = new SolidColorBrush(Colors.Yellow) });
myCollection.Add(new MyData() { Brush = new SolidColorBrush(Colors.Green) });
list.ItemsSource = myCollection;
}
private void btnReorder_Click(object sender, RoutedEventArgs e)
{
// moving an item does not animate the move
myCollection.Move(2, 3);
}
private void btnRemove_Click(object sender, RoutedEventArgs e)
{
// removing does a nice animation
myCollection.RemoveAt(1);
}
}
public class MyData
{
public Brush Brush { get; set; }
}
}
Thanks!
The template of ListView already contains these 4 transition:
<AddDeleteThemeTransition/>
<ContentThemeTransition/>
<ReorderThemeTransition/>
<EntranceThemeTransition IsStaggeringEnabled="False"/>
I don't know why, but ReorderThemeTransition should be triggered when an Item is moved, but it isn't.
Instead of move , try using this:
private void btnReorder_Click(object sender, RoutedEventArgs e)
{
var obj = myCollection[2];
myCollection.RemoveAt(2);
myCollection.Insert(3, obj);
}
it sort of does what you wanted, not fully, but a sequence of remove - add animation.
Hope that helps.
Related
My TextBox shows a property value of a class, which is changed based on the SelectedItem in ListBox. (So far, so good.) However, now I'd like to replace the value defined in Dictionary with a value which a user specified (The key is the SelectedItem in ListBox). This doesn't work, no matter what I do. It just throws an exception. Here is my full code:
MainWindow.xaml.cs
using ChangeTextBoxBasedOnListBox.Model;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
public partial class MainWindow : Window
{
ObservableCollection<string> oc;
ObservableCollection<Graph> graph;
Dictionary<string, Graph> dic = new Dictionary<string, Graph>();
ListBox lB;
public MainWindow()
{
InitializeComponent();
oc = new ObservableCollection<string>()
{
"Test_1",
"Test_2"
};
graph = new ObservableCollection<Graph>()
{
new Graph(10),
new Graph(100)
};
listBox.ItemsSource = oc;
foreach (var test in oc.Select((k, i) => new { kvp = k, index = i }))
{
dic.Add(test.kvp, graph[test.index]);
}
}
private void listBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
Graph dicValue = dic[(sender as ListBox).SelectedItem.ToString()];
textBox.Text = Convert.ToString(dicValue.Step);
}
private void textBox_TextChanged(object sender, TextChangedEventArgs e)
{
// This works, but this isn't what I want to do
if (dic.ContainsKey("Test_1"))
dic["Test_1"].Step = 1000;
// What I want to do is:
// (dic[(The current SelectedItem in ListBox)].Step = (The value user specified)
// This doesn't work ... throws an exception
//if (lB.SelectedItem != null)
//{
// if (dic.ContainsKey(lB.SelectedItem.ToString()))
// {
// dic[lB.SelectedItem.ToString()].Step = (sender as Graph).Step;
// }
//}
}
}
}
MainWindow.xaml
<Window x:Class="ChangeTextBoxBasedOnListBox.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:ChangeTextBoxBasedOnListBox"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<ListBox x:Name="listBox" HorizontalAlignment="Left" Height="238" Margin="88,82,0,0" VerticalAlignment="Top" Width="288" SelectionChanged="listBox_SelectionChanged"/>
<TextBox x:Name="textBox" HorizontalAlignment="Left" Height="23" Margin="523,82,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="120" TextChanged="textBox_TextChanged"/>
</Grid>
</Window>
Model/Graph.cs
namespace ChangeTextBoxBasedOnListBox.Model
{
public class Graph
{
public Graph(int step) { Step = step; }
public int Step { get; set; }
}
}
... I think I just need one more step, but I cannot find the way. Please help me. Thank you in advance.
Your approach is far too complicated. Actually, you do not need any event handlers at all.
Simply pass the Dictionary directly to the ListBox's ItemsSource and set DisplayMemberPath and SelectedValuePath to its Key and Value properties. Thus the ListBox displays the key strings and you can directly access a Graph instance via the SelectedValue property of the ListBox.
<StackPanel>
<ListBox x:Name="listBox" DisplayMemberPath="Key" SelectedValuePath="Value"/>
<TextBox Text="{Binding SelectedValue.Step, ElementName=listBox}"/>
</StackPanel>
and everything works automatically with this code behind:
public MainWindow()
{
InitializeComponent();
listBox.ItemsSource = new Dictionary<string, Graph>()
{
{ "Test_1", new Graph(10) },
{ "Test_2", new Graph(100) }
};
}
I'm using plain list in LongListSelector with ObservableCollection as ItemSource.
Initially all fine - scrolling to the end of list and back to the beginning has no troubles.
But then I call ObservableCollection.Move(2, 1). And now, when I scroll to the end and back to the top (so items are re-realized) I'm getting a partial list - random number of items from beginning of the list are absent (for example: 1,2,3...100 is changing to 1,3,42,43..100).
Am I doing something in a wrong way, or it is a bug in LongListSelector?
Here is a minimal example for my issue:
TestPage.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
using System.Collections.ObjectModel;
namespace myApp.Pages
{
public class Item
{
public string Text { get; set; }
}
public class ItemsViewModel
{
public ObservableCollection<Item> UngroupedItems { get; set; }
}
public partial class TestPage : PhoneApplicationPage
{
ItemsViewModel _vm;
public TestPage()
{
InitializeComponent();
_vm = new ItemsViewModel();
_vm.UngroupedItems = new ObservableCollection<Item>();
for (int i = 0; i < 100; i++)
{
_vm.UngroupedItems.Add(new Item() { Text = string.Format("- line {0}", _vm.UngroupedItems.Count + 1) });
}
DataContext = _vm;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
_vm.UngroupedItems.Move(2, 1);
}
}
}
TestPage.xaml
<phone:PhoneApplicationPage
x:Class="myApp.Pages.TestPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Portrait" Orientation="Portrait"
mc:Ignorable="d"
shell:SystemTray.IsVisible="True">
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<phone:LongListSelector ItemsSource="{Binding UngroupedItems}">
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Text}" Style="{StaticResource PhoneTextTitle2Style}"/>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
<Button VerticalAlignment="Bottom" Content="Test" Click="Button_Click"></Button>
</Grid>
</phone:PhoneApplicationPage>
I never like that function when Binding.
You can see it doesn't even update your long list selector until you scroll back to the portion that has changed, which is not ideal.
At this point, I think you have to safely do the move yourself unless someone else has a better idea.
private void Button_Click(object sender, RoutedEventArgs e)
{
SafeMove(2, 1);
}
private void SafeMove(int old_index, int new_index)
{
var saved_item = _vm.UngroupedItems[old_index];
_vm.UngroupedItems.RemoveAt(old_index);
_vm.UngroupedItems.Insert(new_index, saved_item);
}
Template Version
private void SafeMove<T>(ref ObservableCollection<T> collection, int old_index, int new_index)
{
var saved_item = collection[old_index];
collection.RemoveAt(old_index);
collection.Insert(new_index, saved_item);
}
Using the safe move, you will see that the long list selector is updated immediately.
This is my scenario: My DataTemplate of a ListView contains a TextBox and some buttons, one of the buttons is used to select and highlight all of the text in the TextBox. I can find many solutions for select and highlight text in TextBox from code behind, but none of them define the TextBox and the Button in DataTemplate. Anyone can help?
Thanks
You can do something like this below :
XAML :
<Window x:Class="SOWPF.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">
<Grid>
<ListView Width="200" Height="300" ItemsSource="{Binding FriendList}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBox Width="100" Margin="2" Text="{Binding Name}"></TextBox>
<Button Content="Select" Click="Button_Click"></Button>
<Button Content="Delete"></Button>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Grid>
Code Behind :
using System.Windows;
using System.Windows.Controls;
namespace SOWPF
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var friendViewModel = new FriendViewModel();
friendViewModel.AddFriends();
this.DataContext = friendViewModel;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var parent = (StackPanel)((sender as Button).Parent);
var children = parent.Children;
foreach(var child in children)
{
if (child.GetType().Equals(typeof(TextBox)))
{
var tb = child as TextBox;
tb.Focus();
tb.SelectAll();
break;
}
}
}
}
}
ViewModel :
using System.Collections.ObjectModel;
namespace SOWPF
{
public class FriendViewModel
{
public ObservableCollection<Friend> FriendList
{ get; set; }
public void AddFriends()
{
FriendList = new ObservableCollection<Friend>();
FriendList.Add(new Friend() { Name = "Arpan" });
FriendList.Add(new Friend() { Name = "Nrup" });
FriendList.Add(new Friend() { Name = "Deba" });
}
}
public class Friend
{
public string Name { get; set; }
}
}
Probably would be a good using an Attached property set on the button, and in the attached code using a code something like written by cvraman.
Using this way you absolutely avoid the code behind structure, and better way to using mvvm
Maybe it is a simple question, but I can’t find the answer.
I have three User controls that are different only with colour. There is code one of them:
<UserControl x:Class="SilverlightApplication14.NodePicture"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:SilverlightApplication14">
<UserControl.Resources>
<local:NodeViewModel x:Key="Children" />
</UserControl.Resources>
<Grid x:Name="LayoutRootNodePicture" Height="100" Width="100"
HorizontalAlignment="Center" DataContext="{Binding Source={StaticResource Children}, Path=Children}" >
<Canvas x:Name="ParentCanvas" Background="White" Width="100" Height="100" >
<Rectangle Fill="Yellow" Stroke="Blue" Width="100" Height="100" >
</Rectangle >
</Canvas>
<Image HorizontalAlignment="Center"
Source="add.png"
Stretch="Fill"
Width="16"
VerticalAlignment="Top"
Margin="0,0,2,2"
Height="16" MouseLeftButtonDown="Image_MouseLeftButtonDown">
</Image>
</Grid>
</UserControl>
How can I combine them into ObservableCollection Children?
public class NodeViewModel : INotifyPropertyChanged
{
public ObservableCollection<NodeViewModel> Children
{
get { return _children; }
set
{
_children = value;
NotifyChange("Children");
}
}
private void NotifyChange(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
And how can I use then elements of this controls collection?
Is there a simple (or a right way ) way of doing this?
As far as I understood you right, you have 3 user controls which have names something like NodePicture, GreenNodePicture and BlueNodePicture.
First of all, if the 3 controls differ to a very little degree, it would be better to have only one control which switches the color using some property value.
Let's suppose that your controls differ by the background color of the rectangle on the canvas. So I would change your control so:
<Grid x:Name="LayoutRootNodePicture" Height="100" Width="100"
HorizontalAlignment="Center">
<Canvas x:Name="ParentCanvas" Background="{Binding NodeColor}" Width="100" Height="100" >
</Canvas>
<Image HorizontalAlignment="Center"
Source="add.png"
Stretch="Fill"
Width="16"
VerticalAlignment="Top"
Margin="0,0,2,2"
Height="16" MouseLeftButtonDown="Image_MouseLeftButtonDown">
</Image>
</Grid>
I've removed the Resources section because the view shouldn't create new view model objects, it should use an existing DataContext. You can see that the background color of the rectangle is based on the property NodeColor of the view model.
Let's add this property to the view model:
public class NodeViewModel : INotifyPropertyChanged
{
private SolidColorBrush _nodeColor;
public SolidColorBrush NodeColor
{
get { return _nodeColor; }
set
{
_nodeColor = value;
NotifyChange("NodeColor");
}
}
//...
And now if you want to display 3 controls with different color you should create 3 view models with different properties. Here is the example of the red, blue and green viewmodels:
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
var redBrush = new SolidColorBrush(Color.FromArgb(255, 255, 0, 0));
var greenBrush = new SolidColorBrush(Color.FromArgb(255, 0, 255, 0));
var blueBrush = new SolidColorBrush(Color.FromArgb(255, 0, 0, 255));
this.DataContext = new MainViewModel
{
Nodes = new ObservableCollection<NodeViewModel>{
new NodeViewModel
{
NodeColor = redBrush,
Children = new ObservableCollection<NodeViewModel>{
new NodeViewModel { NodeColor = greenBrush, LeftOffset = 65, TopOffset = 10},
new NodeViewModel { NodeColor = greenBrush, LeftOffset = 55, TopOffset = 60}
}
}, //red
new NodeViewModel { NodeColor = greenBrush}, //green
new NodeViewModel { NodeColor = blueBrush} //blue
}
};
}
}
public class MainViewModel
{
public ObservableCollection<NodeViewModel> Nodes { get; set; }
}
View models are translated into the views using data templates:
<ListBox ItemsSource="{Binding Nodes}">
<ListBox.ItemTemplate>
<DataTemplate>
<local:NodePicture DataContext="{Binding}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I haven't used the Children property because I haven't understood where to use it. Maybe child nodes are displayed on the canvas. Anyway if it is important - you can provide additional information and I'll help with this.
Update:
The easiest way to draw child items on the canvas is to add the dependency property which updates the canvas when the collection is updated:
public partial class NodePicture : UserControl
{
public NodePicture()
{
InitializeComponent();
}
public IEnumerable<NodeViewModel> ChildViewModels
{
get { return (IEnumerable<NodeViewModel>)GetValue(ChildViewModelsProperty); }
set { SetValue(ChildViewModelsProperty, value); }
}
public static readonly DependencyProperty ChildViewModelsProperty =
DependencyProperty.Register("ChildViewModels", typeof(IEnumerable<NodeViewModel>), typeof(NodePicture),
new PropertyMetadata(null, (s, e) => ((NodePicture)s).UpdateCanvas()));
private void UpdateCanvas()
{
this.ParentCanvas.Children.Clear();
var items = this.ChildViewModels;
if(items == null)
return;
var controls = items.Select(item=>
{
var e = new Ellipse{Width = 20, Height = 20};
e.Fill = item.NodeColor;
//Or using the data binding
//BindingOperations.SetBinding(e, Ellipse.FillProperty, new Binding("NodeColor") { Source = item });
Canvas.SetLeft(e, item.LeftOffset);
Canvas.SetTop(e, item.TopOffset);
return e;
});
foreach(var c in controls)
this.ParentCanvas.Children.Add(c);
}
Where the TopOffset and LeftOffset are the properties of the NodeViewModel class.
After that you should set this property in the xaml code:
<DataTemplate>
<local:NodePicture DataContext="{Binding}" ChildViewModels="{Binding Children}" />
</DataTemplate>
It won't work with the ObservableColelction class because i didn't handle the CollectionChanged event.
Another approach - to use the ListBox control with the custom ItemsPanelTemplate and ListBoxItem ControlTemplate. But it is the much more complex solution.
I've been trying to figure out how to effectively trigger animations in a View when a property in the ViewModel updates, where the animation depends on the value of said property.
I've recreated my problem in a simple application with a single View and ViewModel. The goal here is to transition the color change of a rectangle by using a ColorAnimation. For reference, I've been using the MVVM Foundation package by Josh Smith.
The example project can be downloaded here.
To summarize, I want to animate the color transition in the View whenever the Color property changes.
MainWindow.xaml
<Window x:Class="MVVM.ColorAnimation.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ColorAnimation="clr-namespace:MVVM.ColorAnimation" Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<ColorAnimation:MainWindowViewModel />
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="40" />
</Grid.RowDefinitions>
<Rectangle Margin="10">
<Rectangle.Fill>
<SolidColorBrush Color="{Binding Color}"/>
</Rectangle.Fill>
</Rectangle>
<StackPanel Orientation="Horizontal" Grid.Row="1">
<Button Command="{Binding BlueCommand}" Width="100">Blue</Button>
<Button Command="{Binding GreenCommand}" Width="100">Green</Button>
</StackPanel>
</Grid>
</Window>
MainWindowViewModel.cs
namespace MVVM.ColorAnimation
{
using System.Windows.Input;
using System.Windows.Media;
using MVVM;
public class MainWindowViewModel : ObservableObject
{
private ICommand blueCommand;
private ICommand greenCommand;
public ICommand BlueCommand
{
get
{
return this.blueCommand ?? (this.blueCommand = new RelayCommand(this.TurnBlue));
}
}
private void TurnBlue()
{
this.Color = Colors.Blue;
}
public ICommand GreenCommand
{
get
{
return this.greenCommand ?? (this.greenCommand = new RelayCommand(this.TurnGreen));
}
}
private void TurnGreen()
{
this.Color = Colors.Green;
}
private Color color = Colors.Red;
public Color Color
{
get
{
return this.color;
}
set
{
this.color = value;
RaisePropertyChanged("Color");
}
}
}
}
Is there anyway from the View to trigger a ColorAnimation instead of an instant transition between the values? The way I'm currently doing this is another application is quite messy, in that I set the ViewModel through a ViewModel property, and then using a PropertyObserver to monitor value changes, then create the Animation and trigger it from the codebehind:
this.colorObserver = new PropertyObserver<Player>(value)
.RegisterHandler(n => n.Color, this.CreateColorAnimation);
In a situation where I'm dealing with many colors and many potential animations, this becomes quite a mess, and messes up the fact that I'm manually passing in the ViewModel to the View than simply binding the two through a ResourceDictionary. I suppose I could do this in the DataContextChanged event as well, but is there a better way?
If just for a few animations I would recommend using Visual States. Then you can use GoToAction behavior on the view to trigger different animations. If you are dealing with a lot of similar animations, creating your own behavior would be a better solution.
Update
I have created a very simple behaivor to give a Rectangle a little color animation. Here is the code.
public class ColorAnimationBehavior : TriggerAction<FrameworkElement>
{
#region Fill color
[Description("The background color of the rectangle")]
public Color FillColor
{
get { return (Color)GetValue(FillColorProperty); }
set { SetValue(FillColorProperty, value); }
}
public static readonly DependencyProperty FillColorProperty =
DependencyProperty.Register("FillColor", typeof(Color), typeof(ColorAnimationBehavior), null);
#endregion
protected override void Invoke(object parameter)
{
var rect = (Rectangle)AssociatedObject;
var sb = new Storyboard();
sb.Children.Add(CreateVisibilityAnimation(rect, new Duration(new TimeSpan(0, 0, 1)), FillColor));
sb.Begin();
}
private static ColorAnimationUsingKeyFrames CreateVisibilityAnimation(DependencyObject element, Duration duration, Color color)
{
var animation = new ColorAnimationUsingKeyFrames();
animation.KeyFrames.Add(new SplineColorKeyFrame { KeyTime = new TimeSpan(duration.TimeSpan.Ticks), Value = color });
Storyboard.SetTargetProperty(animation, new PropertyPath("(Shape.Fill).(SolidColorBrush.Color)"));
Storyboard.SetTarget(animation, element);
return animation;
}
}
In xaml, you simply attach this behavior like this,
<Rectangle x:Name="rectangle" Fill="Black" Margin="203,103,217,227" Stroke="Black">
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseLeftButtonDown">
<local:ColorAnimationBehavior FillColor="Red"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</Rectangle>
When you click the Rectangle, it should go from Black color to Red.
I used the code that Xin posted, and made a few very minor tweeks (code is below). The only 3 material differences:
I created the behavior to work on any UIElement, not just a rectangle
I used a PropertyChangedTrigger instead of an EventTrigger. That let's me Monitor the color property on the ViewModel instead of listening for click events.
I bound the FillColor to the Color property of the ViewModel.
To use this, you will need to download the Blend 4 SDK (it's free, and you only need it if you don't already have Expression Blend), and add references to System.Windows.Interactivity, and Microsoft.Expression.Interactions
Here's the code for the behavior class:
// complete code for the animation behavior
using System.Windows;
using System.Windows.Interactivity;
using System.Windows.Media;
using System.Windows.Media.Animation;
namespace ColorAnimationBehavior
{
public class ColorAnimationBehavior: TriggerAction<UIElement>
{
public Color FillColor
{
get { return (Color)GetValue(FillColorProperty); }
set { SetValue(FillColorProperty, value); }
}
public static readonly DependencyProperty FillColorProperty =
DependencyProperty.Register("FillColor", typeof(Color), typeof(ColorAnimationBehavior), null);
public Duration Duration
{
get { return (Duration)GetValue(DurationProperty); }
set { SetValue(DurationProperty, value); }
}
// Using a DependencyProperty as the backing store for Duration. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DurationProperty =
DependencyProperty.Register("Duration", typeof(Duration), typeof(ColorAnimationBehavior), null);
protected override void Invoke(object parameter)
{
var storyboard = new Storyboard();
storyboard.Children.Add(CreateColorAnimation(this.AssociatedObject, this.Duration, this.FillColor));
storyboard.Begin();
}
private static ColorAnimationUsingKeyFrames CreateColorAnimation(UIElement element, Duration duration, Color color)
{
var animation = new ColorAnimationUsingKeyFrames();
animation.KeyFrames.Add(new SplineColorKeyFrame() { KeyTime = duration.TimeSpan, Value = color });
Storyboard.SetTargetProperty(animation, new PropertyPath("(Shape.Fill).(SolidColorBrush.Color)"));
Storyboard.SetTarget(animation, element);
return animation;
}
}
}
Now here's the XAML that hooks it up to your rectangle:
<UserControl x:Class="MVVM.ColorAnimation.Silverlight.MainPage"
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:ColorAnimation="clr-namespace:MVVM.ColorAnimation"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
xmlns:ca="clr-namespace:ColorAnimationBehavior;assembly=ColorAnimationBehavior"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<UserControl.DataContext>
<ColorAnimation:MainWindowViewModel />
</UserControl.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="40" />
</Grid.RowDefinitions>
<Rectangle x:Name="rectangle" Margin="10" Stroke="Black" Fill="Red">
<i:Interaction.Triggers>
<ei:PropertyChangedTrigger Binding="{Binding Color}">
<ca:ColorAnimationBehavior FillColor="{Binding Color}" Duration="0:0:0.5" />
</ei:PropertyChangedTrigger>
</i:Interaction.Triggers>
</Rectangle>
<StackPanel Orientation="Horizontal" Grid.Row="1">
<Button Command="{Binding BlueCommand}" Width="100" Content="Blue"/>
<Button Command="{Binding GreenCommand}" Width="100" Content="Green"/>
</StackPanel>
</Grid>
</UserControl>
It was really Xin's idea -- I just cleaned it up a bit.