How can I prevent NSButton from registering presses when disconnected?

Beginner Warning

I have a simple but nasty problem trying to disable 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 am observing. I press the button. The button becomes disabled (judging by its faded appearance), and the task starts to run. While the button is disabled and the task is completed, I press the button a second time. Nothing happens immediately, but as soon as the task is completed, the taskTriggeredByNSButtonPress: method is called a second time, assuming that the second button has been paused and then activated after the button is turned back on.

I tried all kinds of hacks to prevent pressing the second button, including the introduction of a time delay after the instruction [ibOutletToNSButton setEnabled:NO]; , forcing the button to hide rather than disable, closing the button with a custom view while it should be disabled, binding the status of the enabled button to the property, and other things that I'm too confused to mention.

Please help me understand why I cannot get this simple task of disabling a button to work.

+4
source share
2 answers

This method seems to be directly related to the button. You must perform a long action on another thread, or the main runloop will not be available until the method returns. The main runloop does not respond to events while they are unavailable.

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 execute this method in a separate thread when the button is clicked:

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

You can replace self in the above example with the object where -someLongTask is located.

Thanks to multithreading, you leave the main runloop independent and stable. Perhaps your problem will be resolved. Otherwise, you have solved the responsiveness problem.

(By the way, if the method is called only by the button, the sender argument is set to the button. Thus, you do not need to use the output in the method, but this is just a hint.)

+4
source

You should not perform tasks that require a lot of processing time in the main event loop. This is what you do, and the entire user interface of the application will be blocked during the execution of your code. Locking the main thread is causing the "Rotating Pizza of Death". In other words, do not do this.

What you need to do is break your time-consuming code so that it works in another thread, that is, simultaneously in the background. When the background task completes, it should somehow notify the code running in the main thread that it completed. Your code in the main thread can then update the interface accordingly.

There are many ways to do this.

You can use the NSThread methods suggested by Randy Marsh. However, you must be very careful to read the documentation, as you cannot just call any old method in the background thread and expect it to work. You must create your own auto-resource pool in the stream and properly manage it. You should not call any methods that update the user interface from the secondary stream. You must be extremely careful that no variables are accessed or changed more than one thread at a time. Threading is a complex business.

The -performSelectorInBackground:withObject: NSObject is essentially a simple way to use NSThread and has the same caveats.

You can use the NSOperation and NSOperationQueue , which are especially good if you can break your task into small pieces that can be executed at the same time.

The easiest way to deal with this is GCD (Grand Central Dispatch) , which allows you to use built-in blocks to record background processes:

 - (IBAction)taskTriggeredByNSButtonPress:(id)sender { [ibOutletToNSButton setEnabled:NO]; //get a reference to the global GCD thread queue dispatch_queue_t queue = dispatch_get_global_queue(0,0); //get a reference to the main thread queue dispatch_queue_t main = dispatch_get_main_queue(); //perform long-running operation dispatch_async(queue,^{ NSLog(@"Doing something"); sleep(15); //update the UI on the main thread dispatch_async(main,^{ [ibOutletToNSButton setEnabled:YES]; }); }); } 

GCD is very lightweight and effective, and I highly recommend that you use it if possible.

Apple has more information and details about the Concurrency Programming Guide , which I recommend you read, although some details may be outside of you at this point.

+3
source

Source: https://habr.com/ru/post/1388671/


All Articles