是否有必要在 NSView 子类中重写 bind:toObject:withKeyPath:options: 来实现绑定?

发布于 2024-07-11 11:20:59 字数 3362 浏览 4 评论 0原文

我有一个 NSView 子类,它具有我想要可绑定的属性。 我在子类中实现了以下内容:

myView.h:

@property (readwrite, retain) NSArray *representedObjects;

myView.m:

@synthesize representedObjects;

+(void)initialize
{
    [self exposeBinding: @"representedObjects"];
}


-(void)bind:(NSString *)binding toObject:(id)observableController withKeyPath:(NSString *)keyPath options:(NSDictionary *)options
{
    if ([binding isEqualToString:@"representedObjects"]) {
        [observableController addObserver: self forKeyPath:@"arrangedObjects" options:NSKeyValueChangeNewKey context:nil];
    } else {
        [super bind: binding toObject:observableController withKeyPath:keyPath options: options];
    }
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if ([keyPath isEqualToString:@"arrangedObjects"]) {
        [self setRepresentedObjects: [object arrangedObjects]];
    }
}

然后我在 -[AppController awakeFromNib] 中创建到 arrayController 的绑定:

[myView bind:@"representedObjects" toObject:arrayController withKeyPath:@"arrangedObjects" options: nil];

这是实现绑定的正确方法吗? 它涉及很多样板代码,这让我觉得我做错了什么。

我认为 NSObject 会自动实现我在 -bind:toObject:withKeyPath:options: 中手动完成的操作,但事实似乎并非如此。 如果我注释掉 -bind:toObject:withKeyPath:options: ,则永远不会调用 setRepresentedObjects 方法。

附加信息: 我做了更多的调查,并得出结论,我原来的方法是正确的,你必须重写 -bind:toObject:withKeyPath:options: 。 这是 的引用Cocoa 绑定编程主题:绑定如何工作?

在其 bind:toObject:withKeyPath:options: 方法中,对象至少必须执行以下操作:

  • 确定正在设置哪个绑定
  • 记录它使用什么键路径和什么选项绑定到什么对象
  • 注册为所绑定对象的键路径的观察者,以便接收更改通知

清单 2 中的代码示例显示了 Joystick 的 bind:toObject:withKeyPath:options: 方法的部分实现,仅处理角度绑定。

清单 2 Joystick 类的 bind:toObject:withKeyPath:options 方法的部分实现:

<块引用>
static void *AngleBindingContext = (void *)@"JoystickAngle"; 
 
  - (void)绑定:(NSString *)绑定 
   toObject:(id)observableObject 
   withKeyPath:(NSString *)keyPath 
   选项:(NSDictionary *)选项 
  { 
   // 观察 observableObject 的变化 -- 注意,传递绑定标识符 
   // 作为上下文,所以你可以在observeValueForKeyPath:...中得到它 
   // 这样您就可以轻松确定需要更新的内容。 
 
  if ([绑定 isEqualToString:@"angle"]) 
   { 
      [observableObject addObserver:self 
                     forKeyPath:keyPath 
                    选项:0 
                    上下文:AngleBindingContext]; 
 
      // 注册什么对象和什么keypath 
      // 与此绑定关联 
      ObservableObjectForAngle = [observableObject 保留]; 
      观察到的KeyPathForAngle = [keyPath副本]; 
 
      // 记录值转换器,如果有的话 
      角度值转换器 = nil; 
      NSString *vtName = [选项 objectForKey:@"NSValueTransformerName"]; 
      if (vtName!= nil) 
      { 
          角度值转换器 = [NSValueTransformer 
              valueTransformerForName:vtName]; 
      } 
   } 
   // 执行继续... 
  

这清楚地表明 Joystick 类(它是 NSView 子类)需要重写 -bind:toObject:withKeyPath:options:

我觉得这很令人惊讶。 我对这个结论表示怀疑,因为我没有发现其他代码示例可以做到这一点。 然而,正如苹果官方文档所说,我应该覆盖 -bind:toObject:withKeyPath:options: 我得出结论,这是正确的方法。

如果有人能证明我错了,我会很高兴!

I have an NSView subclass which has property which I want to be bindable. I've implemented the following in the subclass:

myView.h:

@property (readwrite, retain) NSArray *representedObjects;

myView.m:

@synthesize representedObjects;

+(void)initialize
{
    [self exposeBinding: @"representedObjects"];
}


-(void)bind:(NSString *)binding toObject:(id)observableController withKeyPath:(NSString *)keyPath options:(NSDictionary *)options
{
    if ([binding isEqualToString:@"representedObjects"]) {
        [observableController addObserver: self forKeyPath:@"arrangedObjects" options:NSKeyValueChangeNewKey context:nil];
    } else {
        [super bind: binding toObject:observableController withKeyPath:keyPath options: options];
    }
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if ([keyPath isEqualToString:@"arrangedObjects"]) {
        [self setRepresentedObjects: [object arrangedObjects]];
    }
}

I then create the binding to an arrayController in -[AppController awakeFromNib]:

[myView bind:@"representedObjects" toObject:arrayController withKeyPath:@"arrangedObjects" options: nil];

Is this the correct way of implementing binding? It involves a lot of boiler plate code which makes me think that I'm doing something wrong.

I thought that NSObject would automagically implement what I have done manually in -bind:toObject:withKeyPath:options: but this doesn't seem to be the case. If I comment out my -bind:toObject:withKeyPath:options: the setRepresentedObjects method is never called.

Additional info:
I've done some more investigating and have reached the conclusion that my original approach is correct and you do have to over ride -bind:toObject:withKeyPath:options:. Here's a quote from Cocoa Bindings Programming Topics: How Do Bindings Work?:

In its bind:toObject:withKeyPath:options: method an object must as a minimum do the following:

  • Determine which binding is being set
  • Record what object it is being bound to using what keypath and with what options
  • Register as an observer of the keypath of the object to which it is bound so that it receives notification of changes

The code sample in Listing 2 shows a partial implementation of Joystick’s bind:toObject:withKeyPath:options: method dealing with just the angle binding.

Listing 2 Partial implementation of the bind:toObject:withKeyPath:options method for the Joystick class:

static void *AngleBindingContext = (void *)@"JoystickAngle";
 
- (void)bind:(NSString *)binding
 toObject:(id)observableObject
 withKeyPath:(NSString *)keyPath
 options:(NSDictionary *)options
{
 // Observe the observableObject for changes -- note, pass binding identifier
 // as the context, so you get that back in observeValueForKeyPath:...
 // This way you can easily determine what needs to be updated.
 
if ([binding isEqualToString:@"angle"])
 {
    [observableObject addObserver:self
                   forKeyPath:keyPath
                  options:0
                  context:AngleBindingContext];
 
    // Register what object and what keypath are
    // associated with this binding
    observedObjectForAngle = [observableObject retain];
    observedKeyPathForAngle = [keyPath copy];
 
    // Record the value transformer, if there is one
    angleValueTransformer = nil;
    NSString *vtName = [options objectForKey:@"NSValueTransformerName"];
    if (vtName != nil)
    {
        angleValueTransformer = [NSValueTransformer
            valueTransformerForName:vtName];
    }
 }
 // Implementation continues...

This clearly shows that the Joystick class (which is an NSView subclass) needs to override -bind:toObject:withKeyPath:options:.

I find this surprising. I was skeptical of this conclusion as I have found no other code samples that do this. However, as the offical Apple documentation says I should over ride -bind:toObject:withKeyPath:options: I conclude that it is the correct approach.

I would be very happy if someone could prove me wrong!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

一瞬间的火花 2024-07-18 11:20:59

不,您不需要那个粘合代码。

你所说的“似乎并非如此”是什么意思? 如果忽略它会发生什么?

No, you shouldn’t need that glue code.

What do you mean by “doesn’t seem to be the case”? What happens if you omit it?

没︽人懂的悲伤 2024-07-18 11:20:59

不,没有必要重写bind:

正如 Peter Hosey 在之前答案的评论中所写,您可以调用 exposeBinding: 并实现 KVC 和 KVO 兼容的访问器和设置器。

MyView.h:

@interface MyView : NSView {
    NSArray *_representedObjects;
}

// IBOutlet is not required for bindings, but by adding it you can ALSO use
// an outlet
@property (readonly, retain) IBOutlet NSArray *representedObjects;

@end

MyView.m:

+ (void)initialize {
    [self exposeBinding:@"representedObjects"];
}

// Use a custom setter, because presumably, the view needs to re-draw
- (void)setRepresentedObjects:(NSArray *)representedObjects {
    [self willChangeValueForKey:@"representedObjects"];
    // Based on automatic garbage collection
    _representedObjects = representedObjects;
    [self didChangeValueForKey:@"representedObjects"];

    [self setNeedsDisplayInRect:[self visibleRect]];
}

然后您可以通过编程方式设置绑定:

[myView bind:@"representedObjects" toObject:arrayController withKeyPath:@"arrangedObjects" options: nil];

但是,要在 Interface Builder 中设置绑定,您必须创建一个自定义调色板。

No, it is not necessary to override bind:.

As Peter Hosey wrote in the comment to the earlier answer, you can call exposeBinding: and implement KVC- and KVO-compliant accessors and setters.

MyView.h:

@interface MyView : NSView {
    NSArray *_representedObjects;
}

// IBOutlet is not required for bindings, but by adding it you can ALSO use
// an outlet
@property (readonly, retain) IBOutlet NSArray *representedObjects;

@end

MyView.m:

+ (void)initialize {
    [self exposeBinding:@"representedObjects"];
}

// Use a custom setter, because presumably, the view needs to re-draw
- (void)setRepresentedObjects:(NSArray *)representedObjects {
    [self willChangeValueForKey:@"representedObjects"];
    // Based on automatic garbage collection
    _representedObjects = representedObjects;
    [self didChangeValueForKey:@"representedObjects"];

    [self setNeedsDisplayInRect:[self visibleRect]];
}

Then you can set the binding programmatically:

[myView bind:@"representedObjects" toObject:arrayController withKeyPath:@"arrangedObjects" options: nil];

To set the binding in Interface Builder, however, you must create a custom palette.

咋地 2024-07-18 11:20:59

如果您想在自定义视图中实现绑定,那么您肯定需要在该视图中实现 -bind:toObject:withKeyPath:options: 。 您在 myView.m 中的实现非常正确。

You definitely DO need to implement -bind:toObject:withKeyPath:options: in a custom view if you want to implement bindings in that view. Your implementation in myView.m is pretty much spot on.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文