首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何实现每16毫秒平滑的UI更新?

如何实现每16毫秒平滑的UI更新?
EN

Stack Overflow用户
提问于 2014-05-27 12:20:36
回答 3查看 960关注 0票数 7

我在尝试制造一种雷达。雷达是VisualCollection,它由360个DrawingVisual(代表雷达波束)组成。雷达被放置在视箱上。

代码语言:javascript
复制
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(画笔、笔、几何图形)的几何形状。钢笔为空,而画笔为LinearGradientBrush,约为500 GradientStops。这个刷子每隔几毫秒更新一次,比方说这个例子是16毫秒。这就是导致滞后的原因。这是总的逻辑。

在MainWindow()构造函数中,我创建了雷达并启动了一个后台线程:

代码语言:javascript
复制
    private Radar radar;

    public MainWindow()
    {
        InitializeComponent();

        radar = new Radar();
        viewbox.Child = radar;

        Thread t = new Thread(new ThreadStart(Run));
        t.Start();
    }

在Run()方法中有一个无限循环,其中生成随机画笔,调用Dispatcher.Invoke(),并设置16 ms的延迟:

代码语言:javascript
复制
    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毫秒绘制一个光束,而是每32-64毫秒绘制2-4个横梁。这是令人不安的。我真的很想实现流畅的运动。我需要一根横梁来按精确的时间绘制。不是这些随机的东西。这是我迄今尝试过的(没有任何帮助)的清单:

  • 在帆布上放置雷达;
  • 使用任务、BackgroundWorker、计时器、自定义Microtimer.dll和设置不同的线程优先顺序;
  • 采用不同的延迟实现方式: Environment.TickCount、DateTime.Now.Ticks、Stopwatch.ElapsedMilliseconds;
  • 将LinearGradientBrush改为预定义SolidColorBrush;
  • 使用BeginInvoke()代替调用()和更改调度程序优先级;
  • 使用InvalidateVisuals()和丑陋的DoEvents();
  • 使用BitmapCache,WriteableBitmap和RenderTargetBitmap (使用DrawingContext.DrawImage(位图));
  • 使用360个多边形对象而不是360 DrawingVisuals。这样我就可以避免使用Invoke()方法。将每个多边形的Polygon.FillProperty绑定到ObservableCollection,并实现了INotifyPropertyChanged。因此,简单的代码行{brushCollectionbeamIndex =(新创建和冻结的画笔)}导致多边形FillProperty更新和UI重新绘制。但仍然没有平稳的运动;
  • 也许还有一些小的解决办法我可以忘记。

我没有尝试的是:

  • 使用工具绘制3D (视图端口)绘制二维雷达;
  • ..。

所以,就是这样。我在乞求帮助。

编辑:这些延迟与PC资源无关--雷达每秒可以完成5圈左右(移动速度相当快)。很可能是关于多线程/UI/Dispatcher或者其他一些我还没有理解的东西。

EDIT2:附加了一个.exe文件,这样您就可以看到实际发生了什么:https://dl.dropboxusercontent.com/u/8761356/Radar.exe

EDIT3: DispatcherTimer(DispatcherPriority.Render)也没有帮上忙。

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2014-05-28 07:50:00

对于流畅的WPF动画,您应该使用CompositionTarget.Rendering事件。

不需要线程或干扰调度程序。该事件将在每一个新框架之前自动触发,类似于HTML的requestAnimationFrame()

在事件中,更新您的WPF场景,您就完成了!

在MSDN上有一个可用的完整示例

票数 3
EN

Stack Overflow用户

发布于 2014-05-27 12:25:36

您可以使用WPF性能套件检查一些图形瓶颈:

http://msdn.microsoft.com/es-es/library/aa969767(v=vs.110).aspx

射孔器是向您展示性能问题的工具。也许你用的是低性能的VGA卡?

票数 0
EN

Stack Overflow用户

发布于 2014-05-28 06:49:37

代码语言:javascript
复制
while (Environment.TickCount - beginTime < delay) { }
        delay += deltaDelay;

上面的顺序阻塞了线程。使用“等待Task.Delay(.)”它不像其对应的Thread.Sleep(.)那样阻塞线程。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/23889294

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档