首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >NSTreeController/NSOutlineView失去其选择

NSTreeController/NSOutlineView失去其选择
EN

Stack Overflow用户
提问于 2013-05-03 10:09:34
回答 1查看 1.8K关注 0票数 1

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

NSTreeController处于实体模式,由核心数据驱动。一切按预期工作,直到下垫模型图发生变化。每当一个新对象插入到注册的NSManagedObjectContext中时,NSTreeController刷新其内容,绑定的NSOutlineView正确地显示结果。控制器的内容按照“标题”与NSSortDescriptor排序,我和我在应用程序启动期间设置了这个排序。唯一的缺点是,即使在NSTreeController的首选项中选中了保存选择框,selectionIndexPath也不会改变。我希望保留在新节点出现在树中之前选定的对象上的选择。

我对NSTreeController进行了子类化,以调试对象图更改期间所选内容发生的情况。我可以看到,NSTreeController通过KVO改变了它的内容,但是setContent:方法没有被调用。而不是通过setSelectionIndexPaths: KVO调用的NSTreeControllerTreeNode,但是参数包含前面的indexPath。

因此,要明确的是:

  • 顶层1
    • 文件夹1-1
    • 文件夹1-2

  • 顶层2级
    • 文件夹2-1
    • *选定文件夹2-3 <==
    • 文件夹2-4

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

  • 顶层1
    • 文件夹1-1
    • 文件夹1-2

  • 顶层2级
    • 文件夹2-1
    • *选定文件夹2-2 <==
    • 文件夹2-3
    • 文件夹2-4

我希望将选择保留在“文件夹2-3”上,因此我设置了“预选”,但是似乎NSTreeController完全忽略了这个属性,或者我误解了什么。

我怎样才能强迫NSTreeController保留它的选择?

UPDATE1:

不幸的是,没有任何一种突变方法(insertObject:atArrangedObjectIndexPath:insertObjects:atArrangedObjectIndexPaths:等)。曾经调用过我的NSTreeController子类。我已经覆盖了大多数工厂方法来调试引擎盖下发生的事情,当一个新的托管对象插入到上下文中时,我可以看到这一点:

代码语言:javascript
复制
-[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处于实体模式,并绑定到应用程序委托的managedObjectContext。我有一个名为“文件夹”的根实体,它有一个名为“子”的属性。它与另一个名为子文件夹的实体有很多关系。子文件夹实体是文件夹的子类,因此它具有与其父类相同的属性。正如您在第一个附加的屏幕截图中看到的那样,NSTreeController的实体已经设置为文件夹实体,并且它正在按预期工作。每当我将一个新的子文件夹插入到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中的内容更改之前保存了“子文件夹9999”,所选内容也将丢失。
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2013-05-03 11:05:56

通过读取文档和标头,NSTreeController使用NSIndexPaths存储选择。这意味着它的选择思想是一系列索引到嵌套数组树中。因此,据它所知,它在你描述的情况下保留了选择。这里的问题是从“对象标识”的角度考虑选择,树控制器将选择定义为“嵌套数组中的一组索引”。您描述的行为是NSTreeController预期的开箱即用行为.

如果您希望通过对象标识保存选择,我的建议是子类NSTreeController并覆盖所有的变异方法,以便在突变之前使用-selectedNodes捕获当前的选择,然后使用一个数组重新设置所选内容,该数组通过在突变后向每个以前选定的节点请求其新的-indexPath来创建。

简而言之,如果你想要的是股票行为以外的行为,你必须自己写。我很好奇这会有多难,所以我尝试了一些似乎对我费心测试的案例有用的东西。这是:

代码语言:javascript
复制
@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:调用找到一些有用的东西。希望这能帮上忙:

代码语言:javascript
复制
- (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的保证是什么。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/16356451

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档