我有一个显示目录层次结构的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"];
}
这是在模型中,所以我无法访问视图。
答案 0 :(得分:9)
我还没有测试或尝试过以下内容,但我还是认为我还是试了一下。
首先,使用NS * Controller管理任何复杂的NSTableView或NSOutlineView都很痛苦,并且为了简单起见牺牲了精确的控制。如果您发现自己在这种情况下打架行为,请考虑在您自己的自定义控制器中实现数据源和委托协议(NSTableViewDataSource,NSTableViewDelegate或NSOutlineViewDataSource,NSOutlineViewDelegate)。
其次,Warren Burton关于解雇KVO通知的评论是相关的,因为你应该告诉负责的控制器(你的NSTreeController)有关变化,因为它是控制(和观察)该集合的人无论如何。更重要的是,您应该直接使用NSTreeController的add / insert / remove方法。你现在这样做的方式(每次你取消整个结构,然后再重置它)将导致整个树重新加载。由于控制器正在观察该集合,它告诉大纲视图刷新自己,可能允许它首先看到一个空的轮廓,然后是稍后进一步扩展的轮廓版本,这将失去你的用户'扩展状态等。通过树控制器修改模型将允许更智能,更有效的视图更新。
第三,您可以考虑通过继承NSTreeController并重写add / insert / remove方法来利用上面的第二点来执行以下操作:
-visibleRect
。-scrollRectToVisible:
。您可能必须通过在主线程上安排调用来延迟步骤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];