Say I have a shape. I've given it a Stroke and StrokeThickness and a StrokeDashArray to get the desired dashed outline. Then I animate the StrokeDashOffset via VisualStateManager to get the "Marching Ants" style animation to it. Everything works great...
Except I want the default of the shape to NOT have a StrokeDashArray and instead want to set that based on a VisualState in the VisualStateManager except unfortunately as we know I can only do a DoubleAnimation on a Property, and not a Double Collection Value like what StrokeDashArray is...
My question is, is there a clever way I could animate that value during runtime so the shape in an UnSelected State has a solid Stroke but via the 'VisualStateManager' (maybe) still supply the StrokeDashOffset on the Selected State to the same shape? Or am I better off having two separate shapes and toggling the visibility between them so that each have their own default values?
If it would help visualize with a picture or something let me know and I'll add more to the question.
One option would be to animate a double property and create a new DoubleCollection that is bound to the StrokeDashArray.
Xaml:
<UserControl x:Class="SilverlightApplication1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Name="UI">
<Grid DataContext="{Binding ElementName=UI}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState x:Name="StrokeDashArrayAnimation">
<Storyboard BeginTime="0">
<DoubleAnimation Duration="0:0:5"
From="0"
Storyboard.TargetName="UI"
Storyboard.TargetProperty="StrokeValue"
To="10" />
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Ellipse x:Name="lo"
Stroke="Red"
StrokeDashArray="{Binding StrokeArray}"
StrokeThickness="5" />
<Button Width="150"
Height="49"
Margin="29,65,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Click="Button_Click_1"
Content="Start" />
</Grid>
Code:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace SilverlightApplication1
{
public partial class MainPage : UserControl
{
public MainPage()
{
InitializeComponent();
}
public double StrokeValue
{
get { return (double)GetValue(StrokeValueProperty); }
set { SetValue(StrokeValueProperty, value); }
}
public static readonly DependencyProperty StrokeValueProperty =
DependencyProperty.Register("StrokeValue", typeof(double), typeof(MainPage),
new PropertyMetadata(0.0, OnStrokeValueChanged));
private static void OnStrokeValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var page = d as MainPage;
if (page != null) page.StrokeArray = new DoubleCollection { (double)e.NewValue, 1 };
}
public DoubleCollection StrokeArray
{
get { return (DoubleCollection)GetValue(StrokeArrayProperty); }
set { SetValue(StrokeArrayProperty, value); }
}
public static readonly DependencyProperty StrokeArrayProperty =
DependencyProperty.Register("StrokeArray", typeof(DoubleCollection), typeof(MainPage)
, new PropertyMetadata(new DoubleCollection { 0, 1 }));
private void Button_Click_1(object sender, RoutedEventArgs e)
{
VisualStateManager.GoToState(this, "StrokeDashArrayAnimation", false);
}
}
}
Related
I want to have a slider that returns to 0 when the user stops dragging.
So far I have this:
<Window x:Class="CenteredSliderTest.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">
<DockPanel>
<!--Value="{Binding ZSpeed}"-->
<Slider DockPanel.Dock="Left"
x:Name="ZSlider"
Minimum="-100" Maximum="100"
SelectionStart="-20" SelectionEnd="20"
Orientation="Vertical"
TickFrequency="10"
TickPlacement="TopLeft"
AutoToolTipPlacement="TopLeft"
AutoToolTipPrecision="2"
LargeChange="10"
SmallChange="1"
IsDirectionReversed="True"
Focusable="False"
>
<Slider.Triggers>
<EventTrigger RoutedEvent="LostMouseCapture" SourceName="ZSlider">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="ZSlider"
Storyboard.TargetProperty="Value"
From="{Binding Value, ElementName=ZSlider}"
To="0.0"
Duration="0:0:1.5"
FillBehavior="Stop"
/>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Slider.Triggers>
</Slider>
<TextBlock Text="{Binding ZSpeed}" />
</DockPanel>
</Window>
This works as long as I don't bind the slider value to my DependencyProperty ZSpeed.
As soon as I do this, the slider jumps back to the original value and at the second attempt the slider can't be dragged anymore.
So what can I do (preferable in xaml) in order to get the animation modify not only the slider but also the ZSpeed property?
EDIT
Code in MainWindow:
public partial class MainWindow : Window
{
public double ZSpeed
{
get { return (double)GetValue(ZSpeedProperty); }
set { SetValue(ZSpeedProperty, value); }
}
// Using a DependencyProperty as the backing store for ZSpeed. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ZSpeedProperty =
DependencyProperty.Register("ZSpeed", typeof(double), typeof(MainWindow), new UIPropertyMetadata(0.0));
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
Binding binding = new Binding("Value") { Source = ZSlider };
this.SetBinding(ZSpeedProperty, binding);
}
}
You might reverse the direction of the binding. Instead of binding the Slider's Value to ZSpeed you could bind ZSpeed to Value. This would also be the "natural" binding direction if the Slider is meant to change ZSpeed, but ZSpeed won't change otherwise.
EDIT: If ZSpeed is a dependency property in some data class MyData you could create a binding in code like this:
MyData dataObject = ...
Binding binding = new Binding("Value") { Source = ZSlider };
dataObject.SetBinding(MyData.ZSpeedProperty, binding);
SECOND EDIT: Picking up Daniels suggestion, you might animate ZSpeed instead of the Slider's Value. Bind the Value to ZSpeed as before, remove the EventTrigger and add an event handler for LostMouseCapture:
<Slider x:Name="ZSlider" ...
Value="{Binding ZSpeed}"
LostMouseCapture="ZSlider_LostMouseCapture"/>
Code behind:
private void ZSlider_LostMouseCapture(object sender, MouseEventArgs e)
{
DoubleAnimation animation = new DoubleAnimation
{
From = ZSpeed,
To = 0d,
Duration = TimeSpan.FromSeconds(1.5 * Math.Abs(ZSpeed) / 100d),
FillBehavior = FillBehavior.Stop
};
ZSpeed = 0d;
BeginAnimation(ZSpeedProperty, animation);
}
You should be using FillBehavior.HoldEnd.
Edit: That apparently doesn't work. You could set the ZSpeed value to 0 manually in the StoryBoard.Completed event.
I'm quite new in WPF-Animations so apologize if it's too easy, but I can't find any answer (nor my question). So:
I have very simple animation - some canvas is rotating from angle -45 degrees to 45 degrees. All animation is made in XAML (got some issues with code-behind animation). I would like to bind function when value equals 0 (e.g. make some noise then). How can I approach this?
Thank you for all hints.
I have two options to solve this problem. One is intrusive but gives you more control over the actual value, another is not intrusive but gives you only indirect control over the value. I'll give the sample code with both options at the end of the answer.
Non intrusive solution
Subscribe to the CurrentTimeInvalidated event on your DoubleAnimation object. If you know the animation function and its duration you can approximately say when the animation value is close to your event. For say, animation duration is 500 ms, and the animation function is linear. Then you can say, that at 250ms you are halfway through.
Intrusive solution
Remember: DoubleAnimation (like any other animation) is just a class and you are welcome to inherit it and override any virtual member. In case of DoubleAnimation of particular interest is GetCurrentValueCore() method. And of course you can define any events or dependency properties on this new class. Now you see where it's all going. Inherit DoubleAnimation, override GetCurrentValueCore(), define ValueChanged event, and fire it on every call to GetCurrentValueCore().
Code example
MainWindow.xaml
<Window x:Class="WpfPlayground.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"
xmlns:l="clr-namespace:WpfPlayground">
<Grid>
<Grid.Triggers>
<EventTrigger RoutedEvent="Loaded">
<BeginStoryboard>
<Storyboard Duration="00:00:00.500" Storyboard.TargetName="rectangle" RepeatBehavior="Forever">
<l:DoubleAnimationWithCallback From="0"
To="180" Duration="00:00:00.500"
Storyboard.TargetProperty="(FrameworkElement.LayoutTransform).(RotateTransform.Angle)"
Callback="{Binding AnimationCallback, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type l:MainWindow}}}"
CurrentTimeInvalidated="OnCurrentTimeInvalidated" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Grid.Triggers>
<!--We animate this rectangle-->
<Rectangle x:Name="rectangle" Width="50" Height="50" Fill="Green">
<Rectangle.LayoutTransform>
<RotateTransform />
</Rectangle.LayoutTransform>
</Rectangle>
<!--Debug information-->
<TextBlock x:Name="tbTime" HorizontalAlignment="Center" VerticalAlignment="Top"/>
<TextBlock x:Name="tbAngle" HorizontalAlignment="Center" VerticalAlignment="Bottom"/>
</Grid>
</Window>
MainWindow.xaml.cs
using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Media.Animation;
using System.Globalization;
namespace WpfPlayground
{
public partial class MainWindow : Window
{
public Func<double, double> AnimationCallback { get { return AnimationCallbackImpl; } }
public MainWindow()
{
InitializeComponent();
}
private double AnimationCallbackImpl(double value)
{
tbAngle.Text = value.ToString(CultureInfo.CurrentCulture);
return value;
}
private void OnCurrentTimeInvalidated(object sender, EventArgs e)
{
tbTime.Text = ((AnimationClock)sender).CurrentTime.ToString();
}
}
}
DoubleAnimationWithCallback.cs
using System;
using System.Windows;
using System.Windows.Media.Animation;
namespace WpfPlayground
{
public class DoubleAnimationWithCallback : DoubleAnimation
{
// Cache Callback DP, to avoid performance hit.
private Func<double, double> _callback;
// reference to frozen instance. See comments below for explanation.
private DoubleAnimationWithCallback _coreInstance;
public Func<double, double> Callback
{
get { return (Func<double, double>)GetValue(CallbackProperty); }
set { SetValue(CallbackProperty, value); }
}
public static readonly DependencyProperty CallbackProperty =
DependencyProperty.Register("Callback", typeof(Func<double, double>), typeof(DoubleAnimationWithCallback), new PropertyMetadata(null, OnCallbackChanged));
private static void OnCallbackChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
var dawc = o as DoubleAnimationWithCallback;
if (dawc != null)
{
dawc.UpdateCallback(e.NewValue as Func<double, double>);
}
}
private void UpdateCallback(Func<double, double> callback)
{
_callback = callback;
if (_coreInstance != null)
{
_coreInstance._callback = _callback;
}
}
protected override Freezable CreateInstanceCore()
{
if (_coreInstance == null)
{
// When callback changes we update corresponding callback on
// the frozen object too.
_coreInstance = new DoubleAnimationWithCallback()
{
Callback = Callback
};
}
return _coreInstance;
}
protected override double GetCurrentValueCore(double defaultOriginValue, double defaultDestinationValue, AnimationClock animationClock)
{
var value = base.GetCurrentValueCore(defaultOriginValue, defaultDestinationValue, animationClock);
if (_callback != null)
{
return _callback(value);
}
return value;
}
}
}
There is one caveat though: animation pipeline works with Freezable objects, so you'll have to override CreateInstanceCore() method and return proper instance. Furthermore, if you change Callback dependency property on the real object you'll have to also update the frozen one. It is not quite welcome practice and that's why I call it intrusive. Be very careful with this code and test it throughly. It just shows a possible direction and is not the final destination.
Hope this helps
I am animating a border resize in Silverlight however I also need to gradually remove the margin around it (currently 50). Blend doesn't seem to generate a tween for margin change - it just jumps from 50 to 0 in one go. Is there a way to achieve this?
The problem is that a Margin is really of type "System.Windows.Thickness" which is NOT a dependency object, thus Left, Top, Right, and Bottom are NOT Dependency Properties and thus cannot be animated using DoubleAnimation (which allows for tweening).
What is used to animate the Margin is an ObjectAnimation which does not tween. This is why you see the margin jump from its original location to its new location. As another common example, the same happens when you try to animate the Visibility property between Visible and Collapsed.
You would either need to do timer based animation in order to animate margin or implement your own Animation type for Thickness objects.
Ben Lemmon gives an elegant solution: http://blogs.msdn.com/blemmon/archive/2009/03/18/animating-margins-in-silverlight.aspx
Here is an updated version that allows you to animate from within XAML
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace NiceCards.Animations
{
public class ThicknessAnimationX
{
public static readonly DependencyProperty ElementProperty = DependencyProperty.RegisterAttached("Element", typeof(DependencyObject), typeof(DoubleAnimation), new PropertyMetadata(new PropertyChangedCallback(OnElementPropertyChanged)));
// The time along the animation from 0-1
public static DependencyProperty TimeProperty = DependencyProperty.RegisterAttached("Time", typeof(double), typeof(DoubleAnimation), new PropertyMetadata(OnTimeChanged));
// The object being animated
public static DependencyProperty TargetProperty = DependencyProperty.RegisterAttached("Target", typeof(DependencyObject), typeof(ThicknessAnimationX), null);
public static DependencyProperty TargetPropertyProperty = DependencyProperty.RegisterAttached("TargetProperty", typeof(DependencyProperty), typeof(DependencyObject), null);
public static readonly DependencyProperty FromProperty = DependencyProperty.RegisterAttached("From", typeof(Thickness), typeof(DoubleAnimation), null);
public static readonly DependencyProperty ToProperty = DependencyProperty.RegisterAttached("To", typeof(Thickness), typeof(DoubleAnimation), null);
public static void SetElement(DependencyObject o, DependencyObject value)
{
o.SetValue(ElementProperty, value);
}
public static DependencyObject GetElement(DependencyObject o)
{
return (DependencyObject)o.GetValue(ElementProperty);
}
private static void OnElementPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue != null)
{
DoubleAnimation doubleAnimation = (DoubleAnimation)d;
doubleAnimation.SetValue(TargetProperty, e.NewValue);
doubleAnimation.From = 0;
doubleAnimation.To = 1;
doubleAnimation.SetValue(TargetPropertyProperty, FrameworkElement.MarginProperty);
Storyboard.SetTargetProperty(doubleAnimation, new PropertyPath("(ThicknessAnimationX.Time)"));
Storyboard.SetTarget(doubleAnimation, doubleAnimation);
}
}
private static void OnTimeChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
DoubleAnimation animation = (DoubleAnimation)sender;
double time = GetTime(animation);
Thickness from = (Thickness)sender.GetValue(FromProperty);
Thickness to = (Thickness)sender.GetValue(ToProperty);
DependencyProperty targetProperty = (DependencyProperty)sender.GetValue(TargetPropertyProperty);
DependencyObject target = (DependencyObject)sender.GetValue(TargetProperty);
target.SetValue(targetProperty, new Thickness((to.Left - from.Left) * time + from.Left,
(to.Top - from.Top) * time + from.Top,
(to.Right - from.Right) * time + from.Right,
(to.Bottom - from.Bottom) * time + from.Bottom));
}
public static double GetTime(DoubleAnimation animation)
{
return (double)animation.GetValue(TimeProperty);
}
public static void SetTime(DoubleAnimation animation, double value)
{
animation.SetValue(TimeProperty, value);
}
public static Thickness GetFrom(DoubleAnimation animation)
{
return (Thickness)animation.GetValue(FromProperty);
}
public static void SetFrom(DoubleAnimation animation, Thickness value)
{
animation.SetValue(FromProperty, value);
}
public static Thickness GetTo(DoubleAnimation animation)
{
return (Thickness)animation.GetValue(ToProperty);
}
public static void SetTo(DoubleAnimation animation, Thickness value)
{
animation.SetValue(ToProperty, value);
}
}
}
And then you can do this in XAML
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="Positions">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="0:0:0.2"/>
</VisualStateGroup.Transitions>
<VisualState x:Name="Left">
<Storyboard>
<DoubleAnimation Duration="0:0:0.3" NiceCards:ThicknessAnimationX.To="0,0,0,0" NiceCards:ThicknessAnimationX.Element="{Binding ElementName=rectangle1}" Storyboard.TargetName="rectangle1" Storyboard.TargetProperty="Opacity"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Right">
<Storyboard>
<DoubleAnimation Duration="0:0:0.3" NiceCards:ThicknessAnimationX.To="0,200,0,0" NiceCards:ThicknessAnimationX.Element="{Binding ElementName=rectangle1}" Storyboard.TargetName="rectangle1" Storyboard.TargetProperty="Opacity"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Rectangle Height="100" HorizontalAlignment="Left" Margin="23,25,0,0" x:Name="rectangle1" Stroke="Black" StrokeThickness="1" VerticalAlignment="Top" Width="200" Fill="#FF1BAA00"/>
Note that if you don't set a Target property to a DoubleAnimation in XAML, you won't be able to display the control/page in Blend. To fix this, just add a fake target property (in the code above I added the opacity property which is a double value), And it will be overriden at runtime anyway
I have a ListView that is set up with a MinHeight and a MaxHeight. The final height is determined by the number of items inside the list.
At the moment, when a list is added to the ItemsSource property of the ListView, the height jumps to the final height. Is there a way to animate this change in height, so that it's smooth?
Here's an example of something that does what you want (as I understand it). I'll call this "quick and dirty" and don't claim to have put a whole lot of thought into it.
public class CustomListView : ListView
{
public bool IsAttached
{
get { return (bool)GetValue(IsAttachedProperty); }
set { SetValue(IsAttachedProperty, value); }
}
// Using a DependencyProperty as the backing store for IsAttached.
// This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsAttachedProperty =
DependencyProperty.Register("IsAttached",
typeof(bool),
typeof(CustomListView),
new UIPropertyMetadata(false));
}
public class ViewModel : INotifyPropertyChanged
{
public void PopulateItems()
{
Items = new List<string>();
for (var i = 0; i < 200; i++ )
{
Items.Add("The quick brown fox jumps over the lazy dog.");
}
InvokePropertyChanged(new PropertyChangedEventArgs("Items"));
IsAttached = true;
InvokePropertyChanged(new PropertyChangedEventArgs("IsAttached"));
}
public List<string> Items { get; private set; }
public bool IsAttached { get; private set; }
public event PropertyChangedEventHandler PropertyChanged;
private void InvokePropertyChanged(PropertyChangedEventArgs e)
{
var changed = PropertyChanged;
if (changed != null)
{
changed(this, e);
}
}
}
<Window x:Class="AnimateHeight.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:AnimateHeight"
Title="Window1" Height="300" Width="300">
<StackPanel>
<Button Width="100" Content="Add Items" Click="OnClickAddItems"/>
<local:CustomListView x:Name="VariableListView" ItemsSource="{Binding Items}" IsAttached="{Binding IsAttached}" >
<local:CustomListView.Style>
<Style TargetType="{x:Type local:CustomListView}">
<Setter Property="MinHeight" Value="50" />
<Setter Property="MaxHeight" Value="50" />
<Style.Triggers>
<Trigger Property="IsAttached" Value="true">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="(ListView.MaxHeight)"
To="150"
Duration="0:0:5"/>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
</Trigger>
</Style.Triggers>
</Style>
</local:CustomListView.Style>
</local:CustomListView>
</StackPanel>
</Window>
/// <summary>
/// Interaction logic for Window1.xaml
/// </summary>
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
DataContext = new ViewModel();
}
private void OnClickAddItems(object sender, RoutedEventArgs e)
{
((ViewModel)DataContext).PopulateItems();
}
}
UPDATE: You should be able to copy this into .cs and .xaml files and run it as an example application. To summarize what I'm doing: Set the MaxHeight property to something artificially low, in my case I just set it to the same value as the MinHeight. Then you can create a storyboard that animates the MaxHeight to its real value, which gives you the smooth transition effect. The trick is indicating when to start the animation, I use a dependency property in a subclassed ListView just because that seemed to be the easiest option to implement in a hurry. I just have to bind the dependency property to a value in my ViewModel and I can trigger the animation by changing that value (since I don't know of an easy way to trigger an animation based on a change to a ListView ItemsSource off the top of my head).
I have an ItemsControl with Items being added through databinding to an observable collection. Each item has a data template that defines its look.
I am trying to figure out if it is possible to apply/trigger animations to/on each of the Items in the ItemsControl when the VisualStateManager puts the ItemsControl in a particular state.
Below is a picture - when the items control goes into the closed state - I want the items in the items control to shrink as well as hide the text and have a number become visible. Is this possible using VSM or do I need to attach animations to each item when they are created and then manually kick them off when I want them to change visual state.
alt text http://www.edefine.com/images/misc/drawing1.jpg
This is possible using a ObjectAnimationUsingKeyFrames, however it's stupidly hard to do, will cause you to rip your hair out, crash your visual studio regularly and gives you very little over doing it the simple way.
The simple way:
public class TestSwapContentControl : ContentControl
{
object StoredOriginalContent;
public object FullContent
{
get { return (object)GetValue(FullContentProperty); }
set { SetValue(FullContentProperty, value); }
}
// Using a DependencyProperty as the backing store for FullContent. This enables animation, styling, binding, etc...
public static readonly DependencyProperty FullContentProperty =
DependencyProperty.Register(
"FullContent"
, typeof(object)
, typeof(TestSwapContentControl)
, null);
public void SwitchToFullContent()
{
if (FullContent != null)
{
StoredOriginalContent = Content;
Content = FullContent;
}
}
public void SwitchToNormalContent()
{
if(StoredOriginalContent != null)
{
Content = StoredOriginalContent;
}
}
}
Then the xaml to use:
<local:TestSwapContentControl x:Name="mySwitch">
<Rectangle Height="50" Width="100" Fill="Black" />
<local:TestSwapContentControl.FullContent>
<StackPanel>
<TextBlock>1</TextBlock>
<TextBlock>2</TextBlock>
<TextBlock>3</TextBlock>
<TextBlock>4</TextBlock>
<Rectangle Height="50" Width="100" Fill="Red" />
</StackPanel>
</local:TestSwapContentControl.FullContent>
</local:TestSwapContentControl>
With the following cs in the page:
private void Button_Click(object sender, RoutedEventArgs e)
{
if (myTempBool)
{
mySwitch.SwitchToFullContent();
myTempBool = false;
}
else
{
mySwitch.SwitchToNormalContent();
myTempBool = true;
}
}
Now, if you really need to make the control completely extensible by other developers, you'll need to use visualstatemenager, but it's a real bitch. If you don't know how to set up visual state manager and states through generic.xaml, here's a how-to guide:
http://scorbs.com/2008/06/11/parts-states-model-with-visualstatemanager-part-1-of/
Here's a working example but it's not perfect as I can't seem to set the content of the ContentPresenter directly.
using System.Windows;
using System.Windows.Controls;
namespace SilverlightTestApplication
{
[TemplateVisualState(Name="Normal", GroupName="SizeStates")]
[TemplateVisualState(Name="Expanded", GroupName="SizeStates")]
public class TestVSMControl : ContentControl
{
public object SmallContent
{
get { return (object)GetValue(SmallContentProperty); }
set { SetValue(SmallContentProperty, value); }
}
// Using a DependencyProperty as the backing store for SmallContent. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SmallContentProperty =
DependencyProperty.Register("SmallContent", typeof(object), typeof(TestVSMControl), null);
public object LargeContent
{
get { return (object)GetValue(LargeContentProperty); }
set { SetValue(LargeContentProperty, value); }
}
// Using a DependencyProperty as the backing store for LargeContent. This enables animation, styling, binding, etc...
public static readonly DependencyProperty LargeContentProperty =
DependencyProperty.Register("LargeContent", typeof(object), typeof(TestVSMControl), null);
public bool Pressed
{
get { return (bool)GetValue(PressedProperty); }
set { SetValue(PressedProperty, value); }
}
// Using a DependencyProperty as the backing store for Pressed. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PressedProperty =
DependencyProperty.Register("Pressed", typeof(bool), typeof(TestVSMControl),
new PropertyMetadata(new PropertyChangedCallback(PressedPropertyChanged)));
static void PressedPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var me = sender as TestVSMControl;
me.ChangeState();
}
public TestVSMControl()
{
DefaultStyleKey = typeof(TestVSMControl);
}
void ChangeState()
{
GoToState(true);
}
private void GoToState(bool useTransitions)
{
if (Pressed)
{
VisualStateManager.GoToState(this, "Normal", useTransitions);
}
else
{
VisualStateManager.GoToState(this, "Expanded", useTransitions);
}
}
}
}
In your generic.xaml (include xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows"):
<Style TargetType="local:TestVSMControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:TestVSMControl">
<StackPanel>
<vsm:VisualStateManager.VisualStateGroups>
<vsm:VisualStateGroup x:Name="SizeStates">
<vsm:VisualState x:Name="Normal">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Content" Storyboard.TargetName="myContentPresenter" BeginTime="00:00:00" Duration="00:00:00.0010000" >
<ObjectAnimationUsingKeyFrames.KeyFrames>
<DiscreteObjectKeyFrame KeyTime="0:0:0">
<DiscreteObjectKeyFrame.Value>
<StackPanel>
<TextBlock>Rararasputin</TextBlock>
<Button Content="{TemplateBinding SmallContent}" />
</StackPanel>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames.KeyFrames>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>
<vsm:VisualState x:Name="Expanded">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Content" Storyboard.TargetName="myContentPresenter" BeginTime="00:00:00" Duration="00:00:00.0010000" >
<ObjectAnimationUsingKeyFrames.KeyFrames>
<DiscreteObjectKeyFrame KeyTime="0:0:0" >
<DiscreteObjectKeyFrame.Value>
<StackPanel>
<TextBlock>Other one</TextBlock>
<Button Content="{TemplateBinding LargeContent}" />
</StackPanel>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames.KeyFrames>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>
</vsm:VisualStateGroup>
</vsm:VisualStateManager.VisualStateGroups>
<ContentPresenter x:Name="myContentPresenter" />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And how to use in your page:
<local:TestVSMControl x:Name="myVSMControl" Height="200">
<local:TestVSMControl.SmallContent>
<Rectangle Height="50" Width="100" Fill="Red" />
</local:TestVSMControl.SmallContent>
<local:TestVSMControl.LargeContent>
<Rectangle Height="50" Width="100" Fill="Green" />
</local:TestVSMControl.LargeContent>
</local:TestVSMControl>
<Button Content="Swap" x:Name="VSMButton" Click="VSMButton_Click" />
with the following in your page:
private void VSMButton_Click(object sender, RoutedEventArgs e)
{
myVSMControl.Pressed = !myVSMControl.Pressed;
}
If you are talking about the Silverlight Visual State Manager, I'm afraid this is not possible.
VisualStates contain only Storyboard objects, which in turn contain animations. As far as I know, you cannot change a template with it.
I'm not sure about the capabilities of the WPF VisualStateManager.