NSTableView 中的自定义单元格
使用自定义单元格填充 NSTableView 的最佳方法是什么?
当我填充标准数据时,我总是使用 Cocoa Bindings,当使用自定义单元格填充表时,我总是使用数据源。我想知道是否有一种方法可以混合这两个概念以获得最佳设计。
遗憾的是,使用 Xcode 3(以及 IBPlugins)不是一个选择。
What's the best way to populate an NSTableView with custom cells?
I always utilize Cocoa Bindings when I'm populating standard data, and I always utilize datasources when populated tables with custom cells. What I'm wondering is if there's a way of mixing the two concepts for an optimal design.
Using Xcode 3 (and therefore, IBPlugins) is sadly not an option.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
我个人不会混合绑定数据和数据源。我在尝试这样做时除了痛苦之外什么也没遇到。我确实有一些方法可能会对您有所帮助。
我能够做的一件事是在 IB 中设置一个自定义单元类,它是 IB 知道的单元之一的子类,然后覆盖您需要的任何内容以使其完成你想要什么。但首先是一些背景知识和一些错过的尝试:
当绑定基于单元格的 NSTableView 时,您通常在列本身而不是列内的单元格上设置绑定。如果您在表列中使用自定义 NSCell 子类,您会注意到像
value
这样的绑定在该列上不再可用,这与单元格是 NSTextFieldCell 时不同。我尝试通过设置value
绑定并将其设置为 NSTextFieldCell,然后切换单元格来解决 IB 的问题——绑定仍然出现在绑定检查器中,但它总是在运行时崩溃出现此错误:[valueForUndefinedKey:]: 这个类与键值的键值编码不兼容。
这让我想到了对 IB 知道如何绑定到的单元格之一进行子类化的方法。我创建了 NSTextFieldCell 的子类,深入到表列中的“文本字段单元格 - 文本单元格”,然后在身份检查器中设置我的自定义子类。我能够确认绑定仍然有效,并且 IB 仍然将其视为 NSTextFieldCell。从那里,我可以在自定义单元类中重写我想要的任何方法并获得自定义行为。我没有理由相信你不能用图像单元来做到这一点。当然,这是一种虚假的方法,但根据自定义单元格的“自定义”程度,它可能比编写一堆自定义代码来连接数据源要好。
经过进一步的实验,我发现这是一个“IB 问题”,而不是真正的 NSTableView/绑定问题。还有另一种很好的解决方法。
假设您想要使用自定义单元格,并且想要将其绑定到某个任意模型对象。您将 NSTableColumn Value 绑定绑定到 NSArrayController,该 NSArrayController 提供自定义模型对象列表,每个对象都有一个属性,将其称为
dataForCustomCell
,它返回自定义单元格执行其操作所需的任何内容。您将设置一个 TextFieldCell 列(如 IB 中的默认值),然后将 NSTableColumn 的value
绑定绑定到 Array Controller >arrangedObjects
并输入模型关键路径dataForCustomCell
。此时,假设dataForCustomCell
返回的对象实现了 NSCopying(如果没有,您的应用程序将会崩溃,但这与此时此刻并不真正相关),如果您运行您的应用程序,您会看到的是NSTextFieldCell 将在dataForCustomCell
返回的对象上调用- (NSString*)description
并将该文本放入单元格中。现在是有趣的部分:在您拥有的对象(NSView、NSViewController 等)中的
-awakeFromNib
时间,替换 dataCell(如果您愿意,还可以替换 headerCell),如下所示:在 NSTableColumn 而不是单元格本身上,您可以交换单元格,而不必担心重新连接任何绑定。在您的自定义单元类中,重写
-(void)setObjectValue:
,您将在运行时从绑定机制中获得调用,推入来自dataForCustomCell
的对象与表的当前绘制行对应的模型对象上的 code> 属性。 (您还会收到一个为每个单元格传递 nil 的调用,但忽略它或将其传递给 super 似乎是安全的。)这种方法的一个缺点是您只能获得 NSTextFieldCell 具有的一个“值”绑定。解决方法是将该 Value 绑定绑定到模型中更大/更高的“颗粒”,然后根据需要在
-setObjectValue:
实现中向下钻取并扇出多个值。它并不完美,但它是“几行代码”修复,而不是“无数行代码”修复。
或者,假设您的目标是相当新的 MacOS 版本,您也可以使用基于视图的 NSTableView。它们非常好,并且以比基于 NSCell 的表更明智的方式处理绑定。但这是一种完全不同的做事方式,因此很难说你的任务将如何映射到它。 Apple 开发者网站上有一个精彩视频< /a> 让您快速了解基于 NSView 的 NSTableView。
I, personally, wouldn't mix bound data and data sources. I've encountered nothing but pain trying to do that. I do have a couple of approaches that might help you though.
One thing I have been able to do is set a custom cell class in IB that is a subclass of one of the cells that IB knows about, and then override whatever you need to in order to make it do what you want. But first some background, and some missed attempts:
When binding cell-based NSTableViews, you typically set the bindings on the column itself and not the cell within the column. If you use a custom NSCell subclass in a table column, you'll notice that bindings like
value
are no longer available on the column, unlike when the cell is an NSTextFieldCell. I've tried to sorta trick IB by setting thevalue
binding with it set up as an NSTextFieldCell, then switching out the cell -- the binding still appears in the bindings inspector, but it always crashes at runtime with this error:[<NSTableColumn 0x10252e910> valueForUndefinedKey:]: this class is not key value coding-compliant for the key value.
That brings me to the approach of subclassing one of the cells that IB knows how to bind to. I made a subclass of NSTextFieldCell, drilled-down to the "Text Field Cell - Text Cell" within the Table Column, and then set my custom subclass in the Identity Inspector. I was able to confirm that bindings still worked and IB still treats it like an NSTextFieldCell. From there, I could override any methods I wanted in my custom cell class and get custom behavior. I have no reason to believe that you couldn't do this with image cells as well. Naturally, this is kind of a bogus approach, but depending on just how "custom" your custom cells are, it might beat writing a bunch of custom code to hook up a data source.
What I found out upon further experimentation is that this is an "IB problem" and not really an NSTableView/bindings problem. And there's another pretty good way around it.
Say you want to use a custom cell, and you want to bind it to some arbitrary model object. You have your NSTableColumn Value binding bound to an NSArrayController that's vending a list of custom model objects, each with a property, call it
dataForCustomCell
that returns whatever the custom cell needs to do it's thing. You would set up a TextFieldCell column (like the default in IB), then bind thevalue
binding of the NSTableColumn to Array Controller >arrangedObjects
and enter the model key pathdataForCustomCell
. At this point, assuming the object returned bydataForCustomCell
implements NSCopying (if it doesn't your app will crash, but that's not really relevant right this second) what you would see if you ran your app is that the NSTextFieldCell would call- (NSString*)description
on the object returned bydataForCustomCell
and put that text in the cell.Now for the fun part: At
-awakeFromNib
time in your owning object (NSView, NSViewController, etc, etc), replace the dataCell (and the headerCell if you like), like this:Since the bindings are on the NSTableColumn and not the cell itself, you can swap out the cell without worrying about re-hooking up any bindings. In your custom cell class, override
-(void)setObjectValue:
and you'll get a call from the bindings mechanism at run-time, pushing in the object that came from thedataForCustomCell
property on the model object corresponding to the currently drawing row of the table. (You'll also get a call passing in nil for every cell, but it seems safe to ignore that or just pass it on to super.)One drawback of this approach is that you only get the one "Value" binding that NSTextFieldCell has. The workaround for that is to bind that Value binding to a bigger/higher "granule" in your model and then drill down and fan out multiple values in your implementation of
-setObjectValue:
if needed.It ain't perfect, but it's a 'couple of lines of code' fix, instead of a 'gazillions of lines of code' fix.
Alternately, assuming you're targeting fairly recent versions of MacOS, you can also work with view-based NSTableViews. They're pretty nice, and handle bindings in a much more sensible way than NSCell-based tables do. It's a completely different way of doing things though, so it's hard to say how your task would map to it. There's a great video on the Apple developer site that brings you up to speed on NSView-based NSTableView.