tkinter tkMessageBox не работает в потоке - PullRequest
2 голосов
/ 10 августа 2011

У меня есть класс tkinter и некоторые функции в нем (предположим, что присутствуют все другие функции для запуска GUI). что я сделал, я запустил одну self.function как поток из другой self.function и в поточной функции при ошибке я хочу использовать tkMessageBox.showerror ('Some Error'), но это не работает в поточной функции, и моя программа получила застрял. msgbox работает в другой функции.

import threading
from Tkinter import *
import Pmw
import tkMessageBox

class tkinter_ui:
      def __init__(self, title=''):
      ... assume all functions are present ...

      def login(self, username, password)
          if password == "":
             tkMessageBox.showerror('Login Error', 'password required') # but on this msg box program become unresponsive why???

      def initiateLogin(self)
          tkMessageBox.showinfo('Thread', 'Started')   #you see this msg box works
          self.t = threading.Timer(1, self.login)
          self.t.start()

Ответы [ 3 ]

9 голосов
/ 16 июля 2012

Поскольку я застрял в той же проблеме и не нашел надлежащего, хорошо объясненного решения, я хотел бы поделиться базовой стратегией, с которой я столкнулся.

Обратите внимание, что это не единственный и не лучший способ сделать многопоточность с помощью tkinter, но он довольно прост и должен сохранить ваш рабочий процесс, если вы разрабатывали свой код, не зная о поточности потоков в tkinter.

Почему темы?

Прежде всего, я решил использовать потоки, видя, что блокирующие действия, такие как os.popen, subprocess.call, time.sleep и т. П. «заморозить» графический интерфейс, пока он не запустится (конечно, это может быть не так, поскольку потоки полезны сами по себе по многим причинам, а иногда они просто необходимы).

Вот так выглядел мой код перед использованием потоков:

from Tkinter import *
import tkMessageBox
from time import sleep

# Threadless version.
# Buttons will freeze the GUI while running (blocking) commands.

def button1():
    sleep(2)
    tkMessageBox.showinfo('title', 'button 1')

def button2():
    sleep(2)
    tkMessageBox.showinfo('title', 'button 2')

root = Tk()
frame = Frame(root)
frame.pack()

Frame(root).pack( side = BOTTOM )
Button(frame, command=button1, text="Button 1").pack( side = LEFT )
Button(frame, command=button2, text="Button 2").pack( side = LEFT )
root.mainloop()

Багги с резьбой

Затем я превратил команды, вызываемые кнопками, в потоки. Таким образом, графический интерфейс не зависнет.

Я думал, что все в порядке, но в Windows этот код приводит к непоправимому сбою интерпретатора из-за того, что tkMessageBox вызывается из потоков, отличных от того, в котором работает корень tkinter:

from Tkinter import *
import tkMessageBox
from time import sleep
import threading

# Buggy threads.
# WARNING: Tkinter commands are run into threads: this is not safe!!!

def button1():
    sleep(2)
    tkMessageBox.showinfo('title', 'button 1')

def button2():
    sleep(2)
    tkMessageBox.showinfo('title', 'button 2')

def start_thread(fun, a=(), k={}):
    threading.Thread(target=fun, args=a, kwargs=k).start()

root = Tk()
frame = Frame(root)
frame.pack()

Frame(root).pack( side = BOTTOM )
Button(frame, command=lambda: start_thread(button1), text="Button 1").pack( side = LEFT)
Button(frame, command=lambda: start_thread(button2), text="Button 2").pack( side = LEFT )
root.mainloop()

Потоковая версия

Когда я обнаружил отсутствие потока в tkinter, я написал небольшую функцию tkloop, которая будет запускаться в основном потоке каждые несколько миллисекунд, проверяя запросы и выполняя запрошенные (tkinter) функции от имени потоков, которые хотят их запустить .

Двумя ключами здесь является метод widget.after, который " регистрирует функцию обратного вызова, которая будет вызываться через определенное количество миллисекунд " и Queue ставить и получать запросы.

Таким образом, поток может просто поместить кортеж (function, args, kwargs) в очередь вместо вызова функции, что приводит к безболезненному изменению исходного кода.

Это последняя, ​​поточно-ориентированная версия:

from Tkinter import *
import tkMessageBox
from time import sleep
import threading
from Queue import Queue

# Thread-safe version.
# Tkinter functions are put into queue and called by tkloop in the main thread.

q = Queue()

def button1():
    sleep(2)
    q.put(( tkMessageBox.showinfo, ('title', 'button 1'), {} ))

def button2():
    sleep(2)
    q.put(( tkMessageBox.showinfo, ('title', 'button 2'), {} ))

def start_thread(fun, a=(), k={}):
    threading.Thread(target=fun, args=a, kwargs=k).start()

def tkloop():
    try:
        while True:
            f, a, k = q.get_nowait()
            f(*a, **k)
    except:
        pass

    root.after(100, tkloop)


root = Tk()
frame = Frame(root)
frame.pack()

Frame(root).pack( side = BOTTOM )
Button(frame, command=lambda: start_thread(button1), text="Button 1").pack( side = LEFT)
Button(frame, command=lambda: start_thread(button2), text="Button 2").pack( side = LEFT )
tkloop() # tkloop is launched here
root.mainloop()

Редактировать: двусторонняя связь : если ваши потоки должны получать информацию из основного (например, возвращаемые значения из функций tkinter), вы можете отредактировать интерфейс tkloop, добавив очередь для возвращаемых значений , Вот пример, основанный на коде выше:

def button1():
    q1 = Queue()
    sleep(2)
    q.put(( tkMessageBox.askokcancel, ('title', 'question'), {}, q1 ))
    response = 'user said ' + 'OK' if q1.get() else 'CANCEL'
    q.put(( tkMessageBox.showinfo, ('title', response), {}, None ))

# ...

def tkloop():
    try:
        while True:
            f, a, k, qr = q.get_nowait()
            r = f(*a, **k)
            if qr: qr.put(r)
    except:
        pass

    root.after(100, tkloop)
5 голосов
/ 10 августа 2011

tkinter не является потокобезопасным - вы не можете надежно вызывать любые функции tkinter из любого потока, кроме того, в котором вы инициализировали tkinter.

0 голосов
/ 07 апреля 2016

Если вы хотите, чтобы ваш другой поток блокировался до получения ответа (например, вы хотите задать вопрос и дождаться ответа), вы можете использовать эту функцию:

def runInGuiThreadAndReturnValue(self, fun, *args, **kwargs):
    def runInGui(fun, ret, args, kwargs):
        ret.append(fun( *args, **kwargs))
    ret = []
    sleeptime = kwargs.pop('sleeptime', 0.5)
    self.after(0, runInGui, fun, ret, args, kwargs)
    while not ret:
        time.sleep(sleeptime)
    return ret[0]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...