应用于虚拟UIElement的故事板停止随机运行

时间:2015-07-20 10:18:02

标签: c# wpf animation storyboard dependency-properties

我想在最后显示我的代码之前先显示我的问题。 我想创建一个在AnimationTimeline上使用的附加属性来帮助动画不可动画的属性(包括所有非DependencyProperties)。这里的想法是连接输入TargetProperty并为虚拟UIElement的属性(相同类型)设置动画。这个虚拟UIElement公开了一个名为ValueChanged的事件,这样我们就可以连接并更新那里的实际对象的不可动画属性。

更具体地说,我想为Position的{​​{1}}属性设置动画。我想让它反复播放。它运行相当顺畅,即使是几十个周期(如果你只看十个周期,你会被欺骗,可能会想:啊!一切都好)。是的,但之后它可能会停止运行。我不知道为什么会发生这种情况。起初唯一的想法可能是我的虚拟对象被GC以某种方式被破坏,但我甚至尝试在静态集合中保留它的引用,但问题仍然存在。

这是我的代码:

代码

MediaElement

此演示使用的//Currently this supports only Double and Int32 animations public class Dummy<T> : UIElement { public Dummy(){ //I found myself that this is very important to make it work //at least for dozens of cycles, otherwise it just stops running //when the first cycle has not been done. //Still don't understand why this is needed. Application.Current.MainWindow.RegisterName("_" + Guid.NewGuid().ToString().Replace("-", ""), this); } public T Data { get; set; } public event DummyValueChangedEventHandler ValueChanged; protected void OnValueChanged(DummyValueChangedEventArgs e) { var handler = ValueChanged; if (handler != null) handler(this, e); } public DependencyProperty GetDependencyPropertyOfType(Type type) { if (typeof(double).IsAssignableFrom(type)) return DoubleProperty; else if (typeof(int).IsAssignableFrom(type)) return Int32Property; return null; } #region DependencyProperties public static readonly DependencyProperty DoubleProperty = DependencyProperty.Register("Double", typeof(double), typeof(Dummy<T>), new PropertyMetadata(propertyChangedHandler)); public static readonly DependencyProperty Int32Property = DependencyProperty.Register("Int32", typeof(int), typeof(Dummy<T>), new PropertyMetadata(propertyChangedHandler)); #endregion #region properties changed handlers private static void propertyChangedHandler(DependencyObject o, DependencyPropertyChangedEventArgs e) { var dummy = o as Dummy<T>; dummy.OnValueChanged(new DummyValueChangedEventArgs(e.NewValue)); } #endregion public delegate void DummyValueChangedEventHandler(object sender, DummyValueChangedEventArgs e); public class DummyValueChangedEventArgs : EventArgs { public object Value { get; private set; } public DummyValueChangedEventArgs(object value) { Value = value; } } }

DummyData

这是struct DummyData { public Action<object> ValueSetter { get; set; } public IValueConverter Converter { get; set; } public DummyData(Action<object> valueSetter, IValueConverter converter = null) : this() { ValueSetter = valueSetter; Converter = converter; } } 的扩展,实际代码更复杂,因为它支持复杂路径,但在这种情况下路径很简单(如PropertyPath)所以这段代码足以测试整个代码:

Position

因为public static class PropertyPathExtension { public static Action<object> GetSetter(this PropertyPath current, object target) { var propInfo = target.GetType().GetProperty(current.Path); if(propInfo != null){ return (o) => propInfo.SetValue(target, o, null); } return null; } } Position,但我使用TimeSpan为其设置动画,所以我需要像这样的转换器将DoubleAnimation(代表毫秒)转换为{ {1}}:

double

这是包装TimeSpan和转换器的另一个类:

public class MillisecondsToTimeSpanConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return new TimeSpan((long)((double)value * TimeSpan.TicksPerMillisecond)); 
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

最后,PropertyPath充当我创建的一些有用附加属性的所有者:

public class PropertyWithConverter : MarkupExtension
{
    public PropertyPath Path { get; set; }
    /// <summary>
    /// The Converter used to convert the input value before setting for the property.
    /// 
    /// </summary>
    public IValueConverter Converter { get; set; }
    public PropertyWithConverter(PropertyPath path)
    {
        Path = path;
    }
    public PropertyWithConverter(string path)
    {
        Path = new PropertyPath(path);
    }
    public PropertyWithConverter(PropertyPath path, IValueConverter converter){
        Path = path;
        Converter = converter;
    }
    public PropertyWithConverter(string path, IValueConverter converter)
    {
        Path = new PropertyPath(path);
        Converter = converter;
    }
    public override object ProvideValue(IServiceProvider serviceProvider) {
        return new PropertyWithConverter(Path, Converter);
    }
}

XAML

目前StoryboardX只能使用public class StoryboardX { public static readonly DependencyProperty TargetPropertyProperty = DependencyProperty.RegisterAttached("TargetProperty", typeof(PropertyWithConverter), typeof(StoryboardX), new PropertyMetadata(targetPropertyChanged)); public static PropertyWithConverter GetTargetProperty(DependencyObject o) { return o.GetValue(TargetPropertyProperty) as PropertyWithConverter; } public static void SetTargetProperty(DependencyObject o, PropertyWithConverter path) { o.SetValue(TargetPropertyProperty, path); } static private void targetPropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) { var anim = o as AnimationTimeline; var target = Storyboard.GetTarget(o); if (!(target is MediaElement)) return; var prop = StoryboardX.GetTargetProperty(o); var dummy = new Dummy<DummyData>() { Data = new DummyData(prop.Path.GetSetter(target), prop.Converter) }; //hook up this to update the actual property dummy.ValueChanged += dummyValueChanged; var dp = dummy.GetDependencyPropertyOfType(anim.TargetPropertyType); if (dp != null) { //update this to make Storyboard animate the dummy instead. Storyboard.SetTarget(o, dummy); Storyboard.SetTargetProperty(o, new PropertyPath(dp.Name)); } } static private void dummyValueChanged(object sender, Dummy<DummyData>.DummyValueChangedEventArgs e) { var dummy = sender as Dummy<DummyData>; var vl = e.Value; if (dummy.Data.Converter != null) vl = dummy.Data.Converter.Convert(vl, typeof(TimeSpan), null, null); dummy.Data.ValueSetter(vl); } } 直接与StoryboardX挂钩,无法从Target派生(使用Storyboard.GetTarget),因此我使用{{ 1}}为目标设置TargetName

Storyboard.GetTargetName

另请注意,{x:Reference}MediaElement应设置为媒体片段持续时间范围内的某些值(以毫秒为单位),您应事先使用某些实际播放器或Windows资源管理器了解此值。我在这里提供的代码是某种开始演示。

我想从你那里得到一些建议,因为我已经没有解决这个棘手问题的想法(它运行平稳了几十个周期,但随后可能会停止随机运行)。正如我所说的,如果您对此感兴趣,请对代码进行足够长时间的测试,它可以在循环中顺利运行,但随后会停止运行。

另外,正如我在<Grid.Resources> <local:MillisecondsToTimeSpanConverter x:Key="cv"/> </Grid.Resources> <MediaElement Name="player" Source="e:\myVideoPath.mp4" LoadedBehavior="Pause" ScrubbingEnabled="True"/> <Grid.Triggers> <EventTrigger RoutedEvent="Loaded"> <BeginStoryboard> <Storyboard> <DoubleAnimation From="21000" To="0" Duration="00:00:21" Storyboard.Target="{x:Reference player}" RepeatBehavior="Forever" local:StoryboardX.TargetProperty="{local:PropertyWithConverter Position, Converter={StaticResource cv}}"/> </Storyboard> </BeginStoryboard> </EventTrigger> </Grid.Triggers> 构造函数中所评论的那样,这表明让它正常运行非常重要,您可以尝试将其评论出去,看看它如何在短时间内停止运行:

From

所以这可能是解决问题的点,我通过猜测可能出错的内容来补充这一点,至少可以帮助更长时间地运行动画。

此外,测试片段应该超过10秒,因为我测试了一个10秒的片段并且它工作正常但我已经测试了超过10秒的许多片段而且没有一个工作。

谢谢大家的帮助。

修改: 我发现如果将虚拟To添加到可视树中,它似乎可以正常工作。通过查看Dummy的源代码,我看到了一些调用的方法: AddLogicalChild,VisualCollection.Add和_visualParent.InvalidateMeasure 。 所以我尝试在Application.Current.MainWindow.RegisterName("_" + Guid.NewGuid().ToString().Replace("-", ""), this); 上调用UIElement(但这次没有将它添加到可视化树中),看起来它几乎可以正常工作。我已经尝试了很多次,让它运行了几个小时,一切似乎都行,但不知何故(在另一个时间运行应用程序)它只是失败一次(非常罕见的失败时间)。至少它现在更好,因为不会太快停止,有时完全工作。所以这个问题真的很棘手。对UIElementCollection(任何InvalidateMeasure可以拥有的)和UIElement的布局(我的意思是UIElement调用的参与)进行动画制作之间的关系完全令人困惑

0 个答案:

没有答案