How to set value of NSComboBox by KVC? - objective-c

I have several NSComboBoxes created in Interface Builder, and one created programmatically.
The NSComboBoxes created in Interface Builder are perfect. I have their Value bound to the Shared User Default Controller, and when their view is opened they are sitting there with the correct value. Importantly, this value is shown as "normal" text in the NSComboBox. In other words, the value doesn't appear selected. This is what I want.
For the NSComboBox that is created programmatically, I set the value using selectItemAtIndex:0. When I do this, the correct item is selected--but the text appears selected. I.e., it is highlighted and everything. I don't want this. Here are the workarounds I've attempted:
(i) Get the field editor and set insertion point to the end of the text. This doesn't work although, oddly, the field editor's string is either nil or empty when doing this. I'm not sure if this is correct behavior for the field editor.
(ii) Trying various other ways of setting the combo box's value, such as setObjectValue, takeStringValueFrom, etc.
(iii) Finally, and most frustratingly, I tried to set the value of the NSComboBox using [myComboBox setValue:#"The Default Item" forKey:#"value"]; This fails with objc_exception_throw, presumably because there is no such KVC key. But I know that the value of the combo box can be set by KVO, because it works in interface builder! I'm guessing that I don't know the correct key path. I tried to enumerate all the properties using introspection, but I can't get the code working right (objc_property_t isn't in the expected headers).
So, I have two questions:
First, does anyone know how to set a default value for NSComboBox programmatically, so that the text in the box isn't selected? I will go to any lengths to do this, including a new NSComboBoxCell subclass, if it comes to that.
Second, can somebody tell me what key or key path IB is using to set the value of an NSComboBox? Or alternatively, why my efforts to do this are failing?
I've been working on this for many hours now and I am truly disspirited!
THANK YOU, mustISignUp! So nice to have this fixed. A little bit of followup:
(i) Selection of the text is definitely caused by focus. Calling setRefusesFirstResponder:YES fixes the problem. Unfortunately, the window really wants to focus on this combo box, as setting refusesFirstResponder back to NO (later, after window inititation) causes text selection again (I do want the user to be able to focus on this box if he desires). Therefore, in my case, the definitive solution was to call [window makeFirstResponder:otherControl]. Oddly, though [window makeFirstResponder:nil] doesn't work. Any ideas why?
(ii) Thanks for pointing out the difference between bindings and properties. I learned a lot while looking into this question. For one, I learned that you can get a list of bindings by calling - (NSArray *)exposedBindings, which for NSComboBox returns (fontSize, alignment, toolTip, fontName, enabled, contentValues, fontFamilyName, font, hidden, fontItalic, textColor, value, content, editable, fontBold). Second, I was able to set the value using [myComboBox bind:#"value" toObject:[NSMutableString stringWithString:#"defaultValue"] withKeyPath:#"string" options:nil], where NSMutableString has a category on it that turns "string" into a property. Finally, this actually doesn't fix the text selection "problem". The difference between text selection with this combo box and those in Interface Builder must be their position in the window...I guess that this combo box is just slated to become initialFirstResponder while the others weren't.
So my only remaining question might be why [window makeFirstResponder:nil] doesn't work to take focus off the combo box. Not super-important, but I'd be curious if anybody has an idea.

Firstly, i think the text is selected because calling selectItemAtIndex: has made the comboBox the firstResponder. You could use setRefusesFirstResponder:YES or you could make another item the first responder to make the text not appear selected.
If i have understood correctly and you want to change the selection of the comboBox you are doing it the right way.
Secondly, you are confusing Bindings and KVC. NSComboBox has a binding called 'value', not a property called 'value'. It is meaningless to try to set it with setValue:forKey:, and Interface Builder definitely isn't doing this.
You would be right in thinking this is un-obvious and confusing and maybe better names could have been chosen for the bindings.

Related

Best way to edit long text in a QTableWidget cell?

What I want to achieve: an easier way to edit longer text (one or two paragraphs) in a QTableWidget cell.
The problem: resizeRows wraps the text in the cell when the table is displayed. However, if I try to edit the cell, all the text appears on one line which makes it awkward to edit.
What I've tried: I tried implementing a QTextEdit widget for editing:
class EditDelegate(QStyledItemDelegate):
def createEditor(self, parent, option, index):
editor = super().createEditor(parent, option, index)
if isinstance(editor, QLineEdit):
textedit = QTextEdit(parent)
textedit.setPlainText(editor.text())
return textedit
That fixed the editing problem, although I have to rely on the edit box losing focus to save the text. However, after editing, the default QTableWidgetItem now shows HTML in the edited cell. I had thought that using setPlainText() would prevent this. The only text entered was "THE NOTE TEXT", seen in the middle of this screenshot:
If I then try to edit the cell again, the HTML is stripped out and just the original text ("THE NOTE TEXT") remains in the QTextEdit box until again I exit the editor.
Questions:
Is my approach a good way to implement multiline editing in a
QTableWidget?
If so, how can I get rid of the HTML markup that the
QTableWidgetItem is now displaying?
Since you only need a multiple line editor and no formatting (colors, text style, etc.) then use QPlainTextEdit.
The reason behind the returned HTML is that the default property (see the USER attribute) of QTextEdit is html: item delegates use the USER property as default to set the value of the editor and then set it back on the model, meaning that setEditorData() will behave like using setHtml() and setModelData() like toHtml().
The user property for QPlainTextEdit is just plainText, so there will be no conversion in either case.
Also note that your implementation of createEditor() will potentially raise an exception if the default editor is not a QLineEdit (if the data uses numbers or Qt dates/times), since textexit is only declared in the if, but that's a bad way to do so anyway (you're creating a widget that won't be used).
Besides, calling textedit.setPlainText(editor.text()) is useless, because at that point the editor is still empty and it will be filled only by setEditorData() (which would overwrite its contents anyway).
A more correct approach would check the data type and return the editor accordingly, then let the delegate fill the widget in its own.
def createEditor(self, parent, option, index):
if isinstance(index.data(), str):
return QTextEdit(parent)
return super().createEditor(parent, option, index)
Finally, be aware that many have tried to implement multiple line editors in item views, and very few attempts actually achieve a valid result. Many issues are related with the available item geometry, cursor navigation, UX aspects and focus management, and almost any solution has to accept some level of compromise that many users might find annoying. The most common acceptable (and less "hated") solution is to use an external editor, either shown in another part of the UI or through a dialog window.

Disregard sheet with single text field using escape key

So this is my setup:
A trivial NSWindow with a single NSTextField for user entry plus an 'Accept' type NSButton.
The window is displayed for my document window as a sheet using beginSheetModalForWindow:completionHandler:
I'd like to close the sheet when the user presses the escape key, however all I get is the funny system sound telling me it's not really expecting an escape key at that point.
There are several similar questions suggesting we should just check for cancelOperation somewhere in the responder chain. Thing is, my window controller (for the window/dialog in the sheet) isn't even in the responder chain when I'm checking it in the debugger.
For the sheet displayed in the way aforementioned the responder chain is still all about my document window. Weird.
So any hints how to properly hook up to either cancelOperation or the Escape key would be much appreciated!
Since your sheet can be cancelled, you should probably add a Cancel button to it to make that possibility clear to the user. If you do that, and set the keyboard shortcut of that Cancel button to the escape key in IB, then it ought to Just Work. I think it is important that the name of the button is actually "Cancel", though; I recall reading that AppKit does some special glue magic behind the scenes based on that button name.
And by the way, it seems like your window controller should be in the responder chain, assuming it is an actual NSWindowController set up in the proper way; see Apple's doc here. Not sure why the debugger indicates that it isn't, but it should be, as far as I can tell. If it really isn't, that might indicate something more deeply wrong with how you're setting up the sheet. I'm not sure about that, though, since I haven't tried it myself.

Cocoa Bindings with NSComboBox

I have Cocoa Bindings working with a NSComboBox that shows and autocompletes values based on a Managed Object Context. My issue is trying to get the current selection after the user either selects from the dropdown or the autocomplete text is used. I know that the Array Controller class has a selected objects property, but when I try to use it to pull out the selected object I get nothing. With a NSComboBox do I have to set the selection once the text/selection of the combo box occurs or is there something I'm missing setting up the Array Controller.
Thanks
A combo box allows any arbitrary string to be entered, right? (You're not limited to the items you can autocomplete, unlike a popup menu.) So it doesn't have a concept of selected item, since the text in it might not correspond to any item in your database.
This question seems to address a similar issue, declaring it to be unsolvable using only bindings, and links to a blog post that has some hints on what code needs to be added. The gist of it is that, when the user finishes editing the combo box, you create your own fetch request in code and use the response from that to link up the model.

Interface Builder NSStaticText value needs to be updated every time anything changes in dialog

I have a modal dialog thats building a string. The string is shown to the user, and the user presses checkboxes, radio boxes, etc to build the string. The string exists nowhere - I build it for display in -()builtString; from the configuration of the self.valuesDict.
I can easily wire up the checkboxes via bindings in IB: for example to the files owner (the controller) with self.valuesDict.checkbox1
Also I bound the display of the string to "self.builtString".
But every time any checkbox changes, I want to redisplay the string that's shown to the user.
If I abandon bindings, then I think I can use the [self willChangeValueForKey:#"builtString"], for each checkbox, etc, I think, but that is some messy looking code by the time I deal with them all.
So how do you tell a nstextfield in IB to update every time say self.valuesDict changes?
Thanks for any comments/suggestions.
--Tom
You can specify dependencies between bindings. Just write a class method +(NSSet*) keyPathsForValuesAffectingBuiltString that returns a set with all key paths of properties builtString depends on. Then things bound to your string will also be updated when one of the other properties is changed. For more details and step-by-step instructions you can look at this article.

How would you make a checkbox in a Outline View become checked when all it's children's checkbox's are checked?

How would you make a checkbox which is on a parents/groups row in an outline view become checked when all it's children's checkbox's are checked. But when only some are checked display a line. Here is an example of what I'm talking about:
alt text http://snapplr.com/snap/042v
Whenever you check a box, check to see if all its siblings are checked. If so, check the parent. (And run the check again on that level.)
To do that... well, your NSOutlineView has a data source, right? Something that's telling it what to display? Well, in the method called by the checkbox when it's clicked (whatever you set that up to be), get the parent object behind the row which was clicked, and then update its status. If you don't have a direct reference to the checkbox from the model, you can use -[NSOutlineView parentForItem:] to find it.
Hopefully that gives you enough to get started. If it doesn't, then you'll need to ask a more specific question, detailing exactly which parts you're having trouble with.