内容更新时,NSOutlineView会跳到顶部

时间:2015-04-20 03:22:22

标签: cocoa nsoutlineview nstreecontroller

我有一个显示目录层次结构的NSOutlineView。它绑定到NSTreeController,它绑定到管理文件系统节点的类。当发生文件系统事件时,我在children密钥路径上触发KVO通知,这会导致大纲视图更新。但是当它更新时,它会突然滚动到最顶端。我希望滚动位置保持不变。有什么想法吗?

这是FS事件发生时运行的代码:

- (void)URLWatcher:(CDEvents *)URLWatcher eventOccurred:(CDEvent *)event {
    [self willChangeValueForKey:@"children"];
    children = nil; // this will refreshed next time children is called
    [self didChangeValueForKey:@"children"];
}

这是在模型中,所以我无法访问视图。

2 个答案:

答案 0 :(得分:9)

我还没有测试或尝试过以下内容,但我还是认为我还是试了一下。

首先,使用NS * Controller管理任何复杂的NSTableView或NSOutlineView都很痛苦,并且为了简单起见牺牲了精确的控制。如果您发现自己在这种情况下打架行为,请考虑在您自己的自定义控制器中实现数据源和委托协议(NSTableViewDataSource,NSTableViewDelegate或NSOutlineViewDataSource,NSOutlineViewDelegate)。

其次,Warren Burton关于解雇KVO通知的评论是相关的,因为你应该告诉负责的控制器(你的NSTreeController)有关变化,因为它是控制(和观察)该集合的人无论如何。更重要的是,您应该直接使用NSTreeController的add / insert / remove方法。你现在这样做的方式(每次你取消整个结构,然后再重置它)将导致整个树重新加载。由于控制器正在观察该集合,它告诉大纲视图刷新自己,可能允许它首先看到一个空的轮廓,然后是稍后进一步扩展的轮廓版本,这将失去你的用户'扩展状态等。通过树控制器修改模型将允许更智能,更有效的视图更新。

第三,您可以考虑通过继承NSTreeController并重写add / insert / remove方法来利用上面的第二点来执行以下操作:

  1. 向大纲视图询问其-visibleRect
  2. 致电超级以启动更改。
  3. 将大纲视图告诉-scrollRectToVisible:
  4. 您可能必须通过在主线程上安排调用来延迟步骤3中的调用(因此它会在当前的运行循环之后发生)。或者,或者替换步骤3,在某处存储可见的rect并实现NSOutlineViewDelegate -outlineView:didAdd/RemoveRowView:forRow:方法以检查此标志,如果rect非零,则从那里调用-scrollRectToVisible:(记得将其重置为NSZeroRect,因此每次添加或删除轮廓行时都不会尝试调整滚动。

    Clunky,但是一个合理的(?)路径,允许你保留NSTreeController。

    第四,或者(和我去的方式),您可以完全删除NSTreeController并在您自己的控制器类中实现NSOutlineViewDataSource(和NSOutlineViewDelegate)协议,并让该控制器直接处理添加到树中或从树中删除结构体。然后它变得更干净,因为你不必担心KVO时间。在任何添加节点时,您可以注意到可见的rect,更新大纲视图,然后在同一方法中调整所有滚动并跳过运行循环。

    我希望这有帮助而且不会太漫无边际。 : - )

答案 1 :(得分:0)

我可以建议您保持当前滚动视图偏移并在发送KVO通知并更新大纲视图后恢复它。

- (void)updateOutlineView:(NSOutlineView *)outlineView
{
  // first save offset
  NSScrollView *scrollView = [outlineView enclosingScrollView];
  NSClipView *clipView = [scrollView contentView];
  NSPoint offset = clipView.bounds.origin;
  // send KVO notification of the 'children' keypath
  // ...
  // restore offset
  [clipView scrollPoint:offset];
}

看一下滚动点是绝对值。您可以根据更新的轮廓视图高度计算目标点。

//... before the notification sent
CGFloat height = [[[[scrollView documentView] frame] size] height];
CGFloat yValue = offset.y / height;
//... after the outline view updated
CGFloat newHeight = [[[[scrollView documentView] frame] size] height];
offset.y = newHeight * yValue;
[clipView scrollPoint:offset];