Правильный способ, чтобы переставить объекты, отправленные 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 через трассировку стека связывания.