如何确保类似 NSSlider 的自定义控件的 KVO 行为正确?

发布于 2024-09-07 15:06:47 字数 2626 浏览 5 评论 0原文

假设您有一个类似于 NSSlider 的自定义控件,但支持选择一系列值,而不是单个值。属性的明显选择是 minValue、maxValue、leftValue、rightValue,或者您想要的任何名称。
您可能还想确保 leftValue 和 rightValue 始终位于 minValue 和 maxValue 之间。 minValue 和 maxValue 相同。您不希望它们潜在地使现有的 leftValue 和 rightValue 无效,例如将 maxValue 设置为低于当前 rightValue 的值。

到目前为止非常简单。

但是,为了确保正确的 KVO 可访问性/合规性,您该怎么做?您根本无法确保 KVO 按正确的顺序设置属性(首先设置最小和最大限制,然后设置其他值)。

我碰巧有这样一个 UI 元素,并且根本无法弄清楚如何在不打开错误设置值的大门的情况下运行它。

自定义控件旨在绑定到模型对象。 如果我现在使用值 (min: 0.00 max: 42.00 left: 14.00 right: 28.00) 创建这样的模型,我最终会在自定义控件中进行以下设置 (min: 0.00 max : 42.00 left: 1.00 right: 1.00)

这样做的原因是,它不是先调用 minValue 和 maxValue,而是先调用 leftValue 和 rightValue,这会导致两个值最终都是 1.0(这是默认的 maxValue) 。 (之后,然后将 maxValue 设置为适当的值。)

//[self prepare] gets called by both, [self initWithCoder:] and [self initWithFrame:]
- (void)prepare
{
    minValue = 0.0;
    maxValue = 1.0;
    leftValue = 0.0;
    rightValue = 1.0;       
}

- (void)setMinValue:(double)aMinValue
{
    if (aMinValue < maxValue && aMinValue < leftValue) {
        minValue = aMinValue;
        [self propagateValue:[NSNumber numberWithDouble:minValue] forBinding:@"minValue"];
        [self setNeedsDisplay:YES];
    }   
}

- (void)setMaxValue:(double)aMaxValue
{
    if (aMaxValue > minValue && aMaxValue > rightValue) {
        maxValue = aMaxValue;
        [self propagateValue:[NSNumber numberWithDouble:maxValue] forBinding:@"maxValue"];
        [self setNeedsDisplay:YES];
    }
}

- (void)setLeftValue:(double)aLeftValue
{
    double newValue = leftValue;
    if (aLeftValue < minValue) {
        newValue = minValue;
    } else if (aLeftValue > rightValue) {
        newValue = rightValue;
    } else {
        newValue = aLeftValue;
    }
    if (newValue != leftValue) {
        leftValue = newValue;
        [self propagateValue:[NSNumber numberWithDouble:leftValue] forBinding:@"leftValue"];
        [self setNeedsDisplay:YES];
    }
}

- (void)setRightValue:(double)aRightValue
{
    double newValue = leftValue;
    if (aRightValue > maxValue) {
        newValue = maxValue;
    } else if (aRightValue < leftValue) {
        newValue = leftValue;
    } else {
        newValue = aRightValue;
    }
    if (newValue != rightValue) {
        rightValue = newValue;
        [self propagateValue:[NSNumber numberWithDouble:rightValue] forBinding:@"rightValue"];
        [self setNeedsDisplay:YES];
    }

正是在这样的时刻,您希望 Apple 开放 Cocoa 的源代码。或者至少是一些用于更深入检查的控制措施。

您将如何实现这样的控件?

或者更一般地说:实现具有交叉依赖属性同时遵守 KVO 的类的最佳实践是什么?

Let's say you have a custom control similar to NSSlider but with support for choosing a range of values, instead of a single value. The obvious choice of properties is minValue, maxValue, leftValue, rightValue, or however you wanna name them.
You'd probably also want to make sure that leftValue and rightValue always lay in between minValue and maxValue. Same for minValue and maxValue. You don't want them to potentially invalidate the existing leftValue and rightValue, like by—let's say—setting maxValue to a value lower than the current rightValue.

Pretty straight forward so far.

What however do you do in case of ensuring proper KVO accessibility/compliency? You simply can't assure that KVO sets the properties in the proper order (that being first setting the min and max limits and then the other values).

I happen to have such an UI element and simply can't figure out hot to get this running without opening the doors for falsely set values.

The custom control is meant to be bound to a model object.
If I now create such a model with the values (min: 0.00 max: 42.00 left: 14.00 right: 28.00) I end up with the following setup in the custom control (min: 0.00 max: 42.00 left: 1.00 right: 1.00)

The reason for this is that instead of calling minValue and maxValue first, it calls leftValue and rightValue first, which causes both values to end up with 1.0 (which is the default maxValue). (After which the maxValue is then set to its proper value.)

//[self prepare] gets called by both, [self initWithCoder:] and [self initWithFrame:]
- (void)prepare
{
    minValue = 0.0;
    maxValue = 1.0;
    leftValue = 0.0;
    rightValue = 1.0;       
}

- (void)setMinValue:(double)aMinValue
{
    if (aMinValue < maxValue && aMinValue < leftValue) {
        minValue = aMinValue;
        [self propagateValue:[NSNumber numberWithDouble:minValue] forBinding:@"minValue"];
        [self setNeedsDisplay:YES];
    }   
}

- (void)setMaxValue:(double)aMaxValue
{
    if (aMaxValue > minValue && aMaxValue > rightValue) {
        maxValue = aMaxValue;
        [self propagateValue:[NSNumber numberWithDouble:maxValue] forBinding:@"maxValue"];
        [self setNeedsDisplay:YES];
    }
}

- (void)setLeftValue:(double)aLeftValue
{
    double newValue = leftValue;
    if (aLeftValue < minValue) {
        newValue = minValue;
    } else if (aLeftValue > rightValue) {
        newValue = rightValue;
    } else {
        newValue = aLeftValue;
    }
    if (newValue != leftValue) {
        leftValue = newValue;
        [self propagateValue:[NSNumber numberWithDouble:leftValue] forBinding:@"leftValue"];
        [self setNeedsDisplay:YES];
    }
}

- (void)setRightValue:(double)aRightValue
{
    double newValue = leftValue;
    if (aRightValue > maxValue) {
        newValue = maxValue;
    } else if (aRightValue < leftValue) {
        newValue = leftValue;
    } else {
        newValue = aRightValue;
    }
    if (newValue != rightValue) {
        rightValue = newValue;
        [self propagateValue:[NSNumber numberWithDouble:rightValue] forBinding:@"rightValue"];
        [self setNeedsDisplay:YES];
    }

It's times like these when you wish Apple had opened up the source of Cocoa. Or at least some of the controls for deeper inspection.

How would you have implemented such a control?

Or more general: What's the best practice for implementing a class that does have cross-dependent properties while complying to KVO?

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

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

发布评论

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

评论(2

擦肩而过的背影 2024-09-14 15:06:47

哇。这是一个棘手的问题。正如您已经确定的,您无法确保 KVO 消息按任何特定顺序发送。

这里有一个想法:在 setLeftValue:setRightValue: 访问器中,为什么不直接存储传入的值,然后在访问时修剪该值,而不是修剪该值。

例如:

- (void)setLeftValue:(double)value {
    leftValue = value;
    [self propagateValue:[NSNumber numberWithDouble:self.leftValue] forBinding:@"leftValue"]; // Note the use of the accessor here
    [self setNeedsDisplay:YES];
}

- (double)leftValue {
    return MAX(self.minValue, MIN(self.maxValue, leftValue));
}

这样,一旦设置了 minValuemaxValueleftValuerightValue 将代表正确的值。而且,通过使用自定义访问器,您可以确保保留 minValue <= leftValue/rightValue <= maxValue 的约束。

此外,对于您正在编写的代码,MINMAX 预处理器宏将为您节省大量精力。

Wow. This is a tricky problem. As you've already identified, there's nothing you can do to ensure that KVO messages are sent in any particular order.

Here's one thought: in your setLeftValue: and setRightValue: accessors, instead of trimming the value, why not just store the value that's passed in, and then trim the value on access.

For example:

- (void)setLeftValue:(double)value {
    leftValue = value;
    [self propagateValue:[NSNumber numberWithDouble:self.leftValue] forBinding:@"leftValue"]; // Note the use of the accessor here
    [self setNeedsDisplay:YES];
}

- (double)leftValue {
    return MAX(self.minValue, MIN(self.maxValue, leftValue));
}

This way, once minValue and maxValue are set, leftValue and rightValue will represent the correct values. And, by using custom accessors, you can ensure that your constraint that minValue <= leftValue/rightValue <= maxValue is preserved.

Also, for the code you're writing, the MIN and MAX preprocessor macros will save you a lot of effort.

澜川若宁 2024-09-14 15:06:47

不太确定我理解你想要做什么,但你的问题与 KVO 无关。

在准备将其设置为 14.0 之后第一次调用 setLeftValue: 时,您会看到以下括号中的变量值:

- (void)setLeftValue:(double)aLeftValue(14.0)
{
    double newValue = leftValue(0.0);
    if (aLeftValue(14.0) < minValue(0.0)) {
        newValue = minValue(0.0);
    } else if (aLeftValue(14.0) > rightValue(1.0)) { // always evaluates true for all aleftValue>1
        newValue = rightValue(1.0); // now newValue==1.0
    } else {
        newValue = aLeftValue(14.0);
    }
    if (newValue(1.0) != leftValue(0.0)) {
        leftValue = newValue(1.0); // now leftvalue==1.0
        [self propagateValue:[NSNumber numberWithDouble:leftValue] forBinding:@"leftValue"];
        [self setNeedsDisplay:YES];
    }
}

对于所有 aLeftvalues>1,此代码将始终设置 leftValue==1.0

由于您从未调用 minValue 和 maxValue 设置器,因此它们对此代码没有影响。

Not exactly sure I understand what your trying to do but your problem has nothing to do with KVO.

The first time that setLeftValue: is called after prepare to set it to 14.0, you see the following with values of variables in parethesis:

- (void)setLeftValue:(double)aLeftValue(14.0)
{
    double newValue = leftValue(0.0);
    if (aLeftValue(14.0) < minValue(0.0)) {
        newValue = minValue(0.0);
    } else if (aLeftValue(14.0) > rightValue(1.0)) { // always evaluates true for all aleftValue>1
        newValue = rightValue(1.0); // now newValue==1.0
    } else {
        newValue = aLeftValue(14.0);
    }
    if (newValue(1.0) != leftValue(0.0)) {
        leftValue = newValue(1.0); // now leftvalue==1.0
        [self propagateValue:[NSNumber numberWithDouble:leftValue] forBinding:@"leftValue"];
        [self setNeedsDisplay:YES];
    }
}

For all aLeftvalues>1, this code will always set leftValue==1.0.

Since you never call either of the minValue and maxValue setters, they have no effect on this code.

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