Cocoa: How to bind a boolean property to NSCellStateValue? - cocoa

I would like to bind the boolean enabled property of an NSTextField to the state of an NSButton. I already tried adding a custom NSValueTransformer that transforms the state of the NSButton into NSNumber. However, in that scenario the text fields are disabled all the time for some reason. My second approach: To bad fails also since NSValueTransformer does not offer return primitives types such as BOOL.
Example:
The screenshot shows an example in which the text fields are disabled because the checkbox has the state NSOnState. I also would like to bind the labels to this state.
Further, it would be convenient, if I could set a "disabled text" in Interface Builder. In the above example I set the text in the associated class.
Edit:
I set self.anonymousLoginCheckbox.state as the Model Key Path for the enabled property of the account text field. Similar for the password text field. However, it does not work.
Update:
I created an example project available on GitHub showing the implementation kindly described by Nicolas Bachschmidt.

NSButton isn't KVO compliant for the key state. Cocoa Bindings require the observed object to emit notifications when the observed property changes. As NSButton's state is just a wrapper for its cell's state, -[NSButton setState:] method (and the automatic KVO notifications) isn't invoked when the user click the button (but -[NSCell setState:] is). If you set the model key path to self.anonymousLoginCheckbox.cell.state, it will work.

Related

NSTextField edit coalescing

I am writing a Cocoa app which contains several NSTextFields. I am trying to propagate changes to the text in the text field to my model layer, but since I am registering an Undo action when the model is updated, this makes it a bit more complicated than simply listening for controlTextDidChange:.
If I hook onto the delegate method controlTextDidChange:, then it gets called once for every key pressed. If I update the model then, the model will treat each separate keypress as a separate Undo event. This is not desirable. What I want is to be notified when the default "Undo Typing" action is registered with the Undo manager, so I can update the model then. The NSTextField registers one (and only one) such event, when I pause typing for a moment. Unfortunately, I have no idea when this occurs.
I have tried listening for controlTextDidEndEditing:, and updating the model in that delegate method, but then I have to explicitly tab out of the text field before the model will update. Since I archive my model object directly in my NSDocument subclass, doing this will result in data loss for the user if they save the document after changing the contents of the text field, but don't tab out of the text field first, because the model would not have been updated in time. Therefore, this technique will not work.
I have also tried hooking onto the text field's field editor (during control:textShouldBeginEditing:) and listening for textDidEndEditing:, but then I find that this simply doesn't fire, so the model won't be updated, causing data loss as described in the previous paragraph. Can anyone help me figure this out? Thanks!
By default, Apple NSTextField leaves dangling edits as you mentioned.
Because of this problem, amongst others, I subclassed NSTextField and NSApplication to handle text field editing the way it should be handled: Pressing 'Enter', clicking outside the field, and other actions should end editing and produce the Undo entry.
Also, I highly recommend GCUndoManager replacement for NSUndoManager.

Trying to see the delegate of an NSTextView of an NSCell

I am having a devil of a time trying to figure out how to get the address of the Text Field Editor (NSTextView) of an NSCell—NSFormCell and NSTextFieldCell in particular? NSCell does not have a property to access it. I did figure out the editor is not allocated until one is actually editing the field.
I want to set the delegate so I can capture keystrokes for auto-completion.
By default, there's a single field editor for each window. Even if a control or cell uses a custom field editor, it's still vended by the window. You would call -[NSWindow fieldEditor:forObject:] to obtain the field editor for a given control.
However, the delegate of the field editor is always set to the control on whose behalf it is working. Setting the delegate to something else is likely to break things. So, you would typically use a custom subclass of the control and implement your delegate methods there.
Finally, controlling completions is normally done using -textView:completions:forPartialWordRange:indexOfSelectedItem: in the text view delegate, not by capturing keystrokes.

Best Practice for Manipulating UI Elements In Cocoa

I'll start by saying that I'm new to cocoa development. I'm also surprised I didn't find a post about this already, but I've filtered through a number of posts now without success.
I have a set of elements that should change state based on the state of a long running algorithm.
Basically, I have a start button, a cancel button, and a next button. The initial state of the app would be start button enabled, cancel and next buttons disabled. The status of the algorithm should swap enabled / disabled on all the buttons as it progresses.
Every option for manipulating button state I have seen involves coding button.enabled into the controller code. I'm coming from an ASP .NET MVC background as I dive into Cocoa and this seems backwards to me. Shouldn't the view logic be separated from the controller logic in the MVC pattern?
To me, it seems I should be able to emit a couple boolean values as IBOutlets like algorithm running and algorithm success, and bind the button state at the view layer. Do I need to toss this idea? Or am I possibly missing something about the Cocoa version of design pattern (like the object I bind the view to should really be a view model, which interacts with a controller class)? Or, lastly, is there an easy way to accomplish what I'm talking about, and I've just missed it.
You don't need to code the enabled state of the button into your controller. What you can do is declare a BOOL property on your controller such as isBusy and then set this property to YES when you start your long operation and to NO when it's finished. You must do this using Key-Value Coding-compliant methods, which essentially means using the setter, so you'd call self.isBusy = YES;, for instance.
The reason you do this is because you can then use Cocoa Bindings to set up a binding on the UI controls. Go into the bindings inspector for one of your buttons, and bind the Enabled binding to your controller object with a key path of isBusy.
Cocoa bindings uses Key-Value Observing (KVO) to monitor the value of observed properties. When a change occurs in the isBusy property, the buttons that are bound to it will notice and change their enabled state in response.
You might be missing the delegate model of Objective-C. In the example you are giving you could have your controller object running the algorithm and updating its status to its delegate, in this case the view.
i.e your ViewController object will call the doSomething method from the ProgramController; and when its over ProgramController will invoque the somethingDidFinish method from its delegate, as defined in your ProgramControllerDelegate protocol)

How do I bind an NSMenuItem to an NSArrayController

How do I bind the enabled state of an NSMenuItem to an NSArrayController's selection? I've tried binding the item's enabled state to the controller's selectedObjects or selectedIndexes and in neither case is the menuitem ever enabled when there are selections. In IB, I unchecked the "enable" checkbox. I simply want the NSMenuItem to be enabled when there are selections in the table. My table allows for multiple selection and I also use a button which is bound to selectedObjects.#count and the button enables/disables as expected, so I thought using the same keypath would work for the menuitem as well, but nope. This can't be difficult as I can't find an answer via google, so I figure it must be simple.
Thanks
The enabled binding has to get a BOOL value, and unfortunately won't just treat any old object as a boolean True. Fortunately, however, NSValueTransformer makes it easy to do so. There's a couple of constants named in the NSValueTransformer Class Reference which you can use in the bindings pane in IB.
In your case, you can bind the Model Key Path to "selectedObjects" and enter "NSIsNotNil" in the Value Transformer field. The transformer gives the binding the BOOL value it needs.

How to get notifications of NSView isHidden changes?

I am building a Cocoa desktop application. I want to know when a NSView's isHidden status has changed. So far using target/action doesn't help, and I can't find anything in NSNotification for this task. I would like to avoid overriding the setHidden method, because then I'll have to override all the NSView derived class that I am using.
UPDATE: I ended up using KVO. The path for "isHidden" is "hidden", probably because the setter is "setHidden".
You could use Key-Value Observing to observe the isHidden property of the NSView(s). When you receive a change notification from one of these views, you can check if it or one of its superviews is hidden with -isHiddenOrHasHiddenAncestor.
A word of warning: getting Key-Value Observing right is slightly tricky. I would highly recommend reading this post by Michael Ash, or using the -[NSObject gtm_addObserver:forKeyPath:selector:userInfo:options] method from the NSObject+KeyValueObserving category from the Google Toolbox for Mac.
More generally, one can override viewWillMoveToWindow: or the other related methods in NSView to tell when a view will actually be showing (i.e. it's window is in the window display list AND the view is not hidden). Thus the dependency on KVO for the 'hidden' key used above is removed, which only works if setIsHidden has been called on that view. In the override, 'window' (or [self window]) will indicate whether the view is being put into a visible view hierarchy (window is non-nil) or being taken out of it (window is nil).
I use it for example to start/stop a timer to update a control from online data periodically - when I only want to update while the control is visible.
Could you override the setter method for the hidden property so that it will trigger some custom notification within your application?

Resources