1

I am working on a cross-platform dynamic dialog box builder that runs in macOS and Windows. I have inherited this codebase. I am trying to support radio buttons. The issue is that macOS automatically treats all radio buttons as a group if they share the same superview and the same action selector. This code uses one action selector for all buttons and only has one superview for all the controls. So a single group of radio buttons works like magic, but there is no way to have more than one group on the window.

I am looking for suggestions as to how this might be handled.

  • Is there a way to suppress the automatic behavior of NSButtonStyleRadio?
  • Is there a way to dynamically create selectors that point back to the same code? (See below.)

A hackish solution might be to build in some number of radio button selector methods and then not allow more than that many groups on a window. It would look something like this:


- (IBAction)buttonPressed:(id)sender // this is the universal NSButton action
{
    if (!_pOwner) return; // _pOwner is a member variable pointing to a C++ class that handles most of the work
    int tagid = (int) [sender tag];
    _pOwner->Notify_ButtonPressed(tagid);
}

- (IBAction)radioButtonGroup1Pressed:(id)sender
{
    if (_pOwner)
        [(id)_pOwner->_GetInstance() buttonPressed:sender];
}

- (IBAction)radioButtonGroup2Pressed:(id)sender
{
    if (_pOwner)
        [(id)_pOwner->_GetInstance() buttonPressed:sender];
}

- (IBAction)radioButtonGroup3Pressed:(id)sender
{
    if (_pOwner)
        [(id)_pOwner->_GetInstance() buttonPressed:sender];
}

- (IBAction)radioButtonGroup4Pressed:(id)sender
{
    if (_pOwner)
        [(id)_pOwner->_GetInstance() buttonPressed:sender];
}

This example would allow for up to 4 button groups on the window, and I could keep track of which ones I'd used. But I'd really like to avoid hard-coding it this way. Is there some way to do this dynamically? Or equally okay, disable the automatic behavior altogether?

rpatters1
  • 382
  • 1
  • 11
  • Some ideas: Put each group in a subview? Don't use the action and observe the state of the cell instead? Subclass `NSButtonCell` and override `setState:`? – Willeke Aug 05 '22 at 23:57
  • Putting each group in a subview seems like a massive refactoring and retesting job, because all the coordinates would be relative to the subview. (Unless I'm missing something.) I thought of not using the action, but how would I get notified when the state changes? Google did not turn up any obvious event to observe and rather all the answers I found pointed me to using the action. I don't understand what overriding `setState:` would do for me. I can set it to any value I need it to be right now. These buttons are created on the fly. – rpatters1 Aug 06 '22 at 00:08
  • Nevermind, I understand now what you are saying about `setState`. That might work, but I'd have to move heaven and earth to cram it into this codebase the way it's stacked. I'll work with it and see. – rpatters1 Aug 06 '22 at 00:23
  • Okay, I think the answer to my question may be to subclass and override `setState:`. It works quite well. Even better, I can still get the automatic group behavior by assigning nonexistent (but differently named) selectors using `NSSelectorFromString`. If you want to write that up as an answer I'll check it. – rpatters1 Aug 06 '22 at 00:55
  • Actually, rats. Apparently `setState:` is only called for the buttons being turned *off*. Not for the one being turned on. So it isn't exactly ideal after all. I may end up with the canned selectors after all. The good news is they can have as many as they want to add, but they can only get change notifications for the first X that are hard-coded to exist. – rpatters1 Aug 06 '22 at 01:39
  • Did you override `-[NSButton setState:]` or `-[NSButtonCell setState:]`? – Willeke Aug 06 '22 at 08:00
  • `NSButton`. If I'm going to override the `NSButtonCell` one, I'm not sure how to do that from an overrridden `NSButton`. (A hint would be appreciated.) – rpatters1 Aug 06 '22 at 11:52
  • See [How to programmatically allocate a subclass of NSButton with a custom NSButtonCell?](https://stackoverflow.com/questions/15475259/how-to-programmatically-allocate-a-subclass-of-nsbutton-with-a-custom-nsbuttonce) – Willeke Aug 06 '22 at 12:44
  • That actually works well. The only small issue is that it fires when I programmatically set the value as well as when I click it. I don't suppose you know a way around that? – rpatters1 Aug 06 '22 at 23:10
  • I just figured out the answer: I can use `class_addMethod` to add functioning new selectors to my window controller on the fly. Will write it up tomorrow. – rpatters1 Aug 07 '22 at 02:47

1 Answers1

0

In a nutshell, the problem is that one cannot know at compile time how many methods the NSWindowController class needs for radio button groups. So the solution is to add new methods at runtime as needed.

My code has a window controller partially declared as follows.

@interface MyWindowController : NSWindowController

- (IBAction)buttonPressed:(id)sender; // universal button action for all button controls
.
.
.

@end

When the program creates the window, it assigns a new window controller to the window and then creates all the controls on the window. For each control it assigns an action and/or a notification monitor as appropriate. For radio button groups, instead of assigning the buttons directly to buttonPressed:, they can be routed through a button-group specific method that is created as needed.

// groupId: the integer id of the group on the window. (Mine counts from 1, but it doesn't matter.)
// button: the NSButton control being created.

SEL mySelector = NSSelectorFromString([NSString stringWithFormat:@"radioButtonGroup%dPressed:", groupId]);
class_addMethod([MyWindowController class], mySelector, (IMP)_radioButtonGroupPressed, "v@:@");
[button setAction:mySelector];

If Objective-C++ permitted lambdas to be cast as (IMP) I would have used a lambda as the method, but instead I used this static function.

// If class_addMethod allowed lambdas, this would be a lambda in the call to class_Method below.
static void _radioButtonGroupPressed(id self, SEL, id sender)
{
    [self buttonPressed:sender];
}

NSSelectorFromString allows the creation of any arbitrary selector name, and class_addMethod allows one to attach it to code. If the selector has already been added, class_addMethod does nothing.

rpatters1
  • 382
  • 1
  • 11