Перемещение инициализации контроллера массива из пера в код нарушает привязки табличного представления - PullRequest
2 голосов
/ 14 октября 2011
  1. Мой подкласс оконного контроллера является владельцем пера.
  2. Я создаю экземпляр моего контроллера массива в коде в моем подклассе документа.Контроллер документа и окна использует его в коде.
  3. Я связываю столбцы таблицы следующим образом: Владелец файла >> document._arrayController.arrangedObjects.attributeName .
  4. Таблицапредставление не отображает никаких строк.
  5. Ни контроллер окна, ни класс документа не получают -addObserver сообщений, связанных с представлением таблицы.

Очевидно, я не привязываюсь к этому контроллеру массиваправильно.Я думаю, что упускаю что-то фундаментальное в том, как столбцы табличного представления привязываются к массивам.

Эта проблема возникла во время некоторого рефакторинга.Я использовал для создания экземпляра контроллера массива в кончике.Документ был владельцем файла и имел выход для контроллера массива.Привязки выглядели как Мой контроллер массива >ограмма объектов> имя_ атрибута .Все работало нормально.

Так как документ обрабатывает вставку объектов через контроллер массива, я не думал, что контроллер окна должен отвечать за его создание.Контроллер окна является новым владельцем пера, поэтому я удалил его из пера.Теперь я создаю его в коде -makeWindowControllers.(Я задал этот связанный вопрос об инициализации .)

При отладке этого я обнаружил кое-что еще.Если контроллер окна является источником данных табличного представления, и я реализую -numberOfRowsInDataSource:

return [[self.document._arrayController arrangedObjects] count];

, табличное представление вызывает его, отправляет сообщения -addObserver для всех столбцов и фактически загружает значения для каждой ячейки, используяпривязки.Но при загрузке значения для данной ячейки вместо загрузки значения атрибута для n-го объекта в arrangedObjects он загружает значения атрибута для всего столбца объектов .Он передает эти массивы в преобразователи значений (которые не могут правильно их обрабатывать) и отображает description массива в текстовых ячейках (в которых они не помещаются).

Когда контроллер окна является табличным представлениемисточник данных, но столбцы используют привязки, табличное представление должно игнорировать результат -numberOfRowsInTableView или, возможно, не должно вызывать его вообще.(Ответ на селектор с помощью return 0 просто позволяет избежать ошибки времени выполнения. Источник данных настраивается в первую очередь для реализации всплывающих подсказок для ячеек. ) И снова, все это работало нормально, когда ясоздал контроллер массива в кончике.

Некоторые мысли:

  1. Можно ли даже использовать IB для привязки столбцов таблицы к контроллеру массива, который принадлежит другому объекту?
  2. Нужно ли вернуть контроллер массива обратно в перо, и чтобы оконный контроллер поделился им с экземпляром документа?(Это звучит как ужасный дизайн.)
  3. Должен ли я иметь два контроллера массива, один для оконного контроллера, а другой для документа?

Добавлено:

Причина, по которой я использовал источник данных табличного представления вместе с привязками, заключается в реализации переупорядочения методом перетаскивания с использованием следующих методов:

  • tableView:writeRowsWithIndexes:toPasteboard:
  • tableView:validateDrop:proposedRow:proposedDropOperation:
  • tableView:acceptDrop:row:dropOperation:

1 Ответ

1 голос
/ 06 января 2012

Несколько мыслей:

Во-первых, вы не должны реализовывать оба протокола NSTableViewDataSource и использовать привязки - обычно это одно или другое. Если у вас есть конкретная причина для этого, я бы сначала запустил ваше приложение, используя только привязки, а затем добавлял все необходимые функции из NSTableViewDataSource по одному шагу за раз, чтобы убедиться, что все работает. Для поддержки всплывающих подсказок доступны привязки.

Далее, я не говорю, что это единственный подход, но я бы предложил вернуть ArrayController обратно в xib. Кажется, между подклассами NSController и связанными с ними элементами управления существует особая связь - ключ контроллера: запись в инспекторе привязок намекает на это, поскольку она отключена, когда вы ее не используете. Я не знаю наверняка, но я предполагаю, что когда вы связываете этот большой keyPath, чтобы вернуться к документу, чтобы получить arrayController, это волшебство не происходит.

Мне также интересно, почему вы хотите, чтобы NSArrayController жил в другом месте, кроме окна, чьи элементы управления привязаны к нему? И в связи с этим, почему у вас есть WindowController общий доступ NSArrayController с документом?

NSArrayControllers хранят состояние выбора, поэтому действительно имеет смысл, что они будут по одному на окно или более абстрактно, что они будут жить близко к пользовательскому интерфейсу и, следовательно, должны существовать в каждом перо, которое нуждается в нем. То есть, если вы не пытаетесь сделать что-то необычное, например разделить одно состояние выбора между несколькими окнами (то есть изменить выбор в окне A, а соответствующие элементы управления в окне B также изменить выбор в соответствии с окном A). Я расскажу об этом ниже, но вкратце, я не могу думать о какой-либо другой причине, по которой вы захотите использовать arrayController, если несколько arrayController связаны с одними и теми же базовыми данными.

Если бы вашей целью было совместное использование выборки, я думаю, что вам было бы лучше сделать что-то вроде установки в документе значений ключа-значения в selectionIndexes ArrayControllers, созданных с помощью пера в каждом окне, и попросить их распространить выборку на Контроллеры массива других окон.

Я закодировал это; Похоже на работу. Я начал со стандартного шаблона приложения Какао на основе NSDocument в XCode, добавил в документ свойство dataModel и подделал его с некоторыми данными. Затем я сделал два окна во время makeWindowControllers, затем добавил наблюдения и т. Д., Чтобы их выбор следовал друг за другом. Казалось, все хорошо сошлось.

В одном кодовом блоке:

#import <Cocoa/Cocoa.h>

@interface SODocument : NSDocument
@property (retain) id dataModel;
@end

@interface SOWindowController : NSWindowController
@property (retain) IBOutlet NSArrayController* arrayController;
@end

@implementation SODocument
@synthesize dataModel = _dataModel;

- (id)init
{
    self = [super init];
    if (self) 
    {
        // Make some fake data to bind to
        NSMutableDictionary* item1 = [NSMutableDictionary dictionaryWithObjectsAndKeys: @"Item 1", @"attributeName", nil];
        NSMutableDictionary* item2 = [NSMutableDictionary dictionaryWithObjectsAndKeys: @"Item 2", @"attributeName", nil];
        NSMutableDictionary* item3 = [NSMutableDictionary dictionaryWithObjectsAndKeys: @"Item 3", @"attributeName", nil];        
        _dataModel = [[NSMutableArray arrayWithObjects: item1, item2, item3, nil] retain];
    }
    return self;
}

- (void)dealloc
{
    [_dataModel release];
    [super dealloc];
}

- (NSString *)windowNibName
{
    return @"SODocument";
}

- (void)makeWindowControllers
{
    SOWindowController* wc1 = [[[SOWindowController alloc] initWithWindowNibName: [self windowNibName]] autorelease];
    [self addWindowController: wc1];

    SOWindowController* wc2 = [[[SOWindowController alloc] initWithWindowNibName: [self windowNibName]] autorelease];
    [self addWindowController: wc2];
}

- (void)addWindowController:(NSWindowController *)windowController
{
    [super addWindowController: windowController];
    [windowController addObserver:self forKeyPath: @"arrayController.selectionIndexes" options: 0 context: [SODocument class]];
}

- (void)removeWindowController:(NSWindowController *)windowController
{
    [windowController removeObserver:self forKeyPath: @"arrayController.selectionIndexes" context: [SODocument class]];
    [super removeWindowController:windowController];
}

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 
{
    if ([SODocument class] == context && [@"arrayController.selectionIndexes" isEqualToString: keyPath])
    {
        NSIndexSet* selectionIndexes = ((SOWindowController*)object).arrayController.selectionIndexes;
        for (SOWindowController* wc in self.windowControllers)
        {
            if (![selectionIndexes isEqualToIndexSet: wc.arrayController.selectionIndexes])
            {
                wc.arrayController.selectionIndexes = selectionIndexes;
            }
        }
    }
}
@end

@implementation SOWindowController
@synthesize arrayController = _arrayController;

-(void)dealloc
{
    [_arrayController release];
    [super dealloc];
}
@end

Перо документа имеет владельца файла SOWindowController. Он имеет NSArrayController, привязанный к File's Owner.document.dataModel, и NSTableView с одним столбцом, привязанным к ArrayController.arrangedObjects.attributeName.

При создании нового документа появляются два окна, каждое из которых показывает одно и то же. Когда я изменяю выбор tableView в одном, другой изменяется также.

В любом случае, надеюсь, это поможет.

...