【发布时间】:2014-05-27 12:20:36
【问题描述】:
我正在尝试创建一种雷达。 Radar 是由 360 DrawingVisual(代表雷达波束)组成的 VisualCollection。雷达放置在 Viewbox 上。
class Radar : FrameworkElement
{
private VisualCollection visuals;
private Beam[] beams = new Beam[BEAM_POSITIONS_AMOUNT]; // all geometry calculation goes here
public Radar()
{
visuals = new VisualCollection(this);
for (int beamIndex = 0; beamIndex < BEAM_POSITIONS_AMOUNT; beamIndex++)
{
DrawingVisual dv = new DrawingVisual();
visuals.Add(dv);
using (DrawingContext dc = dv.RenderOpen())
{
dc.DrawGeometry(Brushes.Black, null, beams[beamIndex].Geometry);
}
}
DrawingVisual line = new DrawingVisual();
visuals.Add(line);
// DISCRETES_AMOUNT is about 500
this.Width = DISCRETES_AMOUNT * 2;
this.Height = DISCRETES_AMOUNT * 2;
}
public void Draw(int beamIndex, Brush brush)
{
using (DrawingContext dc = ((DrawingVisual)visuals[beamIndex]).RenderOpen())
{
dc.DrawGeometry(brush, null, beams[beamIndex].Geometry);
}
}
protected override Visual GetVisualChild(int index)
{
return visuals[index];
}
protected override int VisualChildrenCount
{
get { return visuals.Count; }
}
}
每个 DrawingVisual 都为 DrawingContext.DrawGeometry(画笔、钢笔、几何)预先计算几何。 Pen 为空,画笔是具有大约 500 个 GradientStops 的 LinearGradientBrush。画笔每隔几毫秒更新一次,在这个例子中假设是 16 毫秒。这就是变得滞后的原因。这是整体逻辑。
在 MainWindow() 构造函数中,我创建了雷达并启动了一个后台线程:
private Radar radar;
public MainWindow()
{
InitializeComponent();
radar = new Radar();
viewbox.Child = radar;
Thread t = new Thread(new ThreadStart(Run));
t.Start();
}
在Run()方法中有一个无限循环,生成随机画笔,调用Dispatcher.Invoke()并设置16ms的延迟:
private int beamIndex = 0;
private Random r = new Random();
private const int turnsPerMinute = 20;
private static long delay = 60 / turnsPerMinute * 1000 / (360 / 2);
private long deltaDelay = delay;
public void Run()
{
int beginTime = Environment.TickCount;
while (true)
{
GradientStopCollection gsc = new GradientStopCollection(DISCRETES_AMOUNT);
for (int i = 1; i < Settings.DISCRETES_AMOUNT + 1; i++)
{
byte color = (byte)r.Next(255);
gsc.Add(new GradientStop(Color.FromArgb(255, 0, color, 0), (double)i / (double)DISCRETES_AMOUNT));
}
LinearGradientBrush lgb = new LinearGradientBrush(gsc);
lgb.StartPoint = Beam.GradientStarts[beamIndex];
lgb.EndPoint = Beam.GradientStops[beamIndex];
lgb.Freeze();
viewbox.Dispatcher.Invoke(new Action( () =>
{
radar.Draw(beamIndex, lgb);
}));
beamIndex++;
if (beamIndex >= BEAM_POSITIONS_AMOUNT)
{
beamIndex = 0;
}
while (Environment.TickCount - beginTime < delay) { }
delay += deltaDelay;
}
}
每次调用 Invoke() 都会执行一件简单的事情:dc.DrawGeometry(),它会在当前的 beamIndex 下重绘光束。但是,有时似乎像在 UI 更新之前一样,radar.Draw() 被调用了几次,而不是每 16 毫秒绘制 1 个波束,而是每 32-64 毫秒绘制 2-4 个波束。这令人不安。我真的很想实现流畅的运动。我需要一个光束来绘制每个确切的时间段。不是这种随机的东西。这是我迄今为止尝试过的列表(没有任何帮助):
- 在 Canvas 中放置雷达;
- 使用 Task、BackgroundWorker、Timer、自定义 Microtimer.dll 并设置不同的线程优先级;
- 使用不同的方式实现延迟:Environment.TickCount、DateTime.Now.Ticks、Stopwatch.ElapsedMilliseconds;
- 将 LinearGradientBrush 更改为预定义的 SolidColorBrush;
- 使用 BeginInvoke() 代替 Invoke() 并更改调度程序优先级;
- 使用 InvalidateVisuals() 和丑陋的 DoEvents();
- 使用 BitmapCache、WriteableBitmap 和 RenderTargetBitmap(使用 DrawingContext.DrawImage(bitmap);
- 使用 360 多边形对象而不是 360 绘图视觉对象。这样我可以避免使用 Invoke() 方法。每个多边形的 Polygon.FillProperty 都绑定到 ObservableCollection,并实现了 INotifyPropertyChanged。如此简单的代码行 {brushCollection[beamIndex] = (new created and freeze brush)} 导致多边形 FillProperty 更新并且 UI 被重绘。但还是没有流畅的动作;
- 可能还有一些我可以忘记的小解决方法。
我没有尝试的:
- 使用工具绘制 3D(视口)绘制 2D 雷达;
- ...
所以,就是这样。我正在寻求帮助。
编辑:这些滞后与 PC 资源无关 - 没有延迟,雷达每秒可以完成大约 5 个完整的循环(移动速度非常快)。很可能是关于多线程/UI/Dispatcher 或其他我还没有理解的东西。
EDIT2:附加一个 .exe 文件,以便您查看实际情况:https://dl.dropboxusercontent.com/u/8761356/Radar.exe
EDIT3: DispatcherTimer(DispatcherPriority.Render) 也没有帮助。
【问题讨论】:
标签: wpf