Re: responding to NSStepper clicks
Re: responding to NSStepper clicks
- Subject: Re: responding to NSStepper clicks
- From: Peter <email@hidden>
- Date: Sat, 17 Dec 2011 20:07:02 +0100
Here is a simple tutorial by a list member of ours:
http://juliuspaintings.co.uk/cgi-bin/paint_css/animatedPaint/059-NSStepper-NSTextField.pl
This example uses an object controller but can be reconfigured to bypass it and bind to an ivar easily.
Even though it uses a number formatter besides the controller it exposes the problem you are facing, too:
(1) Validation happens only when you leave the field.
(2) The user is not prevented from entering crap values right on the spot.
I took it as a base for some experiments of my own:
By setting validates immediately in IB for the text field and putting a handler like
- (BOOL)validateFloatValue:(id *)ioValue error:(NSError **)outError
{
NSLog(@"validating");
return YES;
}
into the model class, you can prove that whenever you type a number digit into the text field you see a validating message in the console, but at the same time the user may happily type any other crap without the validation even bothering to kick in. Validation furthermore stops if the value exceeds the max value defined in the number formatter. I could not find anything about this in the KVC docs, so go figure. I hope it's just me doing something terribly wrong or Apple's implementation is rather usless for this purpose.
I guess you might have to implement this method as a supllement if your value is set programmatically from various other sources.
All in all it seems way to complicated to achieve the desired effect without use of the delegate method. I'd be happy to see an example which does not have to rely on it, if this is possible at all. Maybe you have to use a custom value converter in the bindings or something. Dunno.
Therefore, my own implementation of a text field + stepper combination goes like this:
Specs:
- Only integers are allowed. Single 0 inputs, which eventually would lead to leading zeros, are forbidden, since the...
- minimum value is hardcoded at 2 anyway.
- The maximum value is calculated in code (by the window controller's delegate).
So I have a window controller sub-subclass for a modal window implemented like this (only relevant parts are shown here):
MyWC.h
@interface MyWC : ContextMenuWC {
IBOutlet NSTextField *unitNumberField;
IBOutlet NSStepper *unitNumberStepper;
NSInteger unitSize;
IBOutlet id delegate;
}
@property (nonatomic, assign) NSInteger unitSize;
@property (nonatomic, assign) id delegate;
- (IBAction) okAction:(id)sender;
- (id)initWithDelegate:(id <SplitColumnToRowsDelegate>)theDelegate;
@end
The stepper's value is bound directly to unitSize. IB binding setting is validates immediately.
The NSTextField is equally bound to unitSize. IB binding settings are all off.
MyWC.m
#import "RegexKitLite.h"
- (void)controlTextDidChange:(NSNotification *)aNotification
{
NSText *theFieldEditor = [aNotification.userInfo objectForKey:@"NSFieldEditor"];
NSString *theString = [theFieldEditor string];
//0-9 only, but no single 0 (i.e. 0 only allowed in double or more digit numbers)
BOOL isAcceptableIntegersOnly = ([theString isMatchedByRegex:@"^[0-9]{1,2}$"] && ![theString isMatchedByRegex:@"^0{1,2}$"]);
if (!isAcceptableIntegersOnly) {
//fall back to max value if input is bad
[theFieldEditor setString:[NSString stringWithFormat:@"%ld", (NSInteger)[unitNumberStepper maxValue]]];
[theFieldEditor setSelectedRange:NSMakeRange(0, [[theFieldEditor string] length])];
self.unitSize = [[theFieldEditor string] integerValue];
[self.okButton setEnabled:YES];
return;
}
//regex matches, so the following is safe, otherwise integerValue just extracts digits from any kind of string value
NSInteger theInteger = [theString integerValue];
NSInteger theMinValue = (NSInteger)[unitNumberStepper minValue];
//if 10 shall be allowed as input, the user has to be able to type 1
if (theInteger < theMinValue) {
[self.okButton setEnabled:NO];
return;
}
if (theInteger > (NSInteger)[unitNumberStepper maxValue]) {
//fall back to max value if input is too high
[theFieldEditor setString:[NSString stringWithFormat:@"%ld", (NSInteger)[unitNumberStepper maxValue]]];
[theFieldEditor setSelectedRange:NSMakeRange(0, [[theFieldEditor string] length])];
}
[self.okButton setEnabled:YES];
self.unitSize = [[theFieldEditor string] integerValue];
}
Note that I initialize the bound variable to some predefined value (the maximum value possible in this case).
Moreover, even though both GUI elements are bound to self.unitSize, I set this value "manually" at the end of the delegate method to make things work.
Took me quite a bit of experimentation to get it as I wanted it. I also experimented with KVC validation methods for unitSize but these also didn't help.
(Furthermore, I experimented with a @property (copy) previousUnitSizeString as a backup to restore a previously accepted value if the code rejects the user input, but the user experience didn't feel right for the purpose, so I always fall back to the max value. Maybe I'll change my mind eventually to simply reject bad input.)
Hope this helps.
Am 17.12.2011 um 05:04 schrieb Koen van der Drift:
>
> On Dec 16, 2011, at 10:40 AM, Mike Abdullah wrote:
>
>> Your text field is is bound to the model/a controller right? If so, you want the "updates immediately" binding option.
>>
>> On 16 Dec 2011, at 12:59, Koen van der Drift wrote:
>>
>>> On Thu, Dec 15, 2011 at 10:50 AM, Koen van der Drift
>>> <email@hidden> wrote:
>>>> On Thu, Dec 15, 2011 at 10:17 AM, Mike Abdullah
>>>> <email@hidden> wrote:
>>>>
>>>>> NSStepper is a subclass of NSControl. Hook up its action/target to be notified when it's adjusted.
>>>>
>>>> I'll try that, thanks. Using bindings sometimes makes you forget that
>>>> there is still some cdong needed :)
>>>>
>>>> - Koen.
>>>
>>> (with cdong, I meant coding :)
>>>
>>> Adding an IBAction did the trick indeed. One aditional question, how
>>> do I make the textfield immediately send the updated value to
>>> controlTextDidChange without the need of htting enter of tabbing out
>>> of the field? See eg the Date/Time preference panel.
>>>
>>> - Koen.
>>
>
>
> It's not working yet. Whenever I type in the NSTextField, the number shows up twice, eg if I type '6', I see '66'. And I get the message below in the debugger console.
>
> Is there some (Apple) sample code that shows how to use a NSTextField/NSStepper combination bound to an integer value?
>
> Thanks,
>
> - Koen.
>
>
>
> 2011-12-16 22:45:22.286 MyApp[4566:503] -[__NSCFConstantString unsignedLongLongValue]: unrecognized selector sent to instance 0x7fff7847da00
> 2011-12-16 22:45:22.287 MyApp[4566:503] Exception detected while handling key input.
> 2011-12-16 22:45:22.289 MyApp[4566:503] -[__NSCFConstantString unsignedLongLongValue]: unrecognized selector sent to instance 0x7fff7847da00
> 2011-12-16 22:45:22.297 MyApp[4566:503] (
> 0 CoreFoundation 0x00007fff8b121286 __exceptionPreprocess + 198
> 1 libobjc.A.dylib 0x00007fff89b53d5e objc_exception_throw + 43
> 2 CoreFoundation 0x00007fff8b1ad4ce -[NSObject doesNotRecognizeSelector:] + 190
> 3 CoreFoundation 0x00007fff8b10e133 ___forwarding___ + 371
> 4 CoreFoundation 0x00007fff8b10df48 _CF_forwarding_prep_0 + 232
> 5 Foundation 0x00007fff939f4e7c _NSSetUnsignedLongLongValueForKeyWithMethod + 56
> 6 Foundation 0x00007fff939a3ded _NSSetUsingKeyValueSetter + 177
> 7 Foundation 0x00007fff939a38ad -[NSObject(NSKeyValueCoding) setValue:forKey:] + 400
> 8 Foundation 0x00007fff939d5bb2 -[NSObject(NSKeyValueCoding) setValue:forKeyPath:] + 349
> 9 AppKit 0x00007fff8bb6b33b -[NSBinder _setValue:forKeyPath:ofObject:mode:validateImmediately:raisesForNotApplicableKeys:error:] + 243
> 10 AppKit 0x00007fff8bb6aeaa -[NSBinder setValue:forBinding:error:] + 260
> 11 AppKit 0x00007fff8bf08ecb -[NSValueBinder _applyObjectValue:forBinding:canRecoverFromErrors:handleErrors:typeOfAlert:discardEditingCallback:otherCallback:callbackContextInfo:didRunAlert:] + 191
> 12 AppKit 0x00007fff8bf08b6f -[NSValueBinder applyDisplayedValueHandleErrors:typeOfAlert:canRecoverFromErrors:discardEditingCallback:otherCallback:callbackContextInfo:didRunAlert:error:] + 591
> 13 AppKit 0x00007fff8bf08902 -[NSValueBinder _applyDisplayedValueIfHasUncommittedChangesWithHandleErrors:typeOfAlert:discardEditingCallback:otherCallback:callbackContextInfo:didRunAlert:error:] + 154
> 14 AppKit 0x00007fff8bf07f78 -[NSValueBinder validateAndCommitValueInEditor:editingIsEnding:errorUserInterfaceHandled:] + 488
> 15 AppKit 0x00007fff8bf4837d -[_NSBindingAdaptor _validateAndCommitValueInEditor:editingIsEnding:errorUserInterfaceHandled:bindingAdaptor:] + 183
> 16 AppKit 0x00007fff8bf48488 -[_NSBindingAdaptor validateAndCommitValueInEditor:editingIsEnding:errorUserInterfaceHandled:] + 256
> 17 AppKit 0x00007fff8be62cc5 -[NSTextField textDidChange:] + 187
> 18 Foundation 0x00007fff9397cde2 __-[NSNotificationCenter addObserver:selector:name:object:]_block_invoke_1 + 47
> 19 CoreFoundation 0x00007fff8b0c9e0a _CFXNotificationPost + 2634
> 20 Foundation 0x00007fff93969097 -[NSNotificationCenter postNotificationName:object:userInfo:] + 65
> 21 AppKit 0x00007fff8bec4130 -[NSTextView(NSSharing) didChangeText] + 348
> 22 AppKit 0x00007fff8bebe778 _NSDoUserReplaceForCharRange + 484
> 23 AppKit 0x00007fff8bebe7e3 _NSDoUserDeleteForCharRange + 40
> 24 AppKit 0x00007fff8beabd64 -[NSTextView(NSKeyBindingCommands) deleteBackward:] + 441
> 25 CoreFoundation 0x00007fff8b110a1d -[NSObject performSelector:withObject:] + 61
> 26 AppKit 0x00007fff8bdb8bad -[NSResponder doCommandBySelector:] + 62
> 27 AppKit 0x00007fff8be9390e -[NSTextView doCommandBySelector:] + 198
> 28 AppKit 0x00007fff8bcecfff -[NSKeyBindingManager(NSKeyBindingManager_MultiClients) interpretEventAsCommand:forClient:] + 1799
> 29 AppKit 0x00007fff8c03eb4a -[NSTextInputContext handleEvent:] + 747
> 30 AppKit 0x00007fff8bf0aeaf -[NSView interpretKeyEvents:] + 248
> 31 AppKit 0x00007fff8be83c65 -[NSTextView keyDown:] + 691
> 32 AppKit 0x00007fff8b963544 -[NSWindow sendEvent:] + 7430
> 33 AppKit 0x00007fff8b8fb68f -[NSApplication sendEvent:] + 5593
> 34 AppKit 0x00007fff8b891682 -[NSApplication run] + 555
> 35 AppKit 0x00007fff8bb1080c NSApplicationMain + 867
> 36 Spectrum 0x000000010882a1c2 main + 34
> 37 Spectrum 0x000000010882a194 start + 52
> 38 ??? 0x0000000000000003 0x0 + 3
> )
>
>
>
>
>
>
> _______________________________________________
>
> Cocoa-dev mailing list (email@hidden)
>
> Please do not post admin requests or moderator comments to the list.
> Contact the moderators at cocoa-dev-admins(at)lists.apple.com
>
> Help/Unsubscribe/Update your Subscription:
>
> This email sent to email@hidden
>
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden