将数组控制器初始化从nib移动到代码中断表视图绑定

时间:2011-10-13 21:29:26

标签: cocoa nstableview cocoa-bindings nsarraycontroller nswindowcontroller

  1. 我的窗口控制器子类是nib的所有者。
  2. 我在代码中,在我的文档子类中实例化我的数组控制器。文档和窗口控制器都在代码中使用它。
  3. 我像这样绑定表格列:文件所有者>> document._arrayController.arrangedObjects.attributeName 即可。
  4. 表格视图不显示任何行。
  5. 窗口控制器和文档类都不会收到与表视图相关的-addObserver消息。
  6. 显然我没有正确绑定到这个阵列控制器。我想我错过了关于表视图列如何绑定到数组的基本信息。

    在一些重构期间出现了这个问题。我曾经在nib中实例化数组控制器。该文档是文件所有者,并具有阵列控制器的插座。绑定看起来像我的阵列控制器> arrangeObjects>的attributeName 即可。一切都很好。

    由于文档处理通过数组控制器插入对象,我不认为窗口控制器应该负责创建它。窗口控制器是新的笔尖所有者,所以我从笔尖中删除了它。我现在在-makeWindowControllers的代码中创建它。 (我问过related question about initialization。)

    调试时我发现了别的东西。如果窗口控制器是表视图的数据源,我实现-numberOfRowsInDataSource

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

    表视图调用它,为所有列发送-addObserver消息,并使用绑定实际加载每个单元格的值。但是,在为给定单元格加载值时,不是在arrangedObjects中加载第n个对象的属性值,而是为整个对象列加载属性值。它将这些数组传递给值变换器(无法正确处理它们)并在文本单元格中显示数组description(它们不适合)。

    当窗口控制器是表视图的数据源但列使用绑定时,表视图应忽略-numberOfRowsInTableView的结果,或者根本不应该调用它。 (使用return 0响应选择器只是为了避免运行时错误。数据源仅在第一时间设置,以实现单元格的工具提示。)同样,所有这些都用于工作我在笔尖中创建数组控制器时很好。

    一些想法:

    1. 甚至可以使用IB将表列绑定到另一个对象拥有的数组控制器吗?
    2. 我是否需要将数组控制器放回到nib中,并让窗口控制器与文档实例共享它? (这听起来像是糟糕的设计。)
    3. 我应该有两个阵列控制器,一个用于窗口控制器,另一个用于文档吗?

    4. 加了:

      我使用表视图数据源以及绑定的原因是使用以下方法实现拖放重新排序:

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

1 个答案:

答案 0 :(得分:1)

一些想法:

首先,您不应该同时实现NSTableViewDataSource协议使用绑定 - 它通常是一个或另一个。如果您有特定的理由这样做,我会首先使用绑定启动您的应用程序,然后一步一步地从NSTableViewDataSource分层您想要的任何功能,以确保一切正常。有可用于支持工具提示的绑定。

接下来,我不是说这是唯一的方法,但我建议将ArrayController放回xib中。 NSController子类与绑定它们的控件之间似乎存在特殊关系 - 绑定检查器中的Controller Key:条目强烈暗示这一点,因为当您不使用它时它被禁用。我不确定,但我猜测当你通过那个大的keyPath绑定到达文档来获取arrayController时,那种魔力就不会发生了。

我也想知道为什么你会希望NSArrayController住在除了控件绑定到它的窗口之外的某个地方?相关的原因是你将WindowController 与文件共享 NSArrayController?

NSArrayControllers保持选择状态,所以确实每个窗口有一个,或者更抽象地说,它们会靠近UI,因此应该存在于每个需要一个的nib中。也就是说,除非你试图做一些非传统的事情,比如在多个窗口之间共享一个选择状态(即改变窗口A中的选择和窗口B中的相应控件也改变选择以匹配窗口A)。我将在下面探讨一下,但简而言之,我想不出你想要共享一个arrayController而不是将多个arrayControllers绑定到相同的底层数据的任何其他原因。

如果选择共享是你的目标,我认为你最好做一些事情,比如让文档在每个窗口中的nib创建的ArrayControllers的selectionIndexes上设置Key-Value Observances并让它们将选择传播到其他windows的arrayControllers。

我把它编码了;它似乎工作。我从Xcode开始使用标准的基于NSDocument的Cocoa应用程序模板,并在文档中添加了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的文件所有者。它有一个绑定到File's Owner.document.dataModel的NSArrayController,以及一个绑定到ArrayController.arrangedObjects.attributeName的列的NSTableView。

当我创建一个新文档时,会出现两个窗口,每个窗口都显示相同的内容。当我将tableView选项更改为一个时,另一个也会更改。

无论如何,希望这有帮助。