【问题标题】:Subclassing NSPopUpButton to add a bindable property子类化 NSPopUpButton 以添加可绑定属性
【发布时间】:2023-04-06 05:01:01
【问题描述】:

我正在尝试将可绑定属性添加到自定义 NSPopUpButton 子类。

我创建了一个“selectedKey”属性,用于存储与所选菜单项关联的 NSString。

在控件初始化中,我将 self 设置为按钮目标和按钮的操作 (valueChanged:),然后根据用户选择设置“selectedKey”:

@interface MyPopUpButton : NSPopUpButton {
    NSMutableDictionary *_items;
    NSString *_selectedKey;    
}

@property(nonatomic, readwrite, copy) NSString* selectedKey;
- (void)addItemWithTitle:(NSString *)title andKey:(NSString *)key;

@end

@implementation MyPopUpButton
- (instancetype)initWithFrame:(NSRect)frameRect {

    self = [super initWithFrame:frameRect];
    if (self) {

        _items = [NSMutableDictionary new];
        [NSObject exposeBinding:@"selectedKey"];
        [super setTarget:self];
        [super setAction:@selector(valueChanged:)];

    }
    return self;

}

- (void)addItemWithTitle:(NSString *)title andKey:(NSString *)key {

    [super addItemWithTitle:title];
    [_items setValue:title forKey:key];

}

- (void)valueChanged:(id)sender {

    for (NSString *aKey in [_items allKeys]) {
        if ([[_items valueForKey:aKey] isEqualToString:[self titleOfSelectedItem]]) {
            self.selectedKey = aKey;
        }
    }

}

- (void)setSelectedKey:(NSString *)selectedKey {

    [self willChangeValueForKey:@"selectedKey"];
    _selectedKey = selectedKey;
    [self didChangeValueForKey:@"selectedKey"];

    [self selectItemWithTitle:[_items valueForKey:selectedKey]];

}
@end

这似乎按预期工作:当用户更改 PopUpButton 选择时,“selectedKey”属性会更改。

很遗憾,尝试绑定此属性不起作用。

[selectButton bind:@"selectedKey" toObject:savingDictionary withKeyPath:key options:@{NSContinuouslyUpdatesValueBindingOption : @YES }]

更改选择时,绑定对象未进行相应更新。

我做错了什么?

【问题讨论】:

  • selectButton是什么对象?
  • @PaulPatterson selectButton 是一个 MyPopUpButton 实例。它是在运行时(不是在 IB 中)创建并使用我自己的 - (void)addItemWithTitle:(NSString *)title andKey:(NSString *)key 方法填充的,以便填充它的菜单项和它的 _items 字典。
  • 绕过您的问题:不需要自定义绑定。现有的绑定为您提供了很大的灵活性。您可以将内容、内容值和内容对象绑定到单独但相关的事物。 Content 是基础对象,Values 是这些对象中显示内容的关键路径,Objects 也是 Content 元素中作为弹出窗口的objectValue 返回的关键路径。内容对象还控制选定对象绑定映射到的内容。
  • @KenThomases 我经常为 NSPopUpButton 使用标准绑定(例如选定的值或选定的标签),但我目前正在处理一个相当复杂的项目,该项目涉及动态创建一组 NSPopUpButtons用于选择自定义 NSString ,该自定义 NSString 不是显示为它们的项目标题的内容。这就是我(可能是错误的)方法背后的原因。
  • 将显示为项目标题的值与该项目表示的对象分离正是分离内容、内容值和内容对象绑定的原因。他们应该支持你所描述的。

标签: objective-c cocoa binding nsbutton nspopupbutton


【解决方案1】:

我创建了一个“selectedKey”属性,用于存储与所选菜单项关联的 NSString。

绑定绝对是这里的路,但你对bind:toObject:withKeyPath:options的使用是不正确的。

传递给第一个参数的值必须是 Apple 为该特定控件提供的预定义值之一。对于NSPopUpButton 对象,可用值记录在NSPopUpButton Bindings Reference 中。当您浏览此文档时,您会发现没有selectedKey 选项。但是有一个 selectedValue 具有以下描述:

一个 NSString,它指定 NSPopUpButton 中所选项目的标题。

因此设置绑定的正确方法如下:

[self.btn bind:@"selectedValue"
      toObject:self
   withKeyPath:@"mySelectedString"
       options:nil];

这就是您需要做的所有事情:当 action 选择器被触发时,存储在您作为第三个参数传入的 keyPath 中的属性将已经更新。这意味着您可以 (i) 完全摆脱 setSelectedKey 方法,(ii) 删除 exposeBinding 行,以及 (iii) 删除 valueChanged: 中的代码 - Cocoa 已经完成了这一点。

下面的例子只实现了两种方法,但是,如果我理解你的意图,它们应该是你所需要的:

- (void)awakeFromNib {
    self.btn.target = self;
    self.btn.action = @selector(popUpActivity:);

    [self.btn bind:@"selectedValue"
          toObject:self
       withKeyPath:@"mySelectedString"
           options:nil];

    // I've added a couple of additional bindings here; they're
    // not required, but I thought they'd be instructive.
    [self.btn bind:@"content"
          toObject:self
       withKeyPath:@"myItems"
           options:nil];

    [self.btn bind:@"selectedIndex"
          toObject:self
       withKeyPath:@"mySelectedIndex"
           options:nil];

    // Now that you've set the bindings up, use them!
    self.myItems = @[@"Snow", @"Falling", @"On", @"Cedars"];
    self.mySelectedIndex = @3; // "Cedars" will be selected on startup
    // no need to set value of mySelectedString, because it will be
    // updated automatically by the selectedIndex binding.
    NSLog("%@", self.mySelectedString) // -> "Cedars"

}

- (void)popUpActivity:(id)sender {
    NSLog(@"value of <selectedIndex> -> %@", self.mySelectedIndex);
    NSLog(@"value of <selectedString> -> %@", self.mySelectedString);
}

最后一点值得一提的是,以上都不应该是NSPopUpButton 子类的一部分。看起来你可以——因此应该——做所有你需要做的事情没有这个控件的自定义子类。在我的演示应用中,上面的代码属于ViewController 类,你也应该尝试这样做。

【讨论】:

  • 在调用-bind:... 时,您不必仅限于使用预定义的绑定。您可以覆盖该方法以添加对自定义绑定的支持(调用super 以获取其他任何内容),但是您必须实现整个shebang。您将键值观察对象的键路径并处理-observeValueForKeyPath:... 中的更改。这在Cocoa Bindings Programming Topics: How Do Bindings Work? 中有描述
  • 哎呀!抱歉,没有意识到这一点。参考您的评论,我将更新我的答案以反映这一点。
  • @PaulPatterson AFAIK 每个 KVO 和 KVC 投诉的属性都可以在绑定中使用。请参阅我对上述问题的评论,了解我的方法背后的原因。
  • 是的,抱歉,这是我的错误。在肯昨天指出之前,我不知道这是可能的。听起来很有用,我一定会在某个时候自己研究一下。
猜你喜欢
  • 2023-03-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-01-16
相关资源
最近更新 更多