Правильный способ, чтобы переставить объекты, отправленные NSTreeController после изменений в узлах в дереве?

Как правильно переставить объекты rangerangeObject для отправки в NSTreeController после изменений узлов в дереве? У меня есть пример приложения (полный код ниже) с использованием NSOutlineView и NSTreeController с простым деревом объектов Node.

В версии 1 приложения, когда вы редактируете имя узла, дерево не восстанавливается, пока вы не щелкнете по заголовку столбца или не воспользуетесь пунктом "Перегруппировать" в меню. Последний настроен для прямой отправки переупорядоченных объектов в NSTreeController.

В версии 2 я попытался отправить задание переставить объекты из метода setName: узла. Это не кажется хорошим решением, потому что это означает, что модель теперь знает представление / контроллер. Кроме того, он имеет побочный эффект того, что представление контура теряет фокус после переименования (если вы выберете узел и отредактируете его имя, полоса выбора изменится с синего на серый) по некоторым причинам (почему это так?).

NSArrayController имеет метод setAutomaticsRearrangesObjects: но 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;
}

2 ответа

Решение

По крайней мере, я частично могу ответить на свой собственный вопрос после просмотра примера приложения Apple iSpend. Их файл TransactionsController_Sorting.m включает в себя метод scheduleRearrangeObjects, который по-другому вызывает метод rangerangeObjects. Изменение моего собственного кода таким же образом означает включение этого фрагмента в метод setName:

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

С этим изменением контурный вид больше не теряет фокус после переименования узла. Осталось сделать этот код из модели в представлении / контроллере; TransactionsController_Sorting, кажется, также иллюстрирует, как это сделать. (Я до сих пор не понимаю, почему указанное выше изменение предотвращает потерю фокуса в виде структуры, у кого-нибудь есть объяснение?)

Другой ответ, как возможное объяснение

я верю rearrangeObjects а также fetch задерживаются до следующей итерации цикла выполнения. fetch по крайней мере, так вам сказано в документах:

Особые соображения
Начиная с OS X v10.4, результат этого метода откладывается до следующей итерации цикла выполнения, чтобы механизм представления ошибок мог предоставить обратную связь в виде листа.

В своих собственных экспериментах я могу использовать dispatch_async после rearrangeObjects чтобы получить код после перестановки. Другими словами, если я не dispatch_async следующий код rearrangeObjects это будет применено перед отложенной перестановкой. Это отличный способ вырвать ваши волосы.

В любом случае, я подозреваю, что вы теряете фокус, потому что свойство rangerangeObjects уничтожает контекст, в котором вы редактировали узел, так как оно перезагружает все дерево объектов, но если вы заставляете его немедленно выполняться, вы не теряете этот контекст.

[править] Обновите здесь. Я имел дело с rearrangeObjects и данные ядра не кажутся синхронными, и, конечно же, это не так. Я поймал arrayController, вызывающий dispatch_async через трассировку стека связывания.

Другие вопросы по тегам