我正在开发一个桌面Cocoa应用程序。在应用程序中,我有一个基于视图的NSOutlineView绑定到NSTreeController:
NSTreeController处于实体模式,由Core Data驱动。在底层模型图更改之前,一切都按预期工作。每当一个新对象插入到已注册的NSManagedObjectContext中时,NSTreeController就会刷新其内容,并且绑定的NSOutlineView会正确显示结果。控制器的内容按“标题”与NSSortDescriptor排序,并在应用程序启动期间设置此排序。唯一的缺点是即使在NSTreeController的首选项中选中了保留选择框,selectionIndexPath也不会改变。我希望在新节点出现在树中之前保留对所选对象的选择。
我已经将NSTreeController子类化,以调试在更改对象图时选择的内容。我可以看到NSTreeController通过KVO更改了它的内容,但是setContent:
方法没有被调用。比通过NSTreeControllerTreeNode KVO调用的setSelectionIndexPaths:
但参数包含先前的indexPath。
所以,要明确:
在初始阶段选择“文件夹2-3”。然后将“Folder 2-2”插入带有[NSEntityDescription insertNewObjectForEntityForName:@"Folder" inManagedObjectContext:managedObjectContext];
的NSManagedObjectContext:
我想保留选择“文件夹2-3”,因此我设置了“Preseve选择”,但似乎NSTreeController完全忽略了这个属性,或者我误解了一些东西。
如何强制NSTreeController保持其选择?
不幸的是,我的NSTreeController子类中没有调用任何突变方法(insertObject:atArrangedObjectIndexPath:
,insertObjects:atArrangedObjectIndexPaths:
等)。我已经覆盖了大多数工厂方法来调试底层的内容,这就是我在新的托管对象插入上下文时可以看到的内容:
-[FoldersTreeController observeValueForKeyPath:ofObject:change:context:] // Content observer, registered with: [self addObserver:self forKeyPath:@"content" options:NSKeyValueObservingOptionNew context:nil]
-[FoldersTreeController setSelectionIndexPaths:]
-[FoldersTreeController selectedNodes]
-[FoldersTreeController selectedNodes]
FoldersTreeController处于实体模式并绑定到Application委托的managedObjectContext。我有一个名为“Folders”的根实体,它有一个名为“children”的属性。这是与称为子文件夹的其他实体的多对多关系。子文件夹实体是文件夹的子类,因此它具有与其父文件相同的属性。正如您在第一个附加的屏幕截图中看到的那样,NSTreeController的实体已设置为Folders实体,并且它按预期工作。每当我将新的子文件夹插入到managedObjectContext中时,它就会出现在正确文件夹下的树中(作为子节点,按绑定到NSTreeController的NSSortDescriptor排序),但是没有调用NSTreeController变种方法,如果新插入的子文件夹出现在它列出了所有内容,但选择仍保持在同一位置。
我可以看到在应用程序启动期间调用了setContent:
方法,但仅此而已。似乎NSTreeController观察根节点(文件夹)并通过KVO以某种方式反映模型更改。 (因此,当我创建一个新的子文件夹并将其添加到其父[folder addChildrenObject:subfolder]
时,它出现在树中,但没有调用任何树变异方法。)
不幸的是,我无法直接使用NSTreeController变异方法(add:
,addChild:
,insert:
,insertChild:
),因为真正的应用程序会在后台线程中更新模型。后台线程使用自己的managedObjectContext,并将批量更改与mergeChangesFromContextDidSaveNotification
合并。这让我很疯狂,因为一切都很好,期待NSOutlineView的选择。当我将一组子文件夹从后台线程合并到主要的managedObjectContext中时,树会自动更新,但是我从合并之前选择的对象中丢失了选择。
我准备了一个小样本来证明问题:http://cl.ly/3k371n0c250P
答案 0 :(得分:0)
通过阅读文档和标题,NSTreeController使用NSIndexPaths来存储选择。这意味着它的选择思想是嵌入数组树的索引链。所以据他所知, 在你描述的情况下保留选择。这里的问题是你在"对象身份和#34;方面的选择。并且树控制器将选择定义为"一堆索引到嵌套数组"。您描述的行为是(AFAICT)NSTreeController的预期开箱即用行为。
如果你想通过对象标识保存选择,我的建议是继承NSTreeController并覆盖所有变异方法,以便在变异之前使用-selectedNodes
捕获当前选择,然后使用{{重新设置选择1}}通过在突变后向每个以前选择的节点询问其新-setSelectionIndexPaths:
来创建一个数组。
简而言之,如果您想要除股票行为以外的行为,您将不得不自己编写。我很好奇这有多难,所以我采取了一些似乎适用于我需要测试的案例的东西。这里&#t t:
-indexPath
编辑:它有点残酷(性能方面),但我也能为调用@interface SOObjectIdentitySelectionTreeController : NSTreeController
@end
@implementation SOObjectIdentitySelectionTreeController
{
NSArray* mTempSelection;
}
- (void)dealloc
{
[mTempSelection release];
[super dealloc];
}
- (void)p_saveSelection
{
[mTempSelection release];
mTempSelection = [self.selectedNodes copy];
}
- (void)p_restoreSelection
{
NSMutableArray* array = [NSMutableArray array];
for (NSTreeNode* node in mTempSelection)
{
if (node.indexPath.length)
{
[array addObject: node.indexPath];
}
}
[self setSelectionIndexPaths: array];
}
- (void)insertObject:(id)object atArrangedObjectIndexPath:(NSIndexPath *)indexPath
{
[self p_saveSelection];
[super insertObject: object atArrangedObjectIndexPath: indexPath];
[self p_restoreSelection];
}
- (void)insertObjects:(NSArray *)objects atArrangedObjectIndexPaths:(NSArray *)indexPaths
{
[self p_saveSelection];
[super insertObjects:objects atArrangedObjectIndexPaths:indexPaths];
[self p_restoreSelection];
}
- (void)removeObjectAtArrangedObjectIndexPath:(NSIndexPath *)indexPath
{
[self p_saveSelection];
[super removeObjectAtArrangedObjectIndexPath:indexPath];
[self p_restoreSelection];
}
- (void)removeObjectsAtArrangedObjectIndexPaths:(NSArray *)indexPaths
{
[self p_saveSelection];
[super removeObjectsAtArrangedObjectIndexPaths:indexPaths];
[self p_restoreSelection];
}
@end
而工作。希望这会有所帮助:
-setContent:
当然,这依赖于实际上相同的对象。我不确定你的(我不知道)后台操作对CoreData的保证是什么。