在进行图形界面编程时,经常需要对多个图形对象进行精确的布局和复杂的动画控制。然而,现有的图形框架如Silverlight和WPF并没有提供直接的方法来实现这些需求。本文将展示如何使用Lambda表达式和高阶函数来动态地创建对象和动画。
首先,来定义一个简单的类,用于存储一组圆形对象。这个类将继承自集合类,并添加一个构造函数来创建指定数量的对象。
public class LambdaCollection : Collection where T : DependencyObject, new()
{
public LambdaCollection(int count)
{
while (count --> 0) Add(new T());
}
}
接下来,添加一个方法来初始化集合中对象的属性。这个方法将使用Lambda表达式来设置属性值。
public class LambdaCollection : Collection where T : DependencyObject, new()
{
// ...
public LambdaCollection WithProperty(DependencyProperty property, Func generator)
{
for (int i = 0; i < Count; ++i)
this[i].SetValue(property, generator(i));
return this;
}
}
这个方法使用了流畅的接口风格,通过返回this来允许链式调用。它接受两个参数:要设置的属性和值生成器。值生成器是一个函数,它接受集合中元素的索引并返回一个值。
例如,可以创建10个大小递增的圆形对象:
var circles = new LambdaCollection(10)
.WithProperty(WidthProperty, i => 1.5 * (i + 1))
.WithProperty(HeightProperty, i => 1.5 * (i + 1));
为了简化X和Y坐标的设置,可以定义一个辅助方法:
public class LambdaCollection : Collection where T : DependencyObject, new()
{
// ...
public LambdaCollection WithXY(Func xGenerator, Func yGenerator)
{
for (int i = 0; i < Count; ++i)
{
this[i].SetValue(Canvas.LeftProperty, xGenerator(i));
this[i].SetValue(Canvas.TopProperty, yGenerator(i));
}
return this;
}
}
现在,可以将这些方法组合起来,创建一个复杂的图形布局:
int count = 20;
var circles = new LambdaCollection(count)
.WithXY(i => 100.0 + (4.0 * i * Math.Sin(i / 4.0 * (Math.PI))),
i => 100.0 + (4.0 * i * Math.Cos(i / 4.0 * (Math.PI))))
.WithProperty(WidthProperty, i => 1.5 * i)
.WithProperty(HeightProperty, i => 1.5 * i)
.WithProperty(Shape.FillProperty, i => new SolidColorBrush(
Color.FromArgb(255, 0, 0, (byte)(255 - (byte)(12.5 * i)))));
foreach (var circle in circles)
MyCanvas.Children.Add(circle);
接下来,来看动画的实现。可以通过自定义动画类来控制动画的值:
public class LambdaDoubleAnimation : DoubleAnimation
{
public Func ValueGenerator { get; set; }
protected override double GetCurrentValueCore(double origin, double dst, AnimationClock clock)
{
return ValueGenerator(base.GetCurrentValueCore(origin, dst, clock));
}
}
这个类为提供了线性插值的功能,可以通过它来获取变换后的值并进行进一步的操作。
为了更好地管理动画集合,可以定义一个专门的集合类:
public class LambdaDoubleAnimationCollection : Collection
{
// ...
public LambdaDoubleAnimationCollection(int count, Func from, Func to,
Func duration, Func> valueGenerator)
{
for (int i = 0; i < count; ++i)
{
var lda = new LambdaDoubleAnimation
{
From = from(i),
To = to(i),
Duration = duration(i),
ValueGenerator = valueGenerator(i)
};
Add(lda);
}
}
public void BeginApplyAnimation(UIElement[] targets, DependencyProperty property)
{
for (int i = 0; i < Count; ++i)
targets[i].BeginAnimation(property, Items[i]);
}
}
这个集合类接受几个参数,包括值生成器。值生成器是一个二阶函数,它依赖于集合中元素的位置,并且其值依赖于动画过程中的插值值。
例如,可以创建一个动画,将螺旋形展开成正弦波:
var c = new LambdaDoubleAnimationCollection(
circles.Count,
i => Canvas.GetLeft(circles[i]),
i => 10.0 * i,
i => new Duration(TimeSpan.FromSeconds(2)),
i => j => 100.0 / j);
c.BeginApplyAnimation(circles.Cast().ToArray(), Canvas.LeftProperty);
虽然不能直接展示动画效果,但可以看到最终的布局效果。
public class LambdaDoubleAnimationCollection : Collection
{
// ...
public void BeginApplyAnimation(UIElement[] targets, DependencyProperty property)
{
for (int i = 0; i < Count; ++i)
{
Items[i].BeginTime = new TimeSpan(0);
targets[i].BeginAnimation(property, Items[i]);
}
}
public void BeginSequentialAnimation(UIElement[] targets, DependencyProperty property)
{
TimeSpan acc = new TimeSpan(0);
for (int i = 0; i < Items.Count; ++i)
{
Items[i].BeginTime = acc;
acc += Items[i].Duration.TimeSpan;
}
for (int i = 0; i < Count; ++i)
{
targets[i].BeginAnimation(property, Items[i]);
}
}
}