Taming NSTextField with bindings and formatter to behave properly

I want to have a window that needs to accept some user input, for this I have the following:

  • NSWindow loaded from nib with NSWindowController , which is also its delegate
  • Couple of NSTextField 's with NSNumberFormatter
  • Using bindings, the NSTextField 's are bound to a integer properties in the NSWindowController (I didn't used NSObjectController for simplicity but can add it if needed)
  • A 'done' NSButton which the user clicks when finished to accept the changes and close the window
  • In controller's windowShouldClose: method do a final validation and decide whether I can close the window
  • What I want to achieve is pretty simple, yet Cocoa insists on making this challenging:

  • NSTextField should accept only a number for it final value, which is also greater than zero
  • If the user tries to insert non numeric values or zero, there should be an alert prompting the user to fix his entry
  • When the user clicks on done:
  • Do other custom validation (eg compare if one number is great than the other)
  • If the values are correct, accept the changes and close the window (the 'model' should be already updated with the values via bindings)
  • If the values are incorrect, the user should be prompted with a choice whether to fix the entry (leaving the window open) or close the window and discard changes
  • Simple enough. The NSNumberFormatter should already cover most of the task and together with the bindings, this should be pretty easy to achieve.

    Problem #1:

    I couldn't find a way to change the error message that is displayed to the user in the alert when NSTextField loses focus and the value is incorrect to something more descriptive. Is there any way to do this? Or I need to implement my own NSFormatter somehow?

    Problem #2:

    When user changes a value in the NSTextField and clicks on the 'done' button, Cocoa doesn't consider this as a trigger to update the value of the model NSTextField is bound to. This may be the standard OSX behavior but is something that doesn't make any sense.

    I was able to work around this by calling [window makeFirstResponder:nil] in the action of the 'done' button to force the NSTextField lose focus and update the value but I wonder if this is the right way to achieve this.

    Problem #3:

    And here is where I really scratch my head. If the user enters an incorrect value (like non integers) in the NSTextField and click 'done' button, the validation doesn't kick in and the NSTextField will continue to have the incorrect values while the model is not updated.

    I'd expect that the "invalid" alert will still be displayed and I have some place to insert code to make a decision whether to close the window or not but I couldn't find any way to override this behavior.

    What should be the standard practices to achieve these requirements? Should I abandon the formatter and/or bindings and just do it all manually using actions?


    For problem 1, try setting a delegate for the text fields and implementing -control:didFailToFormatString:errorDescription: and possibly -control:didFailToValidatePartialString:errorDescription: . Present any UI you want, making use of the error description or not.

    Alternatively, you can implement a custom subclass of NSNumberFormatter and override -getObjectValue:forString:errorDescription: and -isPartialStringValid:proposedSelectedRange:originalString:originalSelectedRange:errorDescription: . You can call through to super for most of the implementation. If it fails, you can replace the error description that super supplied with a different one. You would use this approach if you need the context of the formatter object (its properties, etc.) to figure out a better error description.

    The correct solution to problem 2 is to not programmatically change the first responder. Rather, you should indeed use an NSObjectController to mediate between the text fields and the window controller. In the action method for the Done button, call either -commitEditing or -commitEditingWithDelegate:didCommitSelector:contextInfo: . NSObjectController inherits those methods from NSController , which adopts the NSEditor protocol. When you get the result (synchronously for the former, asynchronously for the latter), you either proceed or do nothing depending on whether the commit succeeded or failed.

    I suspect that problem 3 is also a result of programmatically changing the window's first responder. It is often the case that programmatic changes are not subject to the same checks as the corresponding change made due to user action. The frameworks assume you, the programmer know what you're doing, and so it would be "wrong" to subject a programmatic change of focus to validation by the formatter, for example.

    链接地址: http://www.djcxy.com/p/14930.html

    上一篇: 处理控件的输入,并更新所有伴随控件?

    下一篇: 使用绑定和格式化程序驯服NSTextField以正确运行