【问题标题】:Can I redraw a custom NSView while another control on the same window is being used?我可以在使用同一窗口上的另一个控件时重绘自定义 NSView 吗?
【发布时间】:2012-12-18 13:18:02
【问题描述】:

在我正在处理的一个程序中,我正在执行大量数据处理,并且希望能够绘制一个自定义 NSView 以直观地表示该过程。

使用 GCD 从主线程处理数据处理(使用 dispatch_sync 作为需要保留的顺序):

for (int i=0; i<iterations; i++) {

    dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // Add a data crunching task to the queue!
    });
}

当处理开始时,我初始化了一个计时器,用于要求自定义显示定期重绘(一旦所有处理任务完成,这个计时器就失效了):

-(void)onTick:(NSTimer *)timer {

    [_customView setNeedsDisplay:YES];
    // Update some other UI object (labels etc.)    
}

我按预期工作,但是我现在注意到的唯一问题是,如果我在同一窗口上单击并按住按钮,或者在进程进行时开始拖动滑块,重绘在我放开鼠标之前,自定义视图会被阻止!

有什么办法可以防止这种情况发生,最好不要将所有控件移到单独的窗口上!

【问题讨论】:

  • dispatch_sync 本身并不“保持秩序”;使用它可以保持顺序,因为dispatch_sync 一直等到分派的块返回(这就是上下文中“同步”的含义)。因此,您在调度下一个块之前等待每个块完成,这意味着您的循环与直接调用每个块的循环完全相同,只是您的循环涉及无效的后台队列。要在不阻塞主线程的情况下串行处理块,请创建并使用串行队列,或者使用 NSOperationQueue 并将每个操作设置为依赖于它之前的操作。
  • 这很有意义。所以 dispatch_async 所以我不是挂在循环上等待返回,而是一个串行队列来维护后台线程的顺序。然后我的 GUI 会正确更新!非常感谢您花时间解释这一点!

标签: cocoa user-interface window nsview


【解决方案1】:

是的,计时器在 runloop 上,而在拖放/跟踪模式下它不会被触发,因为 runloop 对于普通模式不是先进的

将其添加到所需的模式中:

NSRunLoop *runloop = [NSRunLoop currentRunLoop];
NSTimer *timer = [NSTimer timerWithTimeInterval:0.1 target:self selector:@selector(myTimerAction:) userInfo:nil repeats:YES];
[runloop addTimer:timer forMode:NSRunLoopCommonModes];
[runloop addTimer:timer forMode: NSEventTrackingRunLoopMode];

编辑:runloop 基本上是苹果的 while 循环,用于将任何事件分派到应用程序。它会根据所谓的模式过滤它分派的事件。

【讨论】:

  • 非常感谢,我对此一无所知。我虽然可能是 setNeedsDisplay: 被阻止了。这解决了其他 UI 控件干扰计时器的问题。还有另一个问题 - 如果我尝试调整窗口大小,则更新会变得不稳定,如果我开始调整大小并在仍然按住鼠标的情况下停止,则在我松开之前,显示器不会重绘。这是调整大小期间 Windows 绘图行为的问题吗?
  • @tom1990:在鼠标跟踪事件之间等待一堆数字运算是个问题。异步执行此操作,并将您的块分派回主线程以发出需要更新 UI 的信号。
  • 他是异步的。你是什么意思?
  • @Daij-Djan:不在这个问题的代码中,他没有。我在对该问题的评论中进行了详细说明。
  • 脸红抱歉,你当然是对的。我只读过“数据处理是通过使用 GCD 在主线程之外处理的......”
【解决方案2】:

感谢大家的问题、答案和 cmets。

使用 NSEventTrackingRunLoopMode 解决了我使用给定参数不断重绘 OpenGL 视图的情况。

class MyOpenGLView: NSOpenGLView {

  var timer: Timer?

  required init?(coder: NSCoder) {
    super.init(coder: coder)

    timer = Timer.scheduledTimer(withTimeInterval: 1.0/60, repeats: true) { (t) in
      self.needsDisplay = true
    }

    let runloop = RunLoop.current
    runloop.add(timer!, forMode: .commonModes)
    // runloop.add(timer!, forMode: .eventTrackingRunLoopMode)
  }

  override func draw(_ dirtyRect: NSRect) {
    // draw moving 3D-cubes
  }

}

现在即使我拖动滑块来更新其参数,它也会继续绘制。此外,调整窗口大小变得平滑。

对于其他解决方案“异步执行”,很有趣!我稍后会试一试。谢谢!

已编辑:

我改变主意使用 NSRunLoopCommonModes (RunLoopMode.commonModes),而不是 NSEventTrackingRunLoopMode。

两者都运行良好,但 Apple 的文档暗示 Common Modes 在我的情况下更好。

https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多