我想在最后显示我的代码之前先显示我的问题。
我想创建一个在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
调用的参与)进行动画制作之间的关系完全令人困惑