- Koen.
_______________________________________________
Cocoa-dev mailing list (Coco...@lists.apple.com)
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:
http://lists.apple.com/mailman/options/cocoa-dev/cocoa-dev-garchive-98506%40googlegroups.com
This email sent to cocoa-dev-ga...@googlegroups.com
> http://lists.apple.com/mailman/options/cocoa-dev/cocoadev%40mikeabdullah.net
>
> This email sent to coco...@mikeabdullah.net
> 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.
_______________________________________________
Cocoa-dev mailing list (Coco...@lists.apple.com)
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:
(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.
> On Thu, Dec 15, 2011 at 10:50 AM, Koen van der Drift
> <koenvan...@gmail.com> wrote:
>> On Thu, Dec 15, 2011 at 10:17 AM, Mike Abdullah
>> <coco...@mikeabdullah.net> 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.
Not sure I am getting you right here:
As long as everything is correctly hooked up (esp. the delegate outlet of the text field) each and every keystroke will trigger -(void)controlTextDidChange:(NSNotification *)aNotification in the delegate. This should work out of the box. Works for me at least and has worked like this ever since. Put an NSLog inside or set a breakpoint to see if it works. If bindings are involved you might want to check the various binding options in IB.
- Koen.
- Koen.
On Fri, Dec 16, 2011 at 10:40 AM, Mike Abdullah
> 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
>> <koenvan...@gmail.com> wrote:
>>> On Thu, Dec 15, 2011 at 10:17 AM, Mike Abdullah
>>> <coco...@mikeabdullah.net> 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
)
> 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.
Um, it's not just "a message", it's an exception. Your app crashed -- crashed nicely, that's all. Until you've fixed the exception, you don't know if the misbehavior of the text field is significant.
> Is there some (Apple) sample code that shows how to use a NSTextField/NSStepper combination bound to an integer value?
You have a bug in your code. It's subtle, in the sense that it's not obvious until you realize what's going on. Then it's obvious.
When you change the value of a control that's bound to a property, the new value is an object that's set via KVC. When the property happens to be of scalar type, the object needs to be converted. This is handled by the KVC implementation, and you can see this happening in this part of your backtrace:
> 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
When the control is a text field, and the text field has a number formatter, the value object is a NSNumber. When the text field has a date/time formatter, the value object is a NSDate. Otherwise, the value object is a string.
In your case, the model property has type unsigned long long (or something equivalent to that). The KVC implementation uses a standard conversion method to convert the object to a scalar -- in this case 'unsignedLongLongValue'. This works great for NSNumber, but fails for NSString because NSString doesn't have a 'unsignedLongLongValue' method, only a 'longLongValue'. Hence:
> 2011-12-16 22:45:22.286 MyApp[4566:503] -[__NSCFConstantString unsignedLongLongValue]: unrecognized selector sent to instance 0x7fff7847da00
There are various solutions you might choose, depending on how you expect to handle validation errors for your text field. You can add a number formatter to the field, or you can change the property to type long long (though you then need to check for negative numbers yourself), or you can use KVC validation to substitute a NSNumber object for the NSString object.
P.S. I predict, after you fix this, you'll still get extra junk showing up in the text field. I wasn't following this thread very closely, and I don't remember if you said what you were doing at controlTextDidChange time, but I imagine you were trying to change the value of the stepper whenever a character is typed in the text field. Note this likely creates a KVC loop -- setting the stepper changes the property (again) via the binding, which changes the text field, which is in mid-edit. This may not be your exact scenario, but I suspect it's something along that line.
> There are various solutions you might choose, depending on how you expect to handle validation errors for your text field. You can add a number formatter to the field, or you can change the property to type long long (though you then need to check for negative numbers yourself), or you can use KVC validation to substitute a NSNumber object for the NSString object.
Changing the value from NSInteger to NSNumber in my model did the trick, I don't get the crash anymore, and everything updates accordingly. I don't have to use a formatter either, although I probably need to add anyway to prevent the user from typing anything else but numbers.
The only that does't work yet is that I need to hit enter or tab to have my model respond to a change in the NSTextField. As suggested earlier, I set the binding "Continuously Update Value", but that doesn't work.
I also see a small exclamation mark (white on a red circle), in the Model Key Path bindings field, where I bind to my model. Xcode autocompletes the correct property, so I'm not sure what to do with this, since everything works. I don't see that exclamation mark in the NSStepper bindings field.
- Koen.
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.
> http://lists.apple.com/mailman/options/cocoa-dev/magnard%40web.de
>
> This email sent to mag...@web.de
>
> On Dec 16, 2011, at 11:48 PM, Quincey Morris wrote:
>
>> There are various solutions you might choose, depending on how you expect to handle validation errors for your text field. You can add a number formatter to the field, or you can change the property to type long long (though you then need to check for negative numbers yourself), or you can use KVC validation to substitute a NSNumber object for the NSString object.
>
>
> Changing the value from NSInteger to NSNumber in my model did the trick, I don't get the crash anymore, and everything updates accordingly. I don't have to use a formatter either, although I probably need to add anyway to prevent the user from typing anything else but numbers.
You *have* to use a number formatter. What you've probably got a present is the text field sending NSString objects down into the model. You're not seeing an immediate problem because NSNumber is an object just like NSString, and so can be stored, but will completely fail as soon as something tries to work with it.
>
> The only that does't work yet is that I need to hit enter or tab to have my model respond to a change in the NSTextField. As suggested earlier, I set the binding "Continuously Update Value", but that doesn't work.
That option definitely works since we are using it!
>
> I also see a small exclamation mark (white on a red circle), in the Model Key Path bindings field, where I bind to my model. Xcode autocompletes the correct property, so I'm not sure what to do with this, since everything works. I don't see that exclamation mark in the NSStepper bindings field.
Put a formatter in and see what happens.
> You *have* to use a number formatter. What you've probably got a present is the text field sending NSString objects down into the model. You're not seeing an immediate problem because NSNumber is an object just like NSString, and so can be stored, but will completely fail as soon as something tries to work with it.
>>
>> The only that does't work yet is that I need to hit enter or tab to have my model respond to a change in the NSTextField. As suggested earlier, I set the binding "Continuously Update Value", but that doesn't work.
>
> That option definitely works since we are using it!
>>
>> I also see a small exclamation mark (white on a red circle), in the Model Key Path bindings field, where I bind to my model. Xcode autocompletes the correct property, so I'm not sure what to do with this, since everything works. I don't see that exclamation mark in the NSStepper bindings field.
>
> Put a formatter in and see what happens.
Thanks, I'll look at adding a formatter.
Just to be sure I'm doing this correctly in my code I extract the number from my textfield and stepper as follows:
NSUInteger index = [ indexTextField integerValue];
and/or
NSUInteger index = [indexStepper integerValue];
BTW that exclamation mark went away when I select a Value Transformer from the field below it. Is that also required besides a formatter?
- Koen._______________________________________________
> Just to be sure I'm doing this correctly in my code I extract the number from my textfield and stepper as follows:
>
> NSUInteger index = [ indexTextField integerValue];
>
> and/or
>
> NSUInteger index = [indexStepper integerValue];
The whole point of bindings is so you don't write this code at all.
>
>
> BTW that exclamation mark went away when I select a Value Transformer from the field below it. Is that also required besides a formatter?
A value transformer has the same effect as a formatter, but with less control. e.g. it can't report an error, or adjust input as the user types.