Search code examples
cocoansbutton

How do I prevent an NSButton from registering presses while disabled?


Newbie Warning

I have a simple but vexing problem trying to disable an NSButton. Here is sample code to illustrate the problem:

- (IBAction)taskTriggeredByNSButtonPress:(id)sender {
[ibOutletToNSButton setEnabled:NO];
//A task is performed here that takes some time, during which time
//the button should not respond to presses.
//Once the task is completed, the button should become responsive again.
[ibOutletToNSButton setEnabled:YES];
}

This is what I observe. I press the button. The button becomes disabled (judging by its faded appearance), and the task begins executing. While the button is disabled and the task is executing, I press the button a second time. Nothing happens immediately, but once the task is completed, the taskTriggeredByNSButtonPress: method is called a second time, suggesting that the second button press was placed on hold and then activated once the button became re-enabled.

I've tried all kinds of hacks to prevent the second button press from being recognized, including introducing a time delay after the [ibOutletToNSButton setEnabled:NO]; statement, making the button hidden rather than disabled, covering the button with a custom view during the time it should be disabled, binding the button's enabled status to a property, and other things I'm too embarrassed to mention.

Please help me understand why I can't get this simple task of disabling the button to work.


Solution

  • This method seems to be directly linked to the button. You should perform the long action on another thread, or the main runloop won't be available until the method returns. The main runloop doesn't respond to events while it's not available.

    First, create a method:

    - (void)someLongTask: (id)sender {
        // Do some long tasks…
    
        // Now re-enable the button on the main thread (as required by Cocoa)
        [sender performSelectorOnMainThread: @selector(setEnabled:) withObject: YES waitUntilDone: NO];
    }
    

    Then, perform that method in a separate thread when the button's clicked:

    - (IBAction)buttonPushed: (id)sender {
        [sender setEnabled: NO];
        [self performSelectorInBackground: @selector(someLongTask) withObject: nil];
    }
    

    You may replace self in the example above with the object where -someLongTask resides.

    By multi-threading, you leave the main runloop alone and stable. Maybe, your problem will be solved. Otherwise, you solved a problem with responsiveness.

    (By the way, if the method is only called by the button, the sender argument is set to the button. That way, you don't need to use an outlet in the method. But that's just a hint.)