NSOutlineView在内容更新时跳到顶部

15

我有一个显示目录层次结构的 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"];
}

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


你是重新加载整个大纲视图还是只更新需要更新的特定项目? - rocky
@rocky 我并没有显式地重新加载大纲视图。树控制器会为我重新加载它。 - tbodt
哪个对象收到了这个消息?相关的FS节点表示或控制器?我不太清楚为什么你在这里触发KVO。还有,你如何刷新子项。能展示一下吗?我构建了一个快速树控制器/NSOutlineView,只要我不触发KVO,在更新时就不会重置视图。 - Warren Burton
@WarrenBurton 1. 我发布的代码在node目录中。2. 我正在触发KVO,告诉树控制器(它正在观察每个节点)子项已更新。如果您真的需要查看我的代码,可以在https://github.com/vindo-app/vindo/blob/fsevents/Vindo/DirectoryItem.m上找到它。 - tbodt
2个回答

10

我没有测试或尝试以下内容,但还是想试试。

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

其次,沃伦·伯顿(Warren Burton)关于触发KVO通知的评论非常相关,因为您应该将更改告知负责的控制器(您的NSTreeController),因为它控制(并观察)该集合。更重要的是,您应该直接使用NSTreeController的add / insert / remove方法。您现在所做的方式(每次将整个结构设置为nullify然后稍后重新设置)将导致整个树重新加载。由于控制器正在观察该集合,因此它会告诉大纲视图刷新自身,可能会使其首先看到一个空大纲,然后再看到进一步展开的大纲版本,这将失去用户的扩展状态等。通过树控制器修改模型将允许更智能,更高效的视图更新。

第三,您可以通过子类化NSTreeController并覆盖add / insert / remove方法来考虑利用我上面的第二点:

  1. 要求大纲视图提供其 -visibleRect
  2. 调用 super 来启动更改。
  3. 告诉大纲视图 -scrollRectToVisible:

您可能需要通过在主线程上安排它(因此它发生在运行循环的当前旅行之后)来延迟步骤3中的调用。或者,替代地,用某个地方存储可见矩形并实现NSOutlineViewDelegate -outlineView:didAdd / RemoveRowView:forRow: 方法以检查此标志,然后从那里调用 -scrollRectToVisible:如果矩形是非零的(请记住将其重置为NSZeroRect,以便每次添加或删除时都不尝试调整滚动条)。

虽然略显笨重,但这是一个可以让你保留NSTreeController的合理路径。

第四,或者(也是我会选择的方式),你可以完全放弃NSTreeController,在你自己的控制器类中实现NSOutlineViewDataSource(和NSOutlineViewDelegate)协议,并让该控制器直接处理添加或删除树形结构。这样会更加清晰,因为你不必担心KVO的时机。在任何添加节点的情况下,你都可以记录可见矩形、更新大纲视图,然后在同一个方法和运行循环中调整滚动。

希望这可以帮助你,也希望我的表述不算太啰嗦。:-)


我决定尝试第四个选项。可能会授予悬赏。 - tbodt

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];

那在我的情况下不会起作用。为了澄清,我已经在我的问题中添加了相关代码。 - tbodt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接