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

tbo*_*odt 15 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"];
}
Run Code Online (Sandbox Code Playgroud)

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

Jos*_*zzi 9

我没有测试或试过以下,但我想我还是试了一下.

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

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

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

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

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

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

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

我希望这有所帮助,而且不是太漫无边际.:-)


Ast*_*ria 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];
}
Run Code Online (Sandbox Code Playgroud)

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

//... 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];
Run Code Online (Sandbox Code Playgroud)