【问题标题】:NSOutlineView jumps up to the top when content updatesNSOutlineView 在内容更新时跳到顶部
【发布时间】:2015-06-26 15:27:16
【问题描述】:

我有一个 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 我没有明确地重新加载大纲视图。树控制器为我重新加载它。
  • 什么对象得到这个消息?相关的 FS 节点表示或控制器?我有点不清楚你为什么在这里解雇 KVO。还有你如何让孩子们振作起来。你能证明这一点。我构建了一个快速树控制器/nsoutlineview,只要我不触发 KVO,我就不会在更新时重置视图。
  • @WarrenBurton 1. 我发布的代码在目录节点中。 2. 我正在触发 KVO 来告诉树控制器(它正在观察每个节点)孩子们已经更新了。如果你真的需要看我的代码,它在 github 上github.com/vindo-app/vindo/blob/fsevents/Vindo/DirectoryItem.m

标签: cocoa nsoutlineview nstreecontroller


【解决方案1】:

我可以建议您保留当前的滚动视图偏移量,并在您发送 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];

【讨论】:

  • 这在我的情况下不起作用。为了澄清,我已将相关代码添加到我的问题中。
【解决方案2】:

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

首先,用 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 时间。在添加任何节点时,您可以注意可见的矩形,更新大纲视图,然后在同一方法中调整所有滚动并通过运行循环。

我希望这会有所帮助,不要太漫无目的。 :-)

【讨论】:

  • 我决定尝试第四个选项。可能会奖励赏金。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-10-02
  • 2016-04-18
  • 2014-03-11
  • 2021-10-20
  • 2020-03-20
相关资源
最近更新 更多