objective-cmacosnsbutton

Suppress automatic handling of NSButton radio button groups


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.

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?


Solution

  • 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.