I get the same dimensions after I do a scale transfrom on the rendered transfrom property. Here is the code:
shape.Width = 100; shape.Height=100;
shape.RenderTransform = new ScaleTransform(.5, .5);
textBox.text = shape.ActualWidth + " " + shape.ActualHeight;
I've tried getting the rendered geometry bounds' width and height, and still the same. I also tried LayoutTransfrom, still didn't work. What am I doing wrong, any help would be appreciated.
Try setting LayoutTransform instead of RenderTransform.
The main difference between LayoutTransform and RenderTransform is when the transform is applied. When using Render Transform the full size of the object is used during the layout process, and only after that has finished the object is transformed. Other elements are ot influenced by this. With LayoutTransform the object is transformed during the layout phase already, which means that for all intended purposes the bounding box of the object changes. All other elements adapt to this change as well.
Neither will change ActualWidth or ActualHeight, as this would influence the inner layout of the object that's being transformed rendereing the transform operation useless.
You can see this in the following example I whipped up for you:
<StackPanel>
<Border Name="OuterBorder1" HorizontalAlignment="Left" VerticalAlignment="Top" Background="Yellow">
<Border Width="100" Height="100" Background="Green" Name="Border1" />
</Border>
<Button Content="Normal" Click="Normal_Click"/>
<Button Content="Layout Transform" Click="Layout_Click"/>
<Button Content="Render Transform" Click="Render_Click"/>
<TextBlock Text="{Binding Path=ActualWidth, ElementName=Border1}"></TextBlock>
<TextBlock Text="{Binding Path=ActualWidth, ElementName=OuterBorder1}"></TextBlock>
</StackPanel>
When you press the buttons, the following actions are attached:
private void Normal_Click(object sender, RoutedEventArgs e)
{
Border1.RenderTransform = new ScaleTransform(1, 1);
Border1.LayoutTransform = new ScaleTransform(1, 1);
}
private void Layout_Click(object sender, RoutedEventArgs e)
{
Border1.RenderTransform = new ScaleTransform(1, 1);
Border1.LayoutTransform = new ScaleTransform(0.5, 0.5);
}
private void Render_Click(object sender, RoutedEventArgs e)
{
Border1.LayoutTransform = new ScaleTransform(1, 1);
Border1.RenderTransform = new ScaleTransform(0.5, 0.5);
}
The OuterBorder1 has an override on alignment to make sure it fits tight around the item.
As you can see, with RenderTransform neither size changes. With LayoutTransform the transformed element's width remains the same, but the container surrounding it is influenced.
Try and wait till the tranform finishes, the way we can know it is by using a fast 1 miilisecond DoubleAnimation and its Completed event. In this event handler check for new dimesions.
<Button x:Name="MyButton" Content="A Button"
RenderTransformOrigin="0.5,0.5">
<Button.RenderTransform>
<RotateTransform x:Name="AnimatedRotateTransform" Angle="0" />
</Button.RenderTransform>
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="AnimatedRotateTransform"
Storyboard.TargetProperty="Angle"
To="360" Duration="0:0:0.1" FillBehavior="Stop"
Completed="RenderTransform_Completed" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>
Code behind ...
private void RenderTransform_Completed(object sender, EventArgs e)
{
textBox.text = AnimatedRotateTransform.Angle;
}
Related
I found a good explanation here on SO of how to bind the Duration property of a ColorAnimation to the Value property of a Slider. One uses a converter to convert the Double value from the slider to a Duration, and a Binding to have that set the Duration of the ColorAnimation. Here, abbreviated, is how that works:
<Window.Resources>
<local:DoubleToDurationConverter x:Key="DoubleToDurationConverter" />
</Window.Resources>
<Slider x:Name="slider" />
<Button Content="Click me for an animation">
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<ColorAnimation To="Green"
Storyboard.TargetProperty="(Button.Background).(SolidColorBrush.Color)"
FillBehavior="Stop"
Duration="{Binding ElementName=slider,
Path=Value,
Mode=OneWay,
Converter={StaticResource DoubleToDurationConverter}}" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>
I tried that and it worked fine for me. But what I want to do is bind the Duration to a dependency property called FadeTime I've added to my custom control. So, in that control's ControlTemplate I have this:
<ControlTemplate.Triggers>
<Trigger Property="IsLit" Value="true">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="glow"
Storyboard.TargetProperty="Opacity"
To="1"
Duration="{Binding FadeTime, Mode=OneWay,
RelativeSource={RelativeSource TemplatedParent}}"/>
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
</Trigger>
</ControlTemplate.Triggers>
This compiles, but gives me an error message at run-time:
InvalidOperationException: Cannot freeze this Storyboard timeline tree
for use across threads.
How can I bind my DoubleAnimation's Duration to a dependency variable in a custom control's ControlTemplate?
Thanks!
UPDATE
Data-binding is actually gross overkill for what I want to do. Real data-binding would allow for the property's value to change at run-time. All I really want is a way for the developer who is using my custom control to be able to set the Duration of the DoubleAnimation at design time, without having to edit the ControlTemplate. It's okay if the value the developer chooses never changes at run time.
Instead of defining the animation in your XAML markup, you could define it programmatically in the PropertyChangedCallback for the IsLit property.
You could simply define another property that lets the consumer of the control specify the duration of the animation.
Here is an example for you.
Control:
public class MyCustomControl : Control
{
private UIElement glow;
public static readonly DependencyProperty DurationProperty =
DependencyProperty.Register("Duration", typeof(TimeSpan),
typeof(MyCustomControl), new PropertyMetadata(TimeSpan.FromSeconds(1)));
public TimeSpan Duration
{
get { return (TimeSpan)GetValue(DurationProperty); }
set { SetValue(DurationProperty, value); }
}
public static readonly DependencyProperty IsLitProperty =
DependencyProperty.Register("IsLit", typeof(bool),
typeof(MyCustomControl), new PropertyMetadata(false, new PropertyChangedCallback(OnIsLitChanged)));
public bool IsLit
{
get { return (bool)GetValue(IsLitProperty); }
set { SetValue(IsLitProperty, value); }
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
glow = Template.FindName("glow", this) as UIElement;
if (glow != null && IsLit)
Animate(glow);
}
private static void OnIsLitChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
bool newValue = (bool)e.NewValue;
if(newValue)
{
MyCustomControl c = d as MyCustomControl;
if(c != null && c.glow != null)
{
c.Animate(c.glow);
}
}
}
private void Animate(UIElement glow)
{
DoubleAnimation animation = new DoubleAnimation();
animation.To = 1;
animation.Duration = Duration;
glow.BeginAnimation(OpacityProperty, animation);
}
}
Template:
<ControlTemplate x:Key="ct" TargetType="local:MyCustomControl">
<Border x:Name="glow" Width="100" Height="100" Background="Red" Opacity="0.1">
</Border>
</ControlTemplate>
Usage:
<local:MyCustomControl Template="{StaticResource ct}" Duration="0:0:5" IsLit="True" />
Basically, you can't use normal bindings inside the storyboard of a control template. Since you just want a way for developers to change the value, one of the following options might work for you:
(1) Use StaticResource: Place a duration object somewhere outside the control template, where it's easier to change for developers. However, it needs to be somewhere statically accessible to the control template, since DynamicResource won't work in this place.
<Duration x:Key="MyCustomDuration">0:0:1</Duration>
... then later
Duration="{StaticResource MyCustomDuration}"
(2) Use a static code behind field with x:Static:
public static class SettingsClass
{
public static Duration MyCustomDuration = new Duration(new TimeSpan(0, 0, 1));
}
and use:
Duration="{x:Static local:SettingsClass.MyCustomDuration}"
I have a bomb(element) in the canvas that is moving with doublanimation(vertical only) and I want to see if it hits an airplane(also an element on the canvas).
I wrote this code:
private bool HitCheck()
{
AirPlaneRect.Location = airplane.PointToScreen(new Point(Canvas.GetLeft(airplane), Canvas.GetTop(airplane)));
Rect BombPos = new Rect(bombPoint.X, Canvas.GetTop(this),this.bombImage.Height, this.bombImage.Width);
if (BombPos.IntersectsWith(AirPlaneRect))
{
return true;
}
return false;
}
but for some reason the bomb location is always the starting one.
I'm using a timer that call this method every half a second.
Instead of using a Timer to check periodic if they intersects, it is more accurate to check it when the DoubleAnimation is changed. When that happens, CurrentTimeInvalidated is raised.
I created a little sample to demonstrate what I mean. Here is the XAML:
<Canvas>
<!--be shure to assign Top and Left of all objects-->
<Rectangle Canvas.Left="0" Canvas.Top="0" Name="bomb"
Width="20" Height="100" Fill="gray"/>
<Rectangle Canvas.Left="0" Canvas.Top="200" Name="airPlane"
Width="300" Height="50" Fill="lightblue"/>
<Canvas.Triggers>
<!--Run the animation at startup-->
<EventTrigger RoutedEvent="Loaded">
<BeginStoryboard >
<Storyboard >
<DoubleAnimation Name="doubleAnimation"
Storyboard.TargetName="bomb"
Storyboard.TargetProperty="(Canvas.Top)"
From="0" To="400" Duration="0:0:5"
CurrentTimeInvalidated="CurrentTimeInvalidated"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Canvas.Triggers>
</Canvas>
And the code behind:
private void CurrentTimeInvalidated(object sender, EventArgs e)
{
if (HitTest(airPlane, bomb))
{
MessageBox.Show("Hit!");
//unregister the event handler to avoid to see the messagebox more than once
doubleAnimation.CurrentTimeInvalidated -= CurrentTimeInvalidated;
}
}
static bool HitTest(FrameworkElement airPlane, FrameworkElement bomb)
{
var r1 = new Rect(Canvas.GetLeft(airPlane), Canvas.GetTop(airPlane), airPlane.ActualWidth, airPlane.ActualHeight);
var r2 = new Rect(Canvas.GetLeft(bomb), Canvas.GetTop(bomb), bomb.ActualWidth, bomb.ActualHeight);
return r1.IntersectsWith(r2);
}
If you do it similar to me, it should work. Or is your code structure significant different to my?
I have been battling this for a couple of days now, and just cannot find the answer. Hoping someone here can help.
We have an animated keyboard that pops up when a user selects a Textblock Control that requires keyboard input. The code that animates the keyboard is fine. But it calls code to adjust the grid that contains the textblock control so that the textblock control always sits just above the animated keyboard. The problem that I am seeing is that when the page that contains the grid is closed, it exceptions with the 'Children' property value in the path '(0).(1)[0].(2)' points to immutable instance of 'System.Windows.Media.TransformCollection' on this line:
_AppWindowControl.IsEnabled = false;
The code that gets called when the keyboard is removed (hidden by "Done" keypress) is this:
/// <summary>
/// Animation to hide keyboard
/// </summary>
private void HideKeyboard()
{
if (_verticalOffset != 0)
{
TranslateTransform tt = new TranslateTransform();
DoubleAnimation slide = new DoubleAnimation(_verticalOffset, 0, TimeSpan.FromMilliseconds(400));
var name = "myTransform" + tt.GetHashCode();
_mainGrid.RegisterName(name, tt);
name = "mySlide" + slide.GetHashCode();
_mainGrid.RegisterName(name, slide);
_mainGrid.RenderTransform = tt;
tt.BeginAnimation(TranslateTransform.YProperty, slide);
_verticalOffset = 0;
}
Storyboard sb = (Storyboard)this.TryFindResource("HideKeyboard");
sb.Completed += new EventHandler(HideKeyboard_Completed);
sb.Begin(this);
}
I added the name registration in hopes that would fix the problem. But it does not. If I remove the assignment _mainGrid.RenderTransform = tt; then the appWindow closes without any error.
Also, I said the problem occurs when closing the keyboard. This code was just the easiest to show. When the keyboard appears, there is a call to AdjustScreen, which creates a similar assignment of a TranslateTransform to the _mainGrid.RenderTransform. Again, if I remove the assignment, no problem occurs (no animation occurs either). Otherwise, the same error described above will occur.
Any help would be greatly appreciated. Thanks!
Edit. Here is the StoryBoard from the xaml file:
<Storyboard x:Key="HideKeyboard">
<DoubleAnimationUsingKeyFrames AccelerationRatio=".75" BeginTime="00:00:00" DecelerationRatio=".25" Storyboard.TargetName="KeyboardGrid" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.Y)">
<SplineDoubleKeyFrame KeyTime="00:00:00" Value="0" />
<!--<SplineDoubleKeyFrame KeyTime="00:00:00.20" Value="-10" />-->
<!--<SplineDoubleKeyFrame KeyTime="00:00:00.45" Value="450" />-->
<SplineDoubleKeyFrame KeyTime="00:00:00.25" Value="450" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>`enter code here`
Also, I have a workaround for this that basically stores the _mainGrid.RenderTransfrom prior to changing it here. Then, when the HideKeybaord_Completed handler is called, it reverts it back. This method works. But it seems pretty hackish.
The crash is an application crash. Most of the time, we are exiting the UI anyway, so no one ever noticed. But, as I am adding a new view to the model, it crashes when closing my view and so it doesn't get back to the previous view.
This error can appear if you don't have a TransformGroup defined as the RenderTransform for the thing you are animating. It should look something like this:
<Ellipse>
<Ellipse.RenderTransform>
<TransformGroup>
<ScaleTransform CenterX="0.5" CenterY="0.5" />
<SkewTransform/>
<RotateTransform/>
<TranslateTransform/>
</TransformGroup>
</Ellipse.RenderTransform>
</Ellipse>
I attempted to make a Virtual Keyboard kind of behavior
xaml
<StackPanel xmlns:l="clr-namespace:CSharpWPF">
<StackPanel.Resources>
<UniformGrid Columns="5"
Rows="2"
x:Key="dummyKeyboard">
<Button Content="1" />
<Button Content="2" />
<Button Content="3" />
<Button Content="4" />
<Button Content="5" />
<Button Content="A" />
<Button Content="B" />
<Button Content="C" />
<Button Content="D" />
<Button Content="E" />
</UniformGrid>
</StackPanel.Resources>
<TextBox Text="regular textbox" />
<TextBox Text="virtual keyboard enabled textbox"
l:InputProvider.VirtualKeyboard="{StaticResource dummyKeyboard}" />
<TextBox Text="another regular textbox" />
<TextBox Text="another virtual keyboard enabled textbox"
l:InputProvider.VirtualKeyboard="{StaticResource dummyKeyboard}" />
<TextBox Text="one more regular textbox" />
</StackPanel>
for example purpose I have defined a dummyKeyboard containing some buttons. then I have assigned this keyboard to some of the textboxes by setting InputProvider.VirtualKeyboard="{StaticResource dummyKeyboard}", you can assign to all manually or even assign via styles for textbox
InputProvider class
namespace CSharpWPF
{
public class InputProvider : DependencyObject
{
public static object GetVirtualKeyboard(DependencyObject obj)
{
return (object)obj.GetValue(VirtualKeyboardProperty);
}
public static void SetVirtualKeyboard(DependencyObject obj, object value)
{
obj.SetValue(VirtualKeyboardProperty, value);
}
// Using a DependencyProperty as the backing store for VirtualKeyboard. This enables animation, styling, binding, etc...
public static readonly DependencyProperty VirtualKeyboardProperty =
DependencyProperty.RegisterAttached("VirtualKeyboard", typeof(object), typeof(InputProvider), new PropertyMetadata(null, OnVirtualKeyboardChanged));
private static void OnVirtualKeyboardChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextBox tb = d as TextBox;
tb.GotFocus += (sender, ee) => OpenPopup(sender as TextBox);
tb.LostFocus += (sender, ee) => ((Popup)tb.GetValue(PopupProperty)).IsOpen = false;
}
private static void OpenPopup(TextBox textBox)
{
Popup popup = new Popup() { Child = new ContentControl() { Content = GetVirtualKeyboard(textBox), Focusable = false } };
FocusManager.SetIsFocusScope(popup.Child, true);
popup.PlacementTarget = textBox;
popup.AllowsTransparency = true;
popup.PopupAnimation = PopupAnimation.Slide;
textBox.SetValue(PopupProperty, popup);
popup.IsOpen = true;
}
// Using a DependencyProperty as the backing store for Popup. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PopupProperty =
DependencyProperty.RegisterAttached("Popup", typeof(Popup), typeof(InputProvider), new PropertyMetadata(null));
}
}
this class defined an attached property VirtualKeyboard which is of type object, thus allowing you to define data templates as necessary
so this class listen to the GotFocus & LostFocus events of the textbox and displays or hides the virtual keyboard in a sliding animation.
give this a try, I hope this may help you to achieve the desired.
note that the dummy keyboard is really dummy it does not do any typing, you need to replace the same with your actual working virtual keyboard while implementing the same in your project.
I'm trying to create a storyboard in XAML that animates a property of one of the child elements of an element which raises an event. But I can't seem to get it to work without using Names, which is something I can't really do in this specific situation.
I'm basically trying something like this (much simplified of course):
<Canvas>
<Canvas.Triggers>
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Children[0].(Canvas.Left)" From="0" To="400" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Canvas.Triggers>
<Button Canvas.Left="20" Canvas.Top="20">A</Button>
<Button Canvas.Left="40" Canvas.Top="20">B</Button>
</Canvas>
Any ideas on how this could be achieved?
Providing that the UIElement you are indexing in the animation exists (i.e. already present on the Canvas) then you can do the following:
<Canvas x:Name="MyCanvas">
<Button x:Name="btn" Canvas.Left="20" Canvas.Top="20">A</Button>
<Button Canvas.Left="40" Canvas.Top="20">B</Button>
<Canvas.Triggers>
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.Target="{Binding ElementName=MyCanvas, Path=Children[0]}"
Storyboard.TargetProperty="(Canvas.Left)" From="0" To="400" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Canvas.Triggers>
</Canvas>
Notice how I have moved the addition of the Buttons above the Trigger. If the Buttons are below the Trigger as in your question, trying to access Children[0] will throw an ArgumentOutOfRangeException because there are no children at this point.
To use the Storyboard.TargetProperty in the animation, it should always be a dependency property. Children property gets a UIElementCollection of child elements of this Panel (Canvas). Therefore, the following construction Children [n] return UIElement, which should lead to a certain type, to access its dependency property.
This can be done in the code as follows:
Button MyButton = (Button)MyCanvas.Children[0];
MessageBox.Show(MyButton.Width.ToString());
All of these actions missing in the animation by default, this is your construction will not work.
I propose to create animations in the code where this conversion possible.
To demonstrate this, I created a Canvas, in the event Loaded having registered animation. Element number is set via an attached dependency property (of course, the example can be implemented in various ways). Below is my example:
XAML
<Grid>
<local:MyCanvas x:Name="MyCanvas" local:ClassForAnimation.Children="1">
<Button Canvas.Left="20" Canvas.Top="20">A</Button>
<Button Canvas.Left="40" Canvas.Top="20">B</Button>
</local:MyCanvas>
</Grid>
Code behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
public class MyCanvas : Canvas
{
public MyCanvas()
{
this.Loaded += new RoutedEventHandler(MyCanvas_Loaded);
}
private void MyCanvas_Loaded(object sender, RoutedEventArgs e)
{
MyCanvas myCanvas = sender as MyCanvas;
// Get No. of children
int children = ClassForAnimation.GetChildren(myCanvas);
// Get current Button for animation
Button MyButton = (Button)myCanvas.Children[children];
if (myCanvas != null)
{
DoubleAnimation doubleAnimation = new DoubleAnimation();
doubleAnimation.From = 0;
doubleAnimation.To = 400;
MyButton.BeginAnimation(Button.WidthProperty, doubleAnimation);
}
}
}
public class ClassForAnimation : DependencyObject
{
public static readonly DependencyProperty ChildrenProperty;
public static void SetChildren(DependencyObject DepObject, int value)
{
DepObject.SetValue(ChildrenProperty, value);
}
public static int GetChildren(DependencyObject DepObject)
{
return (int)DepObject.GetValue(ChildrenProperty);
}
static ClassForAnimation()
{
PropertyMetadata MyPropertyMetadata = new PropertyMetadata(0);
ChildrenProperty = DependencyProperty.RegisterAttached("Children",
typeof(int),
typeof(ClassForAnimation),
MyPropertyMetadata);
}
}
Note: Access to the items in the Canvas should only be done in the event Loaded, or when it ended. Otherwise, the items are not available because they are not loaded.
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.