15

我有一个NSOutlineView显示目录层次结构的。它绑定到一个NSTreeController,它绑定到我管理文件系统节点的类。当文件系统事件发生时,我会在childrenkeypath 上触发 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"];
}

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

4

2 回答 2

10

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

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

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

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

  1. 询问其大纲视图-visibleRect
  2. 调用 super 以启动更改。
  3. 告诉大纲视图-scrollRectToVisible:

您可能必须通过在主线程上调度它来延迟第 3 步中的调用(因此它发生在当前通过运行循环的行程之后)。或者,或者,将步骤 3 替换为将可见矩形存储在某处并实现 NSOutlineViewDelegate-outlineView:didAdd/RemoveRowView:forRow:方法来检查此标志-scrollRectToVisible:,如果矩形非零,则从那里调用(记得将其重置为 NSZeroRect,这样它就不会尝试调整滚动每次添加或删除大纲行时)。

笨重,但合理的(?)路径允许您保留 NSTreeController。

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

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

于 2015-04-26T17:45:11.290 回答
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];
于 2015-04-24T06:33:33.267 回答