1

我目前遇到了 GDI 和计时器的一个非常奇怪的问题。

首先是代码:

class Graph : UserControl {
  private System.Threading.Timer timer;
  private int refreshRate = 25;              //Hz (redrawings per second)
  private float offsetX = 0;                 //X offset for moving graph

  public Graph() {
    timer = new System.Threading.Timer(timerTick);
  }

  private void timerTick(object data) {
    offsetX -= 1 / refreshRate;
    this.Invalidate();
  }

  public void timerStart() {
    timer.Change(0, 1000 / refreshRate);
  }

  private void onPaint(object sender, PaintEventArgs e) {
    //350 lines of code for drawing the graph
    //Here the offsetX will be used to move the graph
  }
}

我在这里尝试将绘制的图形在特定时间移动到左侧的 1 个“图形单元”。所以我使用了一个计时器,它会以小步骤改变偏移量,所以这将是一个平滑的移动(这就是 refreshRate )。

在第一个视图中,此代码有效,但后来我发现以下问题:如果我使用 1 (1Hz) 的刷新率,它将以 1 步 1(图形单元)向左移动我的图形。如果我增加刷新率,我的动作会变慢。在 20 FPS 时它有点慢,在 200 FPS 时它真的很慢..

所以这是我尝试的:

  1. 我使用了 Refresh 或 Update 而不是 Invalidate

  2. 我使用普通线程(带睡眠)而不是计时器

两个代码更改都没有改变结果..

除了用定时器移动之外,我还可以用鼠标移动图形,如果定时器正在运行,我仍然可以用鼠标平滑地移动图形。所以这不是性能问题..

想到画队列有问题,因为我刷新比画完还快?(但为什么我可以用鼠标平滑地移动图形?!)

所以我需要一点帮助。谢谢

4

2 回答 2

1

在 20 FPS 时它有点慢,在 200 FPS 时它真的很慢..

这里有一个根本问题;要获得 200fps 的刷新率,您需要每 5 毫秒重新绘制一次。这永远不会发生。无论您将计时器的间隔设置为什么,它的分辨率都限制在大约 10-15 毫秒。因此,您的最佳帧速率约为 66-100fps,这是假设您的绘图代码花费零时间,当然事实并非如此。

最重要的是,您使用的System.Threading.Timer不是在 UI 线程执行它的回调,因此从那里调用 Invalidate() 可能甚至不安全。您通常不使用System.Threading.Timerfor UI 代码。

您可能想尝试此处显示的所谓多媒体计时器,它使用 CPU 的高性能计时器并提供大约 1 毫秒的分辨率。

于 2011-09-21T20:55:51.643 回答
0

您可以尝试稍微改变问题。

由于您不知道您的绘画需要多长时间,因此您无法对此做出假设。

您所知道的是您希望过渡发生的时间量,例如 30 秒,如下所示:

private MAX_TRANSITION_TIME = 30; //seconds

此时,您还可以通过记录操作开始的时间来跟踪自操作开始以来经过的时间量,比如说

private DateTime _startMoment;

现在你可以做的是确保你手头有正确的例程,并根据 startMoment 和 now 之间的差异计算你的位置

var elapsedTime = DateTime.Now.Subtract(_startMoment).Milliseconds;
var elapsedPercent = elapsedTime / MAX_TRANSITION_TIME * 1000.0 ;

从现在开始,您可以做的是根据您所用时间的百分比进行绘制。

因此,在您的 OnPaint 完成后,您应该能够刷新它。如果您使用一个 UI 计时器,您可以这样做:

 private void onPaint(object sender, PaintEventArgs e) {
       Timer1.Enabled = false;

     //350 lines of (adjusted)code go here

     If (ElapsedPercent<1)
     {
         Timer1.Enabled=True;
     }
     else
     {
         // you may need to perform the last draw setting the ElapsedPercent 
         // to 1. This is because it is very unlikely that your 
         // last call will happen at the very last millisecond
     }
 }

其中 Timer1 必须是 Timer 控件。

在 Interval 事件中,您只需编写

 _startMoment = DateTime.Now();
 this.Invalidate();

希望这可以帮助

于 2011-09-21T21:05:02.443 回答