简体   繁体   中英

tkinter optionmenu hangs when clicked if after_idle is called elsewhere in the program

I have a GUI that processes messages serially as they arrive and get queued up by several websockets. This function sets up a callback to itself using after_idle to perpetuate the process, like so:

 def process_queue(self, flush=False):
        '''
        Check for new messages in the queue periodically.
        Sets up callback to itself to perpetuate the process.
        '''
        if flush:
            while not self.queue.empty():
                self.get_msg()
        else:
            self.get_msg()
            self.master.after_idle(self.process_queue)

I have an OptionMenu widget on the GUI, which hangs and causes the program to crash when it is clicked:

self.plotind = tk.StringVar()
self.plotind.set('MACD')
options = ['MACD']
self.indopts = tk.OptionMenu(self.analysis_frame, self.plotind, *[option for option in options])
self.indopts.grid(row=1, column=1)

If I change the after_idle() to just after() , it works fine.

I assume this is because clicking on an OptionMenu actually sets up its own after_idle() call to open the menu, which is then competing with the one I have in process_queue() .

I can certainly use after() in my function if need be - it might not be optimally fast at processing the queue, but it's not the end of the world. But is there a more graceful way to handle this? Surely most GUIs should be able to handle having after_idle() called somewhere when an OptionMenu exists?

Generally speaking, you should not call after_idle from a function called via after_idle .

Here is why:

Once tkinter starts processing the idle queue, it won't stop until the queue is empty. If there is one item in the queue, tkinter pulls that item off and calls the function. If that function adds something to the queue, the queue is no longer empty so tkinter processes the new item. If this new item puts something on the idle queue, then tkinter will process that, and so on. The queue never becomes empty, and tkinter is never given the chance to do anything else besides service this queue.

A common solution is to use after twice so that the queue has a chance to become empty, and thus allows tkinter to process other non-idle events.

For example, instead of this:

self.master.after_idle(self.process_queue)

... do this:

self.master.after_idle(self.master.after, 1, self.process_queue)

This creates a tiny window for the queue to become empty, allowing tkinter to process other "non-idle" events such as requests to redraw the screen before calling self.process_queue again.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM