Enabling NSButton with bindings, based on NSTableView selection - cocoa

I have a NSWindow containing an NSButton and an NSTableView.
I'd like the button to be enabled if and only if the table contains at least one item, and exactly one item is selected. (The table does not allow multiple selection.)
What can I bind the button's enabled binding to to make this happen?

This is an old thread, but here are my 2 cents:
Use and array controller and bind the button's enabled state to
Controller Key: selectedObjects
Model Key Path: #count
Works fine.

Try binding to the array controller's selectedObjects, model key path count, with no value transformer.
Note that this would be unsafe if you allowed multiple selection: For one thing, the count could easily be neither YES nor NO; for another, if the user selected a multiple of 256 items, the lowest byte of the count would be 0, so the BOOL value would be NO even though there is a selection.

I ran into this today and I got it to work after some efforts.
My button should be disabled if nothing is selected in the "Master Table":
Problems I ran into:
Use the actual button and not the enclosed Button cell
Specify NO = disabled for Multiple values, No selection etc.
Bind the Enabled property to the Master Table's selection and use a property (code in my case), which is present.
Use the transformer NSIsNotNil to enable the button if something is selected in the master table.

Related

How to update an NSTextField programatically and have value propagate through binding

I have a case where I would like to programmatically assign the first responder to an NSTextField and then programmatically insert a value into the NSTextField as well.
I have this working with the following code:
field.window?.makeFirstResponder(field)
field.stringValue = "Auto generated value"
In the example above, field has a binding to my model. If the user chooses to edit Auto generated value and then hit the return key, the first responder is resigned and the edited value is set on my model. BUT, if the user chooses not to edit Auto generated value and hits the return key, the first responder is resigned but the model is not updated.
It's as if the field is not marked as dirty when programmatically updating the value.
I would like to avoid having to manually update the model when the case above occurs due to some complexities that I have left out in the example. I would like it to apply through whichever binding is set just like it would when the user manually types in the value.
Is this possible?
If you use binding with a NSTextField or any other AppKit control, you should perform programmatically value changes on the data source / model, instead of the other way around by manipulating the control value in code. By doing so, you will not have the problems as described in your question.
Inserting the text in the field editor starts an editing session (undocumented feature).
if window.makeFirstResponder(textField1),
let fieldEditor = textField1.currentEditor() as? NSTextView {
fieldEditor.insertText("Auto generated value", replacementRange: NSRange(location: 0, length: fieldEditor.textStorage?.length ?? 0))
}

Bind each row of NSTableView to separate property of object?

I have a main NSArrayController bound to an NSTableView with each row containing an instance of MyObject. MyObject has about 30 properties, but the NSTableView only has 5 columns (to show the most important properties). When a row (or more) is selected I have another NSTableView (a detail view) which shows all the properties, one per row. The detail table has two columns, one for the property name and one for the property value.
I have this working right now but my detail NSTableView uses a data source rather than bindings. This works fine as long as I notify my detail controller (which manages the data source) of a change in the main selection so that it can reload the detail table.
While it is easy to detect a change in selection, it is harder to detect a change in one of the properties of a selected object. I see elsewhere on StackOverflow, that one way to do this is to have a dummy property and use keyPathsForValuesAffectingValueForKey. Does this work well performance-wise?
Is there some other/better way to build my detail NSTableView/NSArrayController so that each row represents one property of the selected objects on the main NSTableView/NSArrayController? I'd like to use bindings if possible.

How to bind hidden property in a control when a integer equals some certain value in OSX?

I've got a RadioGroup with 3 cells. I want to hide some controls when the selected index in radio group is 1. That is:
[someControl setHidden: radioGroup.selectedIndex == 1];
I've got a lot of controls will show/hidden when radio group selection changed. Some might show when the selected index equals 0, some might show when equals 2.
I want it to be done by binding, not connect each control reference using outlet.
How to acheive that?
There are at least two ways of doing this, as binding hidden requires a Boolean balue:
Create a property that is of type BOOL and returns YES or NO based on your value comparison, then in your class use KVO to observe the original value and set the Boolean property inside of the KVO observer (this is required to make sure the object is updated at the right time)
Use bindings alone, but create a Value Transformer to transform each value you need into a BOOL as necessary to be interpreted correctly. There is an existing value transformer that changes YES to NO and vice-versa, but for other value transforms you will have to create these yourself, and there is no good way to parametrize them inside of the xib file.
The first solution is probably easier.

Why is an NSArrayController allowing removal of objects when there's no NSTableView selection?

I have a Core Data app I'm building for OS X. My xib file has an NSArrayController that is bound to the AppDelegate's managed object context. The NSTableView in the window is bound to the array controller's arrangedObjects. The individual columns are bound to the appropriate properties of the array controller's entity, and almost everything is working fine. Except that there's a button on the layout for removing objects which works even when there's no object selected.
It's target is the array controller's remove: action and its Enabled binding is set to the array controller's canRemove key.
My guess is that there's some configuration of the array controller that I need to set so that it disallows removal of objects unless there's a selection, but I can't seem to find it.
What do I have to do to make is so that the array controller won't allow an object to be removed unless there's a selection in the table view?
Yes there is addition thing you need to do in the configuration.
You need to bind the button to Enable property - > Array Controller -> Selection - > Model Keypath -> "#count"..
Below is the image which will help you to fix this issue.
Buttons binding on selection is shown in the image below
It turns out that in addition to the bindings I mentioned, the NSTableView also needs to be bound. Binding its Selection Indexes to the Array Controller.selectionIndexes solved the problem.

NSArrayController: How to programmatically clear selection?

Very simple question that's driving me crazy: What's the proper way to clear the selection of an NSArrayController programmatically?
I'm designing a view with the following components:
NSArrayController *controller1: Bound to an array of objects
NSPopUpView view1: Content bound to controller1.arrangedObjects; Value bound to controller1.selection; "Inserts Null Placeholder" selected
NSArrayController *controller2: Bound to an array stored in controller1.selection
NSPopupView view2: Content bound to controller2.arrangedObjects; value bound to controller2.selection; "Inserts Null Placeholder" selected
Initially, view1's content is populated; controller1 and controller2 have nil selection values; and view1 and view2 display null placeholders. Selection of controller1 causes controller1's selection to change and view2's content to populate. All good.
I'd like to implement a Clear button that clears the selection of controller1, which, thanks to bindings, should also clear the selection of controller2 and reset view1 and view2 to the null placeholder. For the life of me, I can't figure out the proper code for this very simple function. Altering the selection of controller1 fails to update the value shown in view1. Worse, altering the controller1 selection programatically causes weird things to happen in controller2: further selection of values in view1 fails to have any effect on view2.
Things I've tried:
Calling the SetSelectedObjects method of controller1 with an [NSArray new].
Calling the SetSelectedObjects method of controller1 with null.
Calling the SetSelectedIndex method of controller1 with NSNotFound.
Calling the RemoveSelectedIndex method of controller1 with the SelectedIndex property of controller1.
Looking in the Cocoa NSArrayController documentation for any class method or suggestion for clearing the selection value. Nothing there - not even any mention of this being desirable, let alone how to accomplish it.
Any ideas? Thanks...
According to Apples Developer documentation this can be done using setSelectionIndexes:
To deselect all indexes, pass an empty index set.
Objective-C:
[arrayController setSelectionIndexes:[NSIndexSet indexSet]];
Swift:
arrayController.setSelectionIndexes( NSIndexSet() )
Try controller1.selectionIndex = NSIntegerMax; and see if that works. I did a simple test with a label bound to the array controller's selection, and when I set the selectionIndex to NSIntegerMax, the no selection placeholder was displayed in the label.
As it turns out, the NSArrayController page does include a single recommendation for clearing the selection:
setSelectionIndexes:
Sets the receiver’s selection indexes and returns a Boolean value that indicates whether the selection changed.
(BOOL) setSelectionIndexes: (NSIndexSet *) indexes
Discussion
Attempting to change the selection may cause a commitEditing message which fails, thus denying the selection change.
To select all the receiver’s objects, indexes should be an index set with indexes [0...count -1]. To deselect all indexes, pass an empty index set.
However, I had actually tried that, and it still left me with a problem.
To generalize:
Create two classes, A and B, where A contains a property "NSArray *b_list" that contains a list of instances of B's.
Create an application with a property "NSArray *a_list". Populate it with some instances of A, and populate the b_list of each A instance with some B instances.
Create a window with two array controllers, Controller_A (bound to a_list) and Controller_B (bound to Controller_A.selection.b_list).
Create two pop-up buttons in the window, Popup_A (bound to Controller_A.arrangedObjects) and Popup_B (bound to Controller_B.arrangedObjects).
Create a "Clear" button with some logic to clear the selection of Controller_A. ("Some logic" is either the method recommended in the Apple documentation, or any other method.)
Run the application. Select an entry in Popup_A. Notice that Popup_B populates with the instances of Controller_A.selection.b_list, as it should.
Now hit the Clear button.
ERROR: Note that while the content and selection of Popup_A correctly become null, the same doesn't happen in Popup_B: it presents a blank entry with a single selected (blank) item. Note also that the selection properties of Controller_B indicates that there is a selection, but its properties are weird: selectedIndex points to 0 instead of NSNotFound, and selectedIndexes includes a non-empty selection integer range.
This is clearly an error with the binding logic that can definitely cause some exceptions and logical errors. For example, if there's any sort of binding attached to B_controller.selection, clearing A_controller will raise an exception relating to the selection value in B_controller, since it indicates a selection but points to garbage.
My workaround is not to bind anything directly to the B_controller selection. Instead, access B_controller programmatically, and in view of the selection value of A_controller, like this:
// setting some property c to the value of b_controller:
if ((B_controller.selectedIndex == NSNotFound) || A_controller.selectedIndex == NSNotFound))
c = nil;
else
c = [B_controller objectAtIndex: [B_controller.selectedIndex]];
I'm also submitting a bug report to Apple with this info.
Answer to a very old question but still.
I just needed the same thing, and for me this worked:
[arrayController setAvoidsEmptySelection:NO];
NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:
NSMakeRange(NSNotFound, 0)];
[arrayController setSelectionIndexes:indexes];

Resources