objective-c - NSTreeController/NSOutlineView失去选择

标签 objective-c cocoa core-data nsoutlineview nstreecontroller

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

 

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

我将NSTreeController细分为调试对象图更改期间选择发生的情况。我可以看到NSTreeController通过KVO更改了它的内容,但是没有调用setContent:方法。比通过NSTreeControllerTreeNode KVO调用的setSelectionIndexPaths:要多,但是该参数包含以前的indexPath。



因此,要明确:


最高一级


文件夹1-1
文件夹1-2

顶层2


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



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


最高一级


文件夹1-1
文件夹1-2

顶层2


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



我想将选择保留在“ Folder 2-3”上,因此我设置了“ Preseve selection”,但似乎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。我有一个名为“文件夹”的根实体,它有一个名为“子项”的属性。这是与另一个称为子文件夹的实体的一对多关系。 Subfolders实体是Folders的子类,因此它具有与其父代相同的属性。如您在第一幅截图中所见,NSTreeController的实体已设置为Folders实体,并且按预期工作。每当我将新的子文件夹插入ManagedObjectContext时,它就会出现在树中的适当文件夹下(作为子节点,由绑定到NSTreeController的NSSortDescriptor排序),但是没有调用任何NSTreeController突变方法,并且如果新插入的子文件夹出现在较早的位置,该列表将下拉所有内容,但选择内容将保持不变。

我可以看到在应用程序启动期间调用了setContent:方法,仅此而已。看来NSTreeController观察根节点(文件夹)并通过KVO以某种方式反映模型更改。 (因此,当我创建一个新的子文件夹并使用[folder addChildrenObject:subfolder]将其添加到其父文件夹中时,它会出现在树中,但不会调用任何树突变方法。)

不幸的是,我不能直接使用NSTreeController突变方法(add:addChild:insert:insertChild:),因为实际应用会在后台线程中更新模型。后台线程使用其自己的ManagedObjectContext并使用mergeChangesFromContextDidSaveNotification批量合并更改。这让我发疯,因为一切正常,请期待NSOutlineView的选择。当我将一堆子文件夹从后台线程合并到主ManagedObjectContext中时,树会自行更新,但是我丢失了合并前所选对象的选择。

更新2:

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


展开“文件夹1”,然后选择“选择子文件夹9999”
按“新建子文件夹”。它将在后台操作中批量创建50个子文件夹。
如您所见,即使在MyTreeController.m中更改内容之前将其保存,选择也会从“子文件夹9999”中丢失。

最佳答案

通过阅读文档和标题,NSTreeController使用NSIndexPaths存储选择。这意味着它的选择思想是将索引链链接到嵌套数组树中。据其所知,它会保留您所描述的情况下的选择。这里的问题是您正在考虑根据“对象标识”进行选择,并且树控制器将选择定义为“嵌套数组中的一堆索引”。您描述的行为是(AFAICT)NSTreeController的预期即用型行为。

如果要通过对象标识保留选择,我的建议是子类NSTreeController并覆盖所有变异方法,以便在变异之前使用-selectedNodes捕获当前选择,然后使用创建了数组的-setSelectionIndexPaths:重新设置选择通过在突变后向每个先前选择的节点询问其新的-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:的调用。希望这可以帮助:

- (NSTreeNode*)nodeOfObject: (id)object
{
    NSMutableArray* stack = [NSMutableArray arrayWithObject: _rootNode];
    while (stack.count)
    {
        NSTreeNode* node = stack.lastObject;
        [stack removeLastObject];
        if (node.representedObject == object)
            return node;

        [stack addObjectsFromArray: node.childNodes];
    }

    return nil;
}

- (void)setContent:(id)content
{
    NSArray* selectedObjects = [[self.selectedObjects copy] autorelease];

    [super setContent: content];

    NSMutableArray* array = [NSMutableArray array];
    for (id object in selectedObjects)
    {
        NSTreeNode* node = [self nodeOfObject: object];
        if (node.indexPath.length)
        {
            [array addObject: node.indexPath];
        }
    }

    [self setSelectionIndexPaths: array];
}


当然,这取决于实际上是相同的对象。我不确定您的后台操作对CoreData的保证是什么。

关于objective-c - NSTreeController/NSOutlineView失去选择,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16356451/

相关文章:

objective-c - 有没有办法从您的应用程序获取对另一个应用程序窗口的引用?

ios - 在 Objective C 中前向声明结构

ios - URLForUbiquityContainerIdentifier 总是返回 nil

python - 在 Mac 上检测全屏

iphone - 检测 CGPoint 是否在多边形内

cocoa - 文档交换时将 NSScrollView 内容设置为左上角而不是左下角

cocoa - 添加到多/多对多核心数据关系

ios - Realm 与 iCloud 兼容吗?

ios - 钛模块开发 : Invalid method createView

iphone - 从 fetchedResultsController 中删除对象 fetchedObjects