NSTreeController / NSOutlineView失去了它的选择

时间:2013-05-03 10:09:35

标签: objective-c cocoa core-data nsoutlineview nstreecontroller

我正在开发一个桌面Cocoa应用程序。在应用程序中,我有一个基于视图的NSOutlineView绑定到NSTreeController:

enter image description here enter image description here

NSTreeController处于实体模式,由Core Data驱动。在底层模型图更改之前,一切都按预期工作。每当一个新对象插入到已注册的NSManagedObjectContext中时,NSTreeController就会刷新其内容,并且绑定的NSOutlineView会正确显示结果。控制器的内容按“标题”与NSSortDescriptor排序,并在应用程序启动期间设置此排序。唯一的缺点是即使在NSTreeController的首选项中选中了保留选择框,selectionIndexPath也不会改变。我希望在新节点出现在树中之前保留对所选对象的选择。

我已经将NSTreeController子类化,以调试在更改对象图时选择的内容。我可以看到NSTreeController通过KVO更改了它的内容,但是setContent:方法没有被调用。比通过NSTreeControllerTreeNode KVO调用的setSelectionIndexPaths:但参数包含先前的indexPath。

enter image description here

所以,要明确:

  • 顶级1
    • 文件夹1-1
    • 文件夹1-2
  • 顶级2
    • 文件夹2-1
    • *文件夹2-3< ==已选择
    • 文件夹2-4

在初始阶段选择“文件夹2-3”。然后将“Folder 2-2”插入带有[NSEntityDescription insertNewObjectForEntityForName:@"Folder" inManagedObjectContext:managedObjectContext];的NSManagedObjectContext:

  • 顶级1
    • 文件夹1-1
    • 文件夹1-2
  • 顶级2
    • 文件夹2-1
    • *文件夹2-2< ==已选择
    • 文件夹2-3
    • 文件夹2-4

我想保留选择“文件夹2-3”,因此我设置了“Preseve选择”,但似乎NSTreeController完全忽略了这个属性,或者我误解了一些东西。

如何强制NSTreeController保持其选择?

UPDATE1:

不幸的是,我的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中时,树会自动更新,但是我从合并之前选择的对象中丢失了选择。

UPDATE2:

我准备了一个小样本来证明问题:http://cl.ly/3k371n0c250P

  1. 展开“文件夹1”,然后选择“选择”子文件夹9999“
  2. 按“新建子文件夹”。它将在批处理的后台操作中创建50个子文件夹。
  3. 正如您所看到的,即使在MyTreeController.m中内容更改之前保存了选项,“Subfolder 9999”中的选择也会丢失。

1 个答案:

答案 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的保证是什么。