1

在更改树中的节点后,将重新排列对象发送到 NSTreeController的适当方法是什么?我有一个示例应用程序(下面的完整代码),它使用 NSOutlineView 和 NSTreeController 以及一个简单的 Node 对象树。

在应用程序的版本 1 中,当您编辑节点的名称时,直到您单击列标题或使用菜单中的“重新排列”项,树才会重新排列。后者设置为直接将重新排列对象发送到 NSTreeController。

在版本 2 中,我尝试从节点的 setName: 方法发送重新排列对象。这似乎不是一个好的解决方案,因为这意味着模型现在已经了解了视图/控制器。此外,它的副作用是大纲视图在重命名后失去焦点(如果您选择一个节点并编辑其名称,选择栏会从蓝色变为灰色)出于某种原因(为什么会这样?)。

NSArrayController 有一个方法setAutomaticallyRearrangesObjects:但是 NSTreeController 没有?那么解决这个问题的适当方法是什么?

/* example.m

    Compile version 1:
        gcc -framework Cocoa -o Version1 example.m
    Compile version 2:
        gcc -framework Cocoa -o Version2 -D REARRANGE_FROM_SETNAME example.m

*/

#import <Cocoa/Cocoa.h>

NSTreeController *treeController;
NSOutlineView    *outlineView;
NSScrollView     *scrollView;

@interface Node : NSObject {
    NSString *name;
    NSArray *children;
}
@end

@implementation Node
- (id) initWithName: (NSString*) theName children: (id) theChildren
{
    if (self = [super init]) {
        name = [theName retain];
        children = [theChildren retain];
    }
    return self;
}

- (void) setName: (NSString*) new
{
    [name autorelease];
    name = [new retain];
#ifdef REARRANGE_FROM_SETNAME
    [treeController rearrangeObjects];
#endif
}
@end

NSArray *createSortDescriptors()
{
    return [NSArray arrayWithObject: [NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES]];
}

void createTheTreeController()
{
    Node *childNode1 = [[[Node alloc] initWithName:@"B" children:[NSArray array]] autorelease];
    Node *childNode2 = [[[Node alloc] initWithName:@"C" children:[NSArray array]] autorelease];
    Node *childNode3 = [[[Node alloc] initWithName:@"D" children:[NSArray array]] autorelease];

    Node *topNode1 = [[[Node alloc] initWithName:@"A" children:[NSArray arrayWithObjects:childNode1,childNode2,childNode3,nil]] autorelease];
    Node *topNode2 = [[[Node alloc] initWithName:@"E" children:[NSArray array]] autorelease];
    Node *topNode3 = [[[Node alloc] initWithName:@"F" children:[NSArray array]] autorelease];

    NSArray *topNodes = [NSArray arrayWithObjects:topNode1,topNode2,topNode3,nil];

    treeController = [[[NSTreeController alloc] initWithContent:topNodes] autorelease];
    [treeController setAvoidsEmptySelection:NO];
    [treeController setChildrenKeyPath:@"children"];
    [treeController setSortDescriptors:createSortDescriptors()];
}

void createTheOutlineView()
{
    outlineView = [[[NSOutlineView alloc] initWithFrame:NSMakeRect(0, 0, 284, 200)] autorelease];
    [outlineView bind:@"content" toObject:treeController withKeyPath:@"arrangedObjects" options:nil];
    [outlineView bind:@"sortDescriptors" toObject:treeController withKeyPath:@"sortDescriptors" options:nil];
    [outlineView bind:@"selectionIndexPaths" toObject:treeController withKeyPath:@"selectionIndexPaths" options:nil];

    NSTableColumn *column = [[[NSTableColumn alloc] initWithIdentifier:@"NameColumn"] autorelease];
    [[column headerCell] setStringValue:@"Name"];
    [outlineView addTableColumn:column];
    [outlineView setOutlineTableColumn:column];
    [column bind:@"value" toObject:treeController withKeyPath:@"arrangedObjects.name" options:nil];
    [column setWidth:250];

    scrollView = [[[NSScrollView alloc] initWithFrame:NSMakeRect(0, 0, 300, 200)] autorelease];
    [scrollView setDocumentView:outlineView];
    [scrollView setHasVerticalScroller:YES];
}

void createTheWindow()
{
    id window = [[[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 300, 200)
        styleMask:NSTitledWindowMask backing:NSBackingStoreBuffered defer:NO]
            autorelease];    
    [window cascadeTopLeftFromPoint:NSMakePoint(20,20)];
    [window setTitle:@"Window"];
    [window makeKeyAndOrderFront:nil];

    [[window contentView] addSubview:scrollView];
}

void createTheMenuBar()
{
    id menubar = [[NSMenu new] autorelease];
    id appMenuItem = [[NSMenuItem new] autorelease];
    [menubar addItem:appMenuItem];
    [NSApp setMainMenu:menubar];
    id appMenu = [[NSMenu new] autorelease];
#ifndef REARRANGE_FROM_SETNAME
    id rearrangeMenuItem = [[[NSMenuItem alloc] initWithTitle:@"Rearrange"
        action:@selector(rearrangeObjects) keyEquivalent:@"r"] autorelease];
    [rearrangeMenuItem setTarget: treeController];
    [appMenu addItem:rearrangeMenuItem];
#endif
    id quitMenuItem = [[[NSMenuItem alloc] initWithTitle:@"Quit"
        action:@selector(terminate:) keyEquivalent:@"q"] autorelease];
    [appMenu addItem:quitMenuItem];
    [appMenuItem setSubmenu:appMenu];
}

void setUpAutoReleasePoolAndApplication()
{
    [NSAutoreleasePool new];
    [NSApplication sharedApplication];
    [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
}

void activateAppAndRun()
{
    [NSApp activateIgnoringOtherApps:YES];
    [NSApp run];
}

int main(int argc, const char * argv[])
{
    setUpAutoReleasePoolAndApplication();
    createTheTreeController();
    createTheOutlineView();
    createTheWindow();
    createTheMenuBar();
    activateAppAndRun();
    return 0;
}
4

2 回答 2

1

在查看了 Apple 的iSpend 示例应用程序之后,我至少能够部分回答我自己的问题。他们的文件 TransactionsController_Sorting.m 包含一个方法 scheduleRearrangeObjects,它以不同的方式调用重新排列对象。以相同的方式更改我自己的代码意味着在 setName: 方法中包含此代码段:

#ifdef REARRANGE_FROM_SETNAME
    // Commented out: [treeController rearrangeObjects];
    [treeController performSelector:@selector(rearrangeObjects) withObject:nil afterDelay:0.0];
#endif

通过此更改,大纲视图在重命名节点后不再失去焦点。现在剩下要做的就是将此代码从模型中取出并放入视图/控制器中;TransactionsController_Sorting 似乎也说明了如何做到这一点。(我仍然不明白为什么上述更改会阻止大纲视图失去焦点,有人有解释吗?)

于 2013-10-04T18:10:13.520 回答
0

另一个答案,作为可能的解释

我相信rearrangeObjects并且fetch被延迟到下一次运行循环迭代。 fetch至少在文档中告诉你:

特殊注意事项
从 OS X v10.4 开始,此方法的结果将延迟到 runloop 的下一次迭代,以便错误呈现机制可以以表格形式提供反馈。

在我自己的实验中,我可以使用dispatch_asyncafterrearrangeObjects在重新排列后执行代码。换句话说,如果我不dispatch_async遵循它的代码rearrangeObjects,它将在延迟重新排列之前应用。这是撕掉头发的好方法。

无论如何,我怀疑您正在失去焦点,因为 rerangeObjects 在重新加载整个对象树时会破坏您正在编辑节点的上下文,但是如果您强制它立即执行,则不会丢失该上下文。

[编辑] 在这里更新。 我正在处理rearrangeObjects核心数据似乎不同步,果然,它不是。我通过绑定堆栈跟踪捕获了一个调用 dispatch_async 的 arrayController。

于 2014-05-21T03:07:15.807 回答