gpt4 book ai didi

objective-c - 将KVO用于绑定(bind)在一起的NSTextField

转载 作者:行者123 更新时间:2023-12-03 16:05:35 24 4
gpt4 key购买 nike

我无法让KVO处理在Cocoa应用程序中绑定在一起的文本字段。当使用按钮在NSTextFields中设置字符串时,我已经使用了它,但是它不适用于绑定。与往常一样,非常感谢Stack Overflow提供的任何帮助。

我的代码的目的是:


将几个文本字段绑定在一起
当在一个字段中输入数字时,让其他字段自动更新
观察文本字段中的更改


这是我的MainClass代码,它是一个NSObject子类:

#import "MainClass.h"

@interface MainClass ()

@property (weak) IBOutlet NSTextField *fieldA;
@property (weak) IBOutlet NSTextField *fieldB;
@property (weak) IBOutlet NSTextField *fieldC;

@property double numA, numB, numC;

@end

@implementation MainClass

static int MainClassKVOContext = 0;

- (void)awakeFromNib {
[self.fieldA addObserver:self forKeyPath:@"numA" options:0 context:&MainClassKVOContext];
[self.fieldB addObserver:self forKeyPath:@"numB" options:0 context:&MainClassKVOContext];
[self.fieldC addObserver:self forKeyPath:@"numC" options:0 context:&MainClassKVOContext];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (context != &MainClassKVOContext) {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
return;
}

if (object == self.fieldA) {
if ([keyPath isEqualToString:@"numA"]) {
NSLog(@"fieldA length = %ld", [_fieldA.stringValue length]);
}
}

if (object == self.fieldB) {
if ([keyPath isEqualToString:@"numB"]) {
NSLog(@"fieldB length = %ld", [_fieldB.stringValue length]);
}
}

if (object == self.fieldC) {
if ([keyPath isEqualToString:@"numC"]) {
NSLog(@"fieldC length = %ld", [_fieldC.stringValue length]);
}
}
}

+ (NSSet *)keyPathsForValuesAffectingNumB {
return [NSSet setWithObject:@"numA"];
}

+ (NSSet *)keyPathsForValuesAffectingNumC {
return [NSSet setWithObject:@"numA"];
}

- (void)setNumB:(double)theNumB {
[self setNumA:theNumB * 1000];
}

- (double)numB {
return [self numA] / 1000;
}

- (void)setNumC:(double)theNumC {
[self setNumA:theNumC * 1000000];
}

- (double)numC {
return [self numA] / 1000000;
}

- (void)setNilValueForKey:(NSString*)key {
if ([key isEqualToString:@"numA"]) return [self setNumA: 0];
if ([key isEqualToString:@"numB"]) return [self setNumB: 0];
if ([key isEqualToString:@"numC"]) return [self setNumC: 0];
[super setNilValueForKey:key];
}

@end


这是文本字段之一的绑定:

最佳答案

在NSTextField上观察键值

-awakeFromNib方法的实现中,您已经编写了

[self.fieldA addObserver:self 
forKeyPath:@"numA"
options:0
context:&MainClassKVOContext];


这并没有实现您希望的结果:键 self.fieldAnumA不是 key-value coding compliant:如果尝试将键为 -valueForKey:-setValue:forKey:@"numA"发送到 self.fieldA,您将获得以下例外:


  [valueForUndefinedKey:]:此类不符合键numA编码的键值。





  [setValue:forUndefinedKey:]:此类不符合键numA编码的键值。


As a resultNSTextField实例也不符合 @"numA"的键值观察标准:某些键的KVO兼容的第一要求是该键的KVC兼容。

但是,它除 stringValue外还符合KVO。这使您可以执行 what I described earlier

注意:在Interface Builder中设置绑定的方式都不会改变。以后再说。

在NSTextField的stringValue上观察键值的麻烦

当在 NSTextField上调用 @"stringValue"时,观察 -setStringValue:的值是可行的。这是KVO内部的结果。

KVO内部简介

当您第一次开始观察键值观察对象时,该对象的类将更改-其 NSTextField指针也将更改。您可以通过覆盖 isa看到这种情况

- (void)addObserver:(NSObject *)observer 
forKeyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options
context:(void *)context
{
NSLog(@"%p, %@", self->isa, NSStringFromClass(self->isa));
[super addObserver:observer
forKeyPath:keyPath
options:options
context:context];
NSLog(@"%p, %@", self->isa, NSStringFromClass(self->isa));
}


通常,类的名称从 -addObserver:forKeyPath:options:context:更改为 Object

如果我们在键路径为 NSKVONotifying_Object-addObserver:forKeyPath:options:context:实例上调用了 Object-一个 @"property"实例符合KVC的键-下次我们在 Object实例上调用 -setProperty: Object(实际上,现在是 NSKVONotifying_Object的实例),以下消息将发送到该对象


-willChangeValueForKey:通过 @"property"
-setProperty:通过 @"property"
-didChangeValueForKey:通过 @"property"


深入了解这些方法中的任何一种,都表明它们是从未记录的函数 _NSSetObjectValueAndNotify中调用的。

所有这些的相关性是,在我们添加到 -observeValueForKeyPath:ofObject:change:context:实例的观察者中调用了 Object方法,以获取来自 @"property"的关键路径 -didChangeValueForKey:。这是堆栈跟踪的顶部:

-[Observer observeValueForKeyPath:ofObject:change:context:]
NSKeyValueNotifyObserver ()
NSKeyValueDidChange ()
-[NSObject(NSKeyValueObserverNotification) didChangeValueForKey:] ()


这与 NSTextField@"stringValue"有何关系?

在您的 previous setup中,您正在向 -awakeFromNib上的文本字段添加观察者。这意味着您的文本字段已经是 NSKVONotifying_NSTextField的实例。

然后,您将按下一个或另一个按钮,这将依次在文本字段上调用 -setStringValue。之所以能够观察到此更改,是因为-作为 NSKVONotifying_NSTextField的实例-您的文本字段在收到 setStringValue:value后实际上已收到


willChangeValueForKey:@"stringValue"
setStringValue:value
didChangeValueForKey:@"stringValue"


如上所述,从 didChangeValueForKey:@"stringValue"内,所有正在观察 @"stringValue"的文本字段值的对象都被通知此键的值在其自己的 -observeValueForKeyPath:ofObject:change:context:实现中已更改。特别是对于您添加为 -awakeFromNib文本字段的观察者的对象而言,这是正确的。

总而言之,您能够观察到 @"stringValue"的文本字段值的变化,因为您将自己添加为该键的文本字段的观察者,并且因为在文本字段上调用了 -setStringValue

所以有什么问题?

到目前为止,以讨论“在NSTextFields上进行键值观察的麻烦”为幌子,我们实际上只是在理解开头的句子


  当在 NSTextField上调用 @"stringValue"时,观察 -setStringValue:的值是可行的。


听起来很棒!所以有什么问题?

问题在于,当用户在文本字段中键入 NSTextField时,甚至在用户结束编辑之后(例如,通过跳出文本字段),都不会在文本字段上调用 -setStringValue:。 (此外,不是手动调用 -willChangeValueForKey:-didChangeValueForKey:。如果是,则我们的KVO可以工作;但是不能。)这意味着,当 @"stringValue"上的 -setStringValue:被调用时,我们在 @"stringValue"上的KVO可以工作。文本字段,当用户自己输入文本时它不起作用。

TL; DR: NSTextFieldWindowController上的KVO不够好,因为它不适用于用户输入。

将NSTextField的值绑定到字符串

让我们尝试使用绑定。

最初设定

使用带有XIB的单独的窗口控制器(我已经使用广告素材名称 WindowController.m)创建示例项目。 ( Here's the project I'm starting from on GitHub.)在 stringA中的类扩展中添加了属性 WindowController

@interface WindowController ()
@property (nonatomic) NSString *stringA;
@end


在Interface Builder中,创建一个文本字段并打开“绑定”检查器:



在“值”标题下,展开“值”项:



当前,“绑定到”复选框旁边的弹出按钮已选择“共享用户默认值控制器”。我们想要将文本字段的值绑定到我们的 WindowController实例。因此,请选择“文件的所有者”。发生这种情况时,“ Controller Key”字段将被清空,而“ Model Key Path”字段将变为“ self”。



我们想将此文本字段的值绑定到我们的 stringA实例的属性 self.stringA,因此将“模型键路径”更改为 WindowController



至此,我们完成了。 ( Progress so far on GitHub.)我们已成功将文本字段的值绑定到 stringA的属性 stringA

测试出来

如果我们在-init中将 stringA设置为某个值,则当窗口加载时,该值将显示在文本字段中:

- (id)init
{
self = [super initWithWindowNibName:@"WindowController"];
if (self) {
self.stringA = @"hello world";
}
return self;
}




而且,我们已经在另一个方向建立了绑定;在文本字段中结束编辑后,将设置窗口控制器的属性 NSTextField。我们可以通过覆盖它的设置器来检查它:

- (void)setStringA:(NSString *)stringA
{
NSLog(@"%s: stringA: <<%@>> => <<%@>>", __PRETTY_FUNCTION__, _stringA, stringA);
_stringA = stringA;
}


回复朦胧,重试

在文本字段中输入一些文本并按Tab后,我们将看到打印出来的内容

-[WindowController setStringA:]: stringA: <<(null)>> => <<some text>>


这看起来很棒。我们为什么一直都没有谈论这个???这里有点麻烦:令人讨厌的按标签的东西。将文本字段的值绑定到字符串之前,在文本字段中的编辑操作结束之前,不会设置字符串值。

新希望

但是,仍然有希望! Cocoa Binding Documentation for NSTextField指出 NSContinuouslyUpdatesValueBindingOption可用的一个绑定选项是 stringA。瞧,在Bindings Inspector中,有一个复选框与此NSTextField的值相对应。继续并选中该框。



进行此更改后,在我们键入内容时,将持续注销对窗口控制器的 NSTextField属性的更新:

-[WindowController setStringA:]: stringA: <<(null)>> => <<t>>
-[WindowController setStringA:]: stringA: <<t>> => <<th>>
-[WindowController setStringA:]: stringA: <<th>> => <<thi>>
-[WindowController setStringA:]: stringA: <<thi>> => <<thin>>
-[WindowController setStringA:]: stringA: <<thin>> => <<thing>>
-[WindowController setStringA:]: stringA: <<thing>> => <<things>>
-[WindowController setStringA:]: stringA: <<things>> => <<things >>
-[WindowController setStringA:]: stringA: <<things >> => <<things i>>
-[WindowController setStringA:]: stringA: <<things i>> => <<things in>>


最后,我们从文本字段中不断更新窗口控制器的字符串。其余的很容易。作为概念的快速证明,将几个其他文本字段添加到窗口,将它们绑定到stringA并将它们设置为连续更新。此时,您有三个同步的 stringAHere's the project with three synchronized text fields.

其余的方式

您想要设置三个文本字段,这些文本字段显示相互之间有一定关系的数字。由于我们现在正在处理数字,因此将从 WindowController中删除​​属性 numberA并将其替换为 numberBnumberCquantity

@interface WindowController ()
@property (nonatomic) NSNumber *numberA;
@property (nonatomic) NSNumber *numberB;
@property (nonatomic) NSNumber *numberC;
@end


接下来,我们将第一个文本字段绑定到File Owner上的numberA,将第二个文本字段绑定到numberB,依此类推。最后,我们只需要添加一个属性,该属性就是以这些不同方式表示的数量。我们将该值称为 quantity

@interface WindowController ()
@property (nonatomic) NSNumber *quantity;

@property (nonatomic) NSNumber *numberA;
@property (nonatomic) NSNumber *numberB;
@property (nonatomic) NSNumber *numberC;
@end


我们需要常量转换因子才能将 numberA的单位转换为 quantity的单位,依此类推,因此添加

static float convertToA = 1000.0f;
static float convertToB = 573.0f;
static float convertToC = 720.0f;


(当然,请使用与您的情况相关的数字。)有了这么多,我们可以为每个数字实现访问器:

- (NSNumber *)numberA
{
return [NSNumber numberWithFloat:self.quantity.floatValue * convertToA];
}

- (void)setNumberA:(NSNumber *)numberA
{
self.quantity = [NSNumber numberWithFloat:numberA.floatValue * 1.0f/convertToA];
}

- (NSNumber *)numberB
{
return [NSNumber numberWithFloat:self.quantity.floatValue * convertToB];
}

- (void)setNumberB:(NSNumber *)numberB
{
self.quantity = [NSNumber numberWithFloat:numberB.floatValue * 1.0f/convertToB];
}

- (NSNumber *)numberC
{
return [NSNumber numberWithFloat:self.quantity.floatValue * convertToC];
}

- (void)setNumberC:(NSNumber *)numberC
{
self.quantity = [NSNumber numberWithFloat:numberC.floatValue * 1.0f/convertToC];
}


现在,所有不同的数字访问器都只是用于访问 quantity的间接机制,非常适合于绑定。剩下要做的只有一件事:只要更改 ,我们就需要确保观察者重新轮询所有数字:

+ (NSSet *)keyPathsForValuesAffectingNumberA
{
return [NSSet setWithObject:@"quantity"];
}

+ (NSSet *)keyPathsForValuesAffectingNumberB
{
return [NSSet setWithObject:@"quantity"];
}

+ (NSSet *)keyPathsForValuesAffectingNumberC
{
return [NSSet setWithObject:@"quantity"];
}


现在,只要您键入其中一个文本字段,其他文本字段都会相应更新。 Here's the final version of the project on GitHub

关于objective-c - 将KVO用于绑定(bind)在一起的NSTextField,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13170217/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com