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

时间:2010-06-30 13:52:00

标签: cocoa custom-controls cocoa-bindings key-value-observing

假设您有一个类似于NSSlider的自定义控件,但支持选择一系列值,而不是单个值。明显的属性选择是minValue,maxValue,leftValue,rightValue,或者你想要命名它们。
您可能还想确保leftValue和rightValue始终位于minValue和maxValue之间。对于minValue和maxValue也是如此。您不希望它们可能使现有的leftValue和rightValue无效,例如by-let说 - 将maxValue设置为低于当前rightValue的值。

到目前为止非常直接。

在确保适当的KVO可访问性/兼容性的情况下,您做了什么?您无法确保KVO以正确的顺序设置属性(首先设置最小和最大限制,然后设置其他值)。

我碰巧有这样一个UI元素,如果没有打开错误设置值的大门,就无法弄清楚这个问题。

自定义控件旨在绑定到模型对象。 如果我现在使用值创建这样的模型(最小值:0.00最大值:42.00左:14.00右:28.00)我最终在自定义控件中设置了以下设置(最小值:0.00最大值) :42.00左:1.00右: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的情况下实现具有交叉依赖属性的类的最佳实践是什么?

2 个答案:

答案 0 :(得分:1)

哇。这是一个棘手的问题。正如您已经确定的那样,您无法确保以任何特定顺序发送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));
}

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

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

答案 1 :(得分:0)

我不确定我的理解是什么,但你的问题与KVO无关。

第一次在准备将其设置为14.0后调用setLeftValue:时,您会在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];
    }
}

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

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