GridLength animation using keyframes? - wpf

I want to know are there any classes that I can animate a GridLength value using KeyFrames? I have seen the following sites, but none of them were with KeyFrames:
http://windowsclient.net/learn/video.aspx?v=70654
http://marlongrech.wordpress.com/2007/08/20/gridlength-animation/
Any advice?

Create an attached behavior and animate it instead.
Sure, GridLength clearly is not a numeric type and as such it's not clear how it can be animated. To compnesate that I can create an attached behavior like:
public class AnimatableProperties
{
public static readonly DependencyProperty WidthProperty =
DependencyProperty.RegisterAttached("Width",
typeof(double),
typeof(DependencyObject),
new PropertyMetadata(-1, (o, e) =>
{
AnimatableProperties.OnWidthChanged((Grid)o, (double)e.NewValue);
}));
public static void SetWidth(DependencyObject o,
double e)
{
o.SetValue(AnimatableProperties.WidthProperty, e);
}
public static double GetWidth(DependencyObject o)
{
return (double)o.GetValue(AnimatableProperties.WidthProperty);
}
private static void OnWidthChanged(DependencyObject target,
double e)
{
target.SetValue(Grid.WidthProperty, new GridLength(e));
}
}
That will re-inroduce Grid width as numeric property of double type. Having that in place you can freely animate it.
P.S. Obviously it doesn't make much sense to use Grid's Width as it's already double. any other GridLength based properties can be wrpapped with double wrappers as per the sample above and then animated via that wrappers.

It is fairly straight forward but you need to use an adapter because you can't directly animate Width on the ColumnDefinition class with a DoubleAnimator because ColumnDefinition is not a double. Here's my code:
public class ColumnDefinitionDoubleAnimationAdapter : Control
{
#region Dependency Properties
public static readonly DependencyProperty WidthProperty = DependencyProperty.Register(nameof(Width), typeof(double), typeof(ColumnDefinitionDoubleAnimationAdapter), new PropertyMetadata((double)0, WidthChanged));
private static void WidthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var columnDefinitionDoubleAnimationAdapter = (ColumnDefinitionDoubleAnimationAdapter)d;
columnDefinitionDoubleAnimationAdapter.Width = (double)e.NewValue;
}
#endregion
#region Fields
private ColumnDefinition _ColumnDefinition;
#endregion
#region Constructor
public ColumnDefinitionDoubleAnimationAdapter(ColumnDefinition columnDefinition)
{
_ColumnDefinition = columnDefinition;
}
#endregion
#region Public Properties
public double Width
{
get
{
return (double)GetValue(WidthProperty);
}
set
{
SetValue(WidthProperty, value);
_ColumnDefinition.Width = new GridLength(value);
}
}
#endregion
}
Unfortunately the above is pretty inefficient because it creates a GridLength again and again because ColumnDefinition.Width.Value should be read only.
Here is a method to do the animation. It's important that it uses Task based async because otherwise the storyboard will go out of scope and cause bad behaviour. This is good practice anyway so you can await the animation if you need to:
public async static Task AnimateColumnWidth(ColumnDefinition columnDefinition, double from, double to, TimeSpan duration, IEasingFunction ease)
{
var taskCompletionSource = new TaskCompletionSource<bool>();
var storyboard = new Storyboard();
var animation = new DoubleAnimation();
animation.EasingFunction = ease;
animation.Duration = new Duration(duration);
storyboard.Children.Add(animation);
animation.From = from;
animation.To = to;
var columnDefinitionDoubleAnimationAdapter = new ColumnDefinitionDoubleAnimationAdapter(columnDefinition);
Storyboard.SetTarget(animation, columnDefinitionDoubleAnimationAdapter);
Storyboard.SetTargetProperty(animation, new PropertyPath(ColumnDefinitionDoubleAnimationAdapter.WidthProperty));
storyboard.Completed += (a, b) =>
{
taskCompletionSource.SetResult(true);
};
storyboard.Begin();
await taskCompletionSource.Task;
}
And an example usage:
private async void TheMenu_HamburgerToggled(object sender, EventArgs e)
{
TheMenu.IsOpen = !TheMenu.IsOpen;
var twoSeconds = TimeSpan.FromMilliseconds(120);
var ease = new CircleEase { EasingMode = TheMenu.IsOpen ? EasingMode.EaseIn : EasingMode.EaseOut };
if (TheMenu.IsOpen)
{
await UIUtilities.AnimateColumnWidth(MenuColumn, 40, 320, twoSeconds, ease);
}
else
{
await UIUtilities.AnimateColumnWidth(MenuColumn, 320, 40, twoSeconds, ease);
}
}

Related

Dependency Property design data

I have a UserControl that has an ObservableCollection dependency property with a property changed callback. The callback rebuilds the nodes in a TreeView control. This all works fine but I would like to be able to have design data. Unfortunately, neither the constructor, the callback nor a default value function call is called by the designer unless I embed my control in another. Is there a way loading default data in this scenario?
Below is the code behind for my control
public partial class ScheduleResourcesSummaryTreeView : UserControl
{
public ObservableCollection<PerformanceProjection> SelectedPerformances
{
get { return (ObservableCollection<PerformanceProjection>)GetValue(SelectedPerformancesProperty); }
set { SetValue(SelectedPerformancesProperty, value); }
}
// Using a DependencyProperty as the backing store for MyProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectedPerformancesProperty =
DependencyProperty.Register("SelectedPerformances",
typeof(ObservableCollection<PerformanceProjection>),
typeof(ScheduleResourcesSummaryTreeView),
new FrameworkPropertyMetadata(
new ObservableCollection<PerformanceProjection>(),
FrameworkPropertyMetadataOptions.AffectsRender, SelectedPerformancesChanged)
);
private static void SelectedPerformancesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!(d is ScheduleResourcesSummaryTreeView resourcesTree)) return;
resourcesTree.ResourcesTree.Items.Clear();
var sessions = e.NewValue as ObservableCollection<PerformanceProjection> ?? new ObservableCollection<PerformanceProjection>();
if (sessions.Count == 0) return;
var projections = sessions.SelectMany(x => x.ResourceBookingProjections);
TreeNode<ResourceBookingProjection> resourceBookingTreeRoot = new RvtSummaryTree(new ObservableCollection<ResourceBookingProjection>(projections), "");
foreach (var treeNode in resourceBookingTreeRoot.Children)
{
resourcesTree.ResourcesTree.Items.Add((TreeNode<ResourceBookingProjection>)treeNode);
}
}
private static ObservableCollection<PerformanceProjection> DefaultCollection()
{
var prop = DesignerProperties.IsInDesignModeProperty;
var designMode = (bool) DependencyPropertyDescriptor
.FromProperty(prop, typeof(FrameworkElement))
.Metadata.DefaultValue;
if (!designMode)
return new ObservableCollection<PerformanceProjection>();
var designdata = new PerformanceProjectionsViewModelDesignData();
return designdata.SelectedPerformances;
}
public ScheduleResourcesSummaryTreeView()
{
InitializeComponent();
}
}

In WPF, how do I copy a binding from a TextBlock to custom collection?

Problem: I have a ListBox with TextBlocks whos Text property are bound to different properties. I wish to drag the TextBlock onto a OxyPlot and have the plot create a new LineSeries with a collection that should be bound to the same binding as for the TextBlock (is this making sense?)
I have derived a class from TextBlock to handle the OnMouseMove event like this:
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (CanDrag && (e.LeftButton == MouseButtonState.Pressed))
{
// Make sure we have a data binding
BindingExpression binding = GetBindingExpression(TextProperty);
if(binding == null)
{ return; }
// Package the data.
DataObject data = new DataObject();
data.SetData("DragListText.Binding", binding);
// Inititate the drag-and-drop operation.
DragDrop.DoDragDrop(this, data, DragDropEffects.Copy);
}
}
Also I have derived a class from Oxy.Plot that handles the OnDrop:
protected override void OnDrop(DragEventArgs e)
{
base.OnDrop(e);
// DataObject must contain a DragListText.Binding object
if (e.Data.GetDataPresent("DragListText.Binding"))
{
BindingExpression binding = e.Data.GetData("DragListText.Binding") as BindingExpression;
AddSeries(binding);
}
e.Handled = true;
}
The AddSeries function does the following:
public void AddSeries(BindingExpression binding)
{
plot1 = new PlotCollection();
LineSeries newSeries = new LineSeries();
newSeries.ItemsSource = plot1.Collection;
Series.Add(newSeries);
}
And lastly the PlotCollection is defined as:
public class PlotCollection : DependencyObject
{
public ObservableCollection<DataPoint> Collection;
public static DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(double), typeof(PlotCollection), new PropertyMetadata(0.0, new PropertyChangedCallback(OnValueChanged)));
private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{ ((PlotCollection)d).AddLast(); }
public double Value
{
get { return (double)GetValue(ValueProperty); }
set { SetValue(ValueProperty, value); }
}
public PlotCollection()
{
Collection = new ObservableCollection<DataPoint>();
}
protected void AddLast()
{
Collection.Add(new DataPoint(OxyPlot.Axes.DateTimeAxis.ToDouble(DateTime.Now), Value));
}
}
So my question is this: How do I create a binding on PlotCollection.Value that matches the one from the TextBlock.Text?
In your AddSeries method, try adding this line of code:
BindingOperations.SetBinding(plot1, PlotCollection.ValueProperty, binding.ParentBinding);
Found out the problem,
I needed to add a PropertyChangedCallback to the ValueProperty declaration, like this:
public static DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(double), typeof(DynamicSeries), new PropertyMetadata(0.0, OnValueChanged));
And then handle the property changes in the callback method:
private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
PlotCollection ds = (PlotCollection)d;
ds.AppendValue((double)e.NewValue);
}
I guess that I have misunderstood how the Value property works?!
Thanks for taking the time to try and help me...

How to animate a Point in a MeshGeometry3D in WPF/XAML?

Is it possible to animate points in a MeshGeometry3D? either in XAML or in C# code behind.
I can't seem to find a way to animate the X,Y,Z locations of points over time.
Any ideas?
This may help.. WPF and 3D how do you change a single position point in 3D space?
Maybe not pretty
Xaml
<Viewport3D>
<ModelVisual3D x:Name="VisualHost"/>
</Viewport3D>
CodeBehind
public partial class MyUserControl : UserControl
{
#region TargetZ Property
public static readonly DependencyProperty TargetZProperty =
DependencyProperty.RegisterAttached("TargetZ", typeof(double), typeof(MyUserControl), new PropertyMetadata(TargetZ_Changed));
private static void TargetZ_Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var positions = ((MeshGeometry3D)((GeometryModel3D)d).Geometry).Positions;
var point = positions[0];
positions[0] = new Point3D(point.X, point.Y, point.Z + (double)e.NewValue);
}
public void SetTargetZ(GeometryModel3D d, double value)
{
d.SetValue(TargetZProperty, value);
}
public double GetTargetZ(GeometryModel3D d)
{
return (double)d.GetValue(TargetZProperty);
}
#endregion
public MyUserControl()
{
InitializeComponent();
}
private void SetNewZ(double newValue)
{
var animationTime = TimeSpan.FromSeconds(1);
var model = (GeometryModel3D)VisualHost.Content;
var zAnimation = new DoubleAnimation(newValue, animationTime) { FillBehavior = FillBehavior.HoldEnd };
model.BeginAnimation(TargetZProperty, zAnimation);
}
}

How to set a dependency property from InLine event of a textblock?

I'm trying to set the dependency property "WordPad" from within the Inline event of MouseEnter from a CustomTextBlock. But this error results:
An object reference is required for the non-static field, method, or property 'WpfCustomControlLibrary.CustomTextBlock.WordPad.get'
How can I achieve this?
Any help would be most appreciated. Thanks.
Given the following class:
public class CustomTextBlock : TextBlock
{
public string InLineText
{
get { return (string)GetValue(InLineTextProperty); }
set { SetValue(InLineTextProperty, value); }
}
public static readonly DependencyProperty InLineTextProperty =
DependencyProperty.Register("InLineText", typeof(string), typeof(CustomTextBlock),
new FrameworkPropertyMetadata(string.Empty,
FrameworkPropertyMetadataOptions.AffectsMeasure,
(o, e) =>
{
//PropertyChangedCallback
CustomTextBlock tb = (CustomTextBlock)o;
string text = (string)e.NewValue;
tb.Inlines.Clear();
if (String.IsNullOrEmpty(text))
return;
List<Inline> inlines = new List<Inline>();
string[] words = Regex.Split(text, #"(\s+)");
Inline inline = null;
foreach (string s in words)
{
Run run = new Run(s);
inline = run;
inline.MouseEnter += new System.Windows.Input.MouseEventHandler(inline_MouseEnter);
inline.MouseLeave += new System.Windows.Input.MouseEventHandler(inline_MouseLeave);
tb.Inlines.Add(inline);
}
}));
public WritingPad WordPad
{
get { return (WritingPad)GetValue(WordPadProperty); }
set { SetValue(WordPadProperty, value); }
}
public static readonly DependencyProperty WordPadProperty =
DependencyProperty.Register("WordPad", typeof(WritingPad), typeof(CustomTextBlock), new UIPropertyMetadata(null));
static void inline_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
{
Run Sender = sender as Run;
TextPointer tp0 = Sender.ContentStart;
TextPointer tp1 = Sender.ContentEnd;
Rect StartRect = tp0.GetCharacterRect(LogicalDirection.Forward);
Rect EndRect = tp1.GetCharacterRect(LogicalDirection.Backward);
StartRect.Union(EndRect);
WordPad = new WritingPad(); <--**THIS FAILS ????
}
Make the inline_MouseEnter and inline_MouseLeave methods non-static and attach them to your Runs like this:
inline.MouseEnter += tb.inline_MouseEnter;
inline.MouseLeave += tb.inline_MouseLeave;
Even better would be to make the whole PropertyChangedCallback non-static and then write the dependency property declaration like this:
public static readonly DependencyProperty InLineTextProperty =
DependencyProperty.Register("InLineText", typeof(string), typeof(CustomTextBlock),
new FrameworkPropertyMetadata(string.Empty,
FrameworkPropertyMetadataOptions.AffectsMeasure,
(o, e) => ((CustomTextBlock)o).InlineTextChanged((string)e.NewValue)));
private void InlineTextChanged(string text)
{
Inlines.Clear();
if (!string.IsNullOrEmpty(text))
{
foreach (string s in Regex.Split(text, #"(\s+)"))
{
var run = new Run(s);
run.MouseEnter += inline_MouseEnter;
run.MouseLeave += inline_MouseLeave;
Inlines.Add(run);
}
}
}
I'm hopeful a guru can give a better solution.
It should be noted that Run has a "Parent" property which resolved to this instance of CustomTextBlock. Hence, this seems to work,
CustomTextBlock ctb = Sender.Parent as CustomTextBlock;
ctb.WordPad = new WritingPad
{
WordBounds = StartRect,
WritingPadMode = Enumerations.WritingPadModes.EditingByWord
};

wpf twoway bound DependencyProperty setcurrentvalue not working

I'm working on a custom behavior for the visiblox chart. This custom behavior has a dependency property Value that identifies the position of a cursor that consists of vertical line draw in the chart. This cursor follows the mouse if I set the property FollowMouse to true.
If I bind the Value property the changedvaluecallback only gets 0 as the newValue, while if the value is not bound it works properly. But if i change the source property of the binding (property on ViewModel) it works too. So the problem is setting the value with SetCurrentValue on PointerMoved.
Here is the source code of the behavior:
public class TimeCursorBehavior : BehaviourWithAxesBase
{
private System.Windows.Shapes.Line _line;
public TimeCursorBehavior()
: base("TimeCursor")
{
_line = new System.Windows.Shapes.Line();
_line.Stroke = System.Windows.Media.Brushes.Black;
_line.StrokeThickness = 2;
}
public override void DeInit()
{
base.DeInit();
Chart.BehaviourCanvas.Children.Remove(_line);
}
protected override void Init()
{
base.Init();
Chart.BehaviourCanvas.Children.Add(_line);
}
public override void PointerMoved(IBehaviourEventSource sender, PointerEventContext context)
{
base.PointerMoved(sender, context);
if (!FollowMouse)
return;
IComparable xDataValue = XAxis.GetRenderPositionAsDataValueWithZoom(context.Point.X);
SetCurrentValue(ValueProperty, xDataValue);
}
public override void BehaviourCanvasSizeChanged(IBehaviourEventSource sender, SizeChangedEventArgs e)
{
base.BehaviourCanvasSizeChanged(sender, e);
_line.Y2 = e.NewSize.Height;
}
#region Value
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(IComparable), typeof(TimeCursorBehavior), new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnValueChanged));
private static void OnValueChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
(sender as TimeCursorBehavior).OnValueChanged(args.OldValue as IComparable, args.NewValue as IComparable);
}
private void OnValueChanged(IComparable oldValue, IComparable newValue)
{
if (XAxis == null)
return;
double x = XAxis.GetDataValueAsRenderPositionWithZoom(newValue);
_line.X1 = x;
_line.X2 = x;
}
public IComparable Value
{
get
{
return GetValue(ValueProperty) as IComparable;
}
set
{
SetValue(ValueProperty, value);
}
}
#endregion
#region FollowMouse
public static readonly DependencyProperty FollowMouseProperty = DependencyProperty.Register("FollowMouse", typeof(bool), typeof(TimeCursorBehavior), new PropertyMetadata(false));
public bool FollowMouse
{
get
{
return (bool)GetValue(FollowMouseProperty);
}
set
{
SetValue(FollowMouseProperty, value);
}
}
#endregion
}
Does anyone know why setcurrentvalue is not updating the value accordingly?
Found the problem.
My property in the ViewModel is an decimal, and the property returned by the line below is a double.
IComparable xDataValue = XAxis.GetRenderPositionAsDataValueWithZoom(context.Point.X);
I added an converter to the binding it everything worked as expected.

Resources