subclassing NSComboBox

I have views to present to mouser which can have two states: editing or noediting

In non editing state, the user can only see informations.textfields are not editable and not bezeled:


iif the user has authorization, he can push a button in the toolbar and the view enter editing mode:




in some of my views, I have a combobox

I have subclassed this combo so that in non editing mode, it is replaced by a NSTextField


My code is the following (I just write what I think is necessary, for bigger clarity):


class myControlCombo: NSComboBox {


var myHiddenText: NSTextField!

var state: etatWindow = .nonedition


override var stringValue: String {

get {

return super.stringValue

}

set (S) {

myHiddenText.stringValue = S

super.stringValue = S

}

}

required init?(coder: NSCoder) {

super.init(coder: coder)

myHiddenText = NSTextField(frame: self.frame)

myHiddenText.isBezeled = false

myHiddenText.isEditable = false

myHiddenText.font = self.font

self.superview?.addSubview(myHiddenText)

}

override func draw(_ dirtyRect: NSRect) {

if state == .nonedition {

myHiddenText.draw(dirtyRect)

} else {

super.draw(dirtyRect)

}

}

func setState (state: etatWindow, couleur: NSColor) {

self.state = state

if (state == .nonedition) {

isSelectable = false

//isBezeled = false

isButtonBordered = false

isEditable = false

backgroundColor = couleur

myHiddenText.isHidden = false

} else {

backgroundColor = couleur

//isBezeled = true

isButtonBordered = true

isSelectable = true

isEditable = true

myHiddenText.isHidden = true

}

}

}


I have the following problem: in non editing mode, when the NSTextField is displayed instead of the combobox it is not properly aligned and the font is not correct

Any Ideas why?

Accepted Reply

>> First, why do you speak about ancient controls? do new controls exist?


NSControl and its subclasses represent an implementation strategy devised for macOS 10.0, and probably based on NeXTStep software designs that are even earlier — maybe 25 or 30 years old. Controls are actually wrapper views, around a functional core subclassing NSCell. By modern standards, the techniques are very unstructured, and (because they are old) there are many layers of "encrusted" behaviors and changes in behavior. You only have to look at the way that NSButton properties interact to create the various standard button appearances to realize how messy this has become over time.


That means, as soon as you subclass a control, you are working in a very unpredictable environment. Even something apparently simple, like overriding the "draw()" method can have unforeseen consequences.


That said, subclassing controls isn't impossible. In your case (in the code you posted), I don't see anything wrong, but it's hard to even know whether your code is setting the extra text field properties and alignment correctly. Any solution that treats controls as indivisible units is more likely to be robust. Also, as I noted, the essence of your original requirement was simply the setting of "isHidden" on two different controls, so subclassing hardly seems necessary.


Regarding validation, it's not usual to put custom validation inside a control's implementation, since there exist other standard mechanisms. In particular, there is a validation mechanism built into KVC:


https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/KeyValueCoding/Validation.html


That is, you provide validation methods with names of the form "validate<key>" where "<key>" is the name of the property. Then, typically, you use Cocoa bindings to link the value shown in the control to a property in your data model, and the bindings will invoke the validation whenever the control value changes.


There is also a set of informal protocols (NSEditor/NSEditorRegistration) that mutable controls such as NSTextField implement. In effect, they register themselves with their view controller, and you can add code to your view controller subclass to trigger validation at appropriate times. Unfortunately, this mechanism is very poorly documented.


These are all complex design issues, so I can't tell you the right answer for your situation. However, my general advice is that if you can avoid subclassing controls, you will likely end up with an easier and more robust solution.

Replies

This seems like the wrong approach. You are not really modifying the NSComboBox. Instead, you are substituting a different control. All you really need is two separate controls — one NSComboBox, one NSTextField — with 'isHidden == true' on one of them at a time. No subclassing seems necessary.


If you take that approach, you can design your UI with both controls visible (side-by-side, for example) and just hide one at run-time. That should make it easier to configure both in IB to have the correct alignment and appearance. If you are targeting recent macOS versions, you can even use a stack view to simplify management of their visibility.


Subclassing ancient controls like NSTextField and NSComboBox is difficult, because they're so old that their implementations are quirky. It's hard to discover how to configure them properly in code.

I agree with you, subclassing controls is very difficult


First, why do you speak about ancient controls? do new controls exist?


I already subclass nstextfield, nexcombobox and nscheckbox, because I need them toperform some verifications once the user has finish with the control

for instance some of them are mandatory, some of them require additional verification


there is for instance the code I use in my subclassed control;


func verifControl()->Bool {

let obligatoire: Any? = self.value(forKey: "obligatoire")

if (obligatoire != nil && (obligatoire as! Bool) == true ) {

if stringValue == "" {

let alert = NSAlert()

alert.messageText = "zone obligatoire"

alert.runModal()

return false

}

}

let nomMethode: String = "verif"+(self.identifier?.capitalized)!+"WithCtrl:"

let methode = Selector(nomMethode)

if myController != nil && myController.responds (to: methode) {

let res = myController.perform(methode, with:self as NSControl)

let value = Unmanaged<AnyObject>.fromOpaque(

res!.toOpaque()).takeUnretainedValue() as! Bool

return value

}

else {

return true

}

}


if the windowController contains a method verifIdent, the control whose identifier is ident will automatically call this method (this method can be very sofisticated, as verifying the data from a WebService)

so I really need subclassing controls, and I have this problem with NSComboBox controls


I code programs for a very long time (more than 30 years) but I am very new in Swift, XCode and OSX programmation

>> First, why do you speak about ancient controls? do new controls exist?


NSControl and its subclasses represent an implementation strategy devised for macOS 10.0, and probably based on NeXTStep software designs that are even earlier — maybe 25 or 30 years old. Controls are actually wrapper views, around a functional core subclassing NSCell. By modern standards, the techniques are very unstructured, and (because they are old) there are many layers of "encrusted" behaviors and changes in behavior. You only have to look at the way that NSButton properties interact to create the various standard button appearances to realize how messy this has become over time.


That means, as soon as you subclass a control, you are working in a very unpredictable environment. Even something apparently simple, like overriding the "draw()" method can have unforeseen consequences.


That said, subclassing controls isn't impossible. In your case (in the code you posted), I don't see anything wrong, but it's hard to even know whether your code is setting the extra text field properties and alignment correctly. Any solution that treats controls as indivisible units is more likely to be robust. Also, as I noted, the essence of your original requirement was simply the setting of "isHidden" on two different controls, so subclassing hardly seems necessary.


Regarding validation, it's not usual to put custom validation inside a control's implementation, since there exist other standard mechanisms. In particular, there is a validation mechanism built into KVC:


https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/KeyValueCoding/Validation.html


That is, you provide validation methods with names of the form "validate<key>" where "<key>" is the name of the property. Then, typically, you use Cocoa bindings to link the value shown in the control to a property in your data model, and the bindings will invoke the validation whenever the control value changes.


There is also a set of informal protocols (NSEditor/NSEditorRegistration) that mutable controls such as NSTextField implement. In effect, they register themselves with their view controller, and you can add code to your view controller subclass to trigger validation at appropriate times. Unfortunately, this mechanism is very poorly documented.


These are all complex design issues, so I can't tell you the right answer for your situation. However, my general advice is that if you can avoid subclassing controls, you will likely end up with an easier and more robust solution.

I think I understand. I'll try to look after another architecture.

So I surely come back to you

anyway, thank's a lot for yout advices and your time