Вопрос о многопоточности всплывающей подсказки кнопки Tkinter - PullRequest
0 голосов
/ 01 января 2019

Привет. Я пытаюсь создать кнопки Tkinter, у которых всплывающая подсказка.Я хочу, чтобы всплывающая подсказка задерживалась при вводе и отображалась только в течение определенного времени.Я использую многопоточность для отображения всплывающей подсказки, чтобы не блокировать нажатие кнопки.Я, кажется, очень близок к решению, но есть проблема, которая, я думаю, связана с потоками.Подсказка представляет собой окно верхнего уровня без каких-либо оконных декораций (строка заголовка, границы и т. Д.).При вводе кнопки всплывающая подсказка отображается, но иногда она имеет декорации, а иногда ее нет, иногда она отображается в правильном положении, а иногда нет.Я понятия не имею, почему происходит такое поведение, ниже я разместил тестовый код, который я использую для решения этой проблемы, но теперь я потерян, любая помощь будет принята с благодарностью.

from time import sleep
from tkinter import Tk, Button, Label, Toplevel
from threading import Thread


class _Button(Button):
    def __init__(self, parent, *args, **kwargs):
        self.tooltip_text = kwargs.pop('tooltip', None)
        super().__init__(*args, **kwargs)

        self.t = None
        self.parent = parent
        self.btn_tooltip = None

    def tooltip(self):
        if not self.t:
            self.t = Thread(target=self.tooltip_render)
            self.t.start()

    def tooltip_render(self):
        sleep(0.5)
        if not self.btn_tooltip:
            self.btn_tooltip = Toplevel()
            self.btn_tooltip.wm_overrideredirect(True)

            x, y, cx, cy = self.bbox('insert')
            x += self.winfo_rootx() + 25
            y += self.winfo_rooty() + 25

            self.btn_tooltip.geometry('+%d+%d' % (x, y))
            label = Label(
                self.btn_tooltip, text=self.tooltip_text, background='yellow', borderwidth=1,
            )
            label.pack(ipadx=5, ipady=2)

            sleep(1)
            if self.btn_tooltip:
                self.btn_tooltip.destroy()
                self.btn_tooltip = None
                self.t = None


def enter(e):
    e.widget.tooltip()


def leave(e):
    if e.widget.btn_tooltip:
        e.widget.btn_tooltip.destroy()
        e.widget.btn_tooltip = None
        e.widget.t = None


root = Tk()

bt1 = _Button(root, text='Button 1', tooltip='Tooltip1')
bt1.bind('<Enter>', enter)
bt1.bind('<Leave>', leave)
bt1.grid()

bt2 = _Button(root, text='Button 2', tooltip='Tooltip2')
bt2.bind('<Enter>', enter)
bt2.bind('<Leave>', leave)
bt2.grid(row=0, column=1)


root.mainloop()

Ответы [ 2 ]

0 голосов
/ 04 января 2019
from tkinter import Tk, Toplevel, TclError, Label, Button


class Tooltip:
    def __init__(self, widget, text, delay=750, duration=1500):
        self.widget = widget
        self._tooltip = None

        self._hide_id = None
        self._render_id = None
        self._tooltip_text = text
        self._tooltip_delay = delay
        self._tooltip_duration = duration

        self._enter_bind = self.widget.bind("<Enter>", self.show)
        self._leave_bind = self.widget.bind("<Leave>", self.hide)
        self._button_bind = self.widget.bind("<Button>", self.hide)

    def __del__(self):
        try:
            self.widget.unbind("<Enter>", self._enter_bind)
            self.widget.unbind("<Leave>", self._leave_bind)
            self.widget.unbind("<Button>", self._button_bind)
        except TclError:
            pass

    def show(self, _):
        def render():
            if not self._tooltip:
                self._tooltip = tw = Toplevel(self.widget)
                tw.wm_overrideredirect(True)

                x, y = 20, self.widget.winfo_height() + 1
                root_x = self.widget.winfo_rootx() + x
                root_y = self.widget.winfo_rooty() + y
                self._tooltip.wm_geometry("+%d+%d" % (root_x, root_y))

                label = Label(
                    self._tooltip,
                    text=self._tooltip_text,
                    justify='left',
                    background="#ffffe0",
                    relief='solid',
                    borderwidth=1
                )
                label.pack()
                self._tooltip.update_idletasks()  # Needed on MacOS -- see #34275.
                self._tooltip.lift()
                self._hide_id = self.widget.after(self._tooltip_duration, self.hide)

        if self._tooltip_delay:
            if self._render_id:
                self.widget.after_cancel(self._render_id)
            self._render_id = self.widget.after(self._tooltip_delay, render)
        else:
            render()

    def hide(self, _=None):
        try:
            if self._hide_id:
                self.widget.after_cancel(self._hide_id)
            if self._render_id:
                self.widget.after_cancel(self._render_id)
        except TclError:
            pass

        tooltip = self._tooltip
        if self._tooltip:
            try:
                tooltip.destroy()
            except TclError:
                pass
            self._tooltip = None


def app():
    top = Toplevel()
    top.title("Test tooltip")

    button1 = Button(top, text="Button 1")
    button1.pack()
    Tooltip(button1, "Tooltip for Button 1", delay=500, duration=1500)

    button2 = Button(top, text="Button 2")
    button2.pack()
    Tooltip(button2, "Tooltip for Button 2", delay=0, duration=1500)


if __name__ == '__main__':
    tk = Tk()

    app(tk)

    tk.mainloop()
0 голосов
/ 03 января 2019

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

Вместо этого вам следует использовать средства, которые предоставляет tkinter.А именно, метод after, который может запланировать выполнение кода в будущем.

Все, что вам нужно сделать, это использовать after, чтобы отобразить подсказку, а затем снова использовать after, чтобы закрыть ее.

Базовый шаблон выглядит следующим образом:

def tooltip_render(self):
    # create the tooltip
    self.btn_tooltip = Toplevel()
    ... 
    <the rest of your code to render the tooltip> 
    ...

    # schedule it to go away
    self.after(1000, self.btn_tooltip.destroy)

Далее, снова вызовите render_tooltip, используя after:

def tooltip(self):
    self.after(500, self.tooltip_render)

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...