将NSStepper与NSTextField集成

时间:2009-03-31 20:12:07

标签: cocoa user-interface

我需要让NSTextField使用NSStepper作为一个控件,以便我可以通过直接在文本字段上更改它或使用步进向上/向下箭头来编辑整数值。

在IB中我添加了这两个控件,然后将NSStepper的takeIntValueFrom连接到NSTextField,这使得每次单击步进箭头时文本值都会改变。问题是,如果我编辑文本字段然后再次单击步进器,它将忘记我手动编辑的值并使用步进器的内部值。

每当更改文本字段值时,更新步进器值的最佳/最简单方法是什么?

6 个答案:

答案 0 :(得分:23)

跳过takeIntValueFrom:方法。而是将两个视图绑定到控制器中的同一属性。您可能还想创建一个格式化程序并将文本字段的formatter出口连接到它。

答案 1 :(得分:15)

我会有一个带有一个整数变量的模型,它代表两个控件的值。

在我的控制器中,我将使用一个连接到两个控件的IBAction和两个IBOutlet,每个控件一个。那么我会有一种从模型值更新出口的方法。

IBOutlet NSStepper * stepper;
IBOutlet NSTextField * textField;

- (IBAction) controlDidChange: (id) sender
{
    [model setValue:[sender integerValue]];
    [self updateControls];
}

- (void) updateControls
{
    [stepper setIntegerValue:[model value]];
    [textField setIntegerValue:[model value]];
}

这是原则。正如Peter Hosey所述,格式化程序可能对您的文本字段很有用,至少要考虑步进器的最小值和最大值。

答案 2 :(得分:6)

我发现简单的方法是将步进器值绑定到输入和输入值到步进器

@property (strong) IBOutlet NSTextField *timeInput;
@property (strong) IBOutlet NSStepper *timeStepper;

bind input value

bind stepper value

答案 3 :(得分:5)

如果要跟踪某个模型中某个字段的值,例如当前页码,则无需在步进控件中保留另一个副本。我只是将控件配置为初始值为0,范围从-1到1.在步进控件的IBAction方法中,对控件的任何单击(或自动重复)调用,请求它的当前值,如果单击向上箭头,则为1,向下箭头为-1。立即将控件的当前值重置为0,然后使用基于方向1或-1的新值更新模型和其他任何内容(关联的文本字段或新的页面视图等)。如,

- (IBAction) bumpPageNum:(id)sender
{
    int whichWay = [sender intValue];   // Either 1 or -1
    [sender setIntValue:0];             // Same behavior next time
    [model changePageBy:whichWay];
}

这样,步进控件根本不必链接到模型中的任何值。

答案 4 :(得分:4)

我按Peter Hosey建议,因为在我看来这是最干净的方法。在我的控制器中创建了一个属性:

int editValue_;
...
@property (nonatomic, readwrite) int editValue;
...
@synthesize editValue = editValue_;

然后在IB中为Bindings选项卡中的两个控件设置了“Bind to:”复选框并选中了我的控制器,然后在“Model Key Path”字段中设置了“editValue”和voilá,它有效!只需3行代码和一些IB编辑。如果我需要更改控制器上的值,我使用setEditValue:文本字段会更新。

答案 5 :(得分:0)

这是给那些关心可可的人的。

NSSteppers in action

NSStepper一起使用NSTextField的唯一原因是因为文本字段中有一些数字。

完整的高级可可解决方案的步骤(可悲的是,这里缺少):

第1步:在您的文本字段中添加数字格式器,并根据需要设置格式。

Number formatter

第2步:添加NSObjectController并将您的文本字段/步进粘贴到其中。当人们进行直接绑定时,这是一个常见的错误。嗯在模型中添加相应的keyPath。

Bindings

第3步:确保您的文本字段对关键事件有反应。总是被新手错过。将文本字段委托连接到我们的控制器并添加代码。

- (BOOL)control:(NSControl *)control textView:(NSTextView *)textView doCommandBySelector:(SEL)commandSelector
{
    if (commandSelector == @selector(moveUp:) || commandSelector == @selector(moveDown:)) {
        if (control == [self minAgeTextField]) {
            return [[self minAgeStepper] sendAction:commandSelector to:[self minAgeStepper]];
        }
        if (control == [self maxAgeTextField]) {
            return [[self maxAgeStepper] sendAction:commandSelector to:[self maxAgeStepper]];
        }
    }
    return NO;
}

步骤4:一些粘合代码。这也是我们设置objectController内容的地方。

@property (weak) IBOutlet NSObjectController *profilesFilterObjectController;

@property (weak) IBOutlet NSTextField *minAgeTextField;
@property (weak) IBOutlet NSTextField *maxAgeTextField;
@property (weak) IBOutlet NSStepper *minAgeStepper;
@property (weak) IBOutlet NSStepper *maxAgeStepper;

@property (nonatomic) ProfilesFilter *filter;

- (void)awakeFromNib //or viewDidLoad...
{
    [self setFilter:[ProfilesFilter new]];
    [[self profilesFilterObjectController] setContent:[self filter]];
}

第5步:验证您的值(KVC验证)

@implementation ProfilesFilter

- (BOOL)validateValue:(inout id  _Nullable __autoreleasing *)ioValue forKey:(NSString *)inKey error:(out NSError * _Nullable __autoreleasing *)outError
{
    if ([inKey isEqualToString:@"minAge"] || [inKey isEqualToString:@"maxAge"]) {

        if (*ioValue == nil) {
            return YES;
        }

        NSInteger minAge = [[self minAge] integerValue];
        NSInteger maxAge = [[self maxAge] integerValue];
        if ([inKey isEqualToString:@"minAge"]) {
            if (maxAge != 0) {
                *ioValue = @(MAX(18, MIN([*ioValue integerValue], maxAge)));
            }
        }
        if ([inKey isEqualToString:@"maxAge"]) {
            if (minAge != 0) {
                *ioValue = @(MIN(99, MAX([*ioValue integerValue], minAge)));
            }

        }
    }
    return YES;
}

@end

注意:值错误? NSNumberFormatter将显示错误。最高年龄低于最低年龄?我们使用KVC验证(第5步)。尤里卡!

奖金:如果用户按住CTRL或SHIFT或同时按住两者(用户希望减慢或加快增量)怎么办?我们可以根据按下的键(子类NSStepper和覆盖程序increment的吸气剂来修改增量,并检查例如NSEvent.modifierFlags.contains(.shift))

- (double)increment
{
    BOOL isShiftDown = ([NSEvent modifierFlags] & NSEventModifierFlagShift) ? YES : NO;
    //BOOL isOptionDown = ([NSEvent modifierFlags] & NSEventModifierFlagOption) ? YES : NO;

    double increment = ([self defaultIncrement] - 0.001 > 0) ? [self defaultIncrement] : 1.0;
    if (isShiftDown) {
        increment = increment * 5;
    }
    return increment;
}

将此添加到- (BOOL)control:(NSControl *)control textView:(NSTextView *)textView doCommandBySelector:(SEL)commandSelector

//JUST AN ILLUSTRATION, holding shift + key up doesn't send moveUp but moveUpAndModifySelection (be careful of crash, just modify the command to moveUp; if not `NSStepper` doesn't know `moveUpAndModifySelection`)
if (commandSelector == @selector(moveUpAndModifySelection:)) {
    commandSelector = @selector(moveUp:);
}
if (commandSelector == @selector(moveToEndOfDocument:) || commandSelector == @selector(moveDownAndModifySelection:)) {
    commandSelector = @selector(moveDown:);
}

PS:最好的解决方案是使用自定义NSTextField,它具有并绘制步进并控制所有事件。您最终会得到一个较小的控制器!