簡體   English   中英

在 tkinter python 中添加滾動條以更新標簽?

[英]Add scrollbar to updating label in tkinter python?

我遇到的問題是我找不到將滾動條與當前代碼合並的方法。 我有一個更新標簽,它添加了由子進程吐出的文本,但隨着更多數據的創建,數據消失了。 在這種情況下,數字消失在視線之外。 這是我當前的代碼:

import logging
import os
import sys
from subprocess import Popen, PIPE, STDOUT
from threading import Thread

try:
    import tkinter as tk
except ImportError: # Python 3
    import tkinter as tk

info = logging.getLogger(__name__).info

# define dummy subprocess to generate some output
cmd = [sys.executable or "python", "-u", "-c", """
import itertools, time

for i in itertools.count():
    print(i)
    time.sleep(0.5)
"""]

class ShowProcessOutputDemo:
    def __init__(self, root):
        """Start subprocess, make GUI widgets."""
        self.root = root

        # start subprocess
        self.proc = Popen(cmd, stdout=PIPE, stderr=STDOUT)

        # stop subprocess using a button
        tk.Button(root, text="Stop subprocess", command=self.stop).pack()

        self.label = tk.Label(root) # put subprocess output here
        self.label.pack()

        # Create a buffer for the stdout
        self.stdout_data = ""
        # Create a new thread that will read stdout and write the data to
        # `self.stdout_buffer`
        thread = Thread(target=self.read_output, args=(self.proc.stdout, ))
        thread.start()

        # A tkinter loop that will show `self.stdout_data` on the screen
        self.show_stdout()

    def read_output(self, pipe):
        """Read subprocess' output and store it in `self.stdout_data`."""
        while True:
            data = os.read(pipe.fileno(), 1 << 20)
            # Windows uses: "\r\n" instead of "\n" for new lines.
            data = data.replace(b"\r\n", b"\n")
            if data:
                info("got: %r", data)
                self.stdout_data += data.decode()
            else:  # clean up
                info("eof")
                self.root.after(5000, self.stop) # stop in 5 seconds
                return None

    def show_stdout(self):
        """Read `self.stdout_data` and put the data in the GUI."""
        self.label.config(text=self.stdout_data.strip("\n"))
        self.root.after(100, self.show_stdout)

    def stop(self, stopping=[]):
        """Stop subprocess and quit GUI."""
        if stopping:
            return # avoid killing subprocess more than once
        stopping.append(True)

        info("stopping")
        self.proc.terminate() # tell the subprocess to exit

        # kill subprocess if it hasn't exited after a countdown
        def kill_after(countdown):
            if self.proc.poll() is None: # subprocess hasn't exited yet
                countdown -= 1
                if countdown < 0: # do kill
                    info("killing")
                    self.proc.kill() # more likely to kill on *nix
                else:
                    self.root.after(1000, kill_after, countdown)
                    return # continue countdown in a second

            self.proc.stdout.close()  # close fd
            self.proc.wait()          # wait for the subprocess' exit
            self.root.destroy()       # exit GUI
        kill_after(countdown=5)

logging.basicConfig(level=logging.INFO, format="%(asctime)s %(message)s")
root = tk.Tk()
app = ShowProcessOutputDemo(root)
root.protocol("WM_DELETE_WINDOW", app.stop) # exit subprocess if GUI is closed
root.mainloop()
info("exited")

我知道標簽不支持滾動條,但文本支持。 雖然這將是一個很好的解決方案,但我還沒有找到將當前輸出數據轉換為文本的方法。

感謝閱讀 - 康納

在 tkinter 上運行良好的唯一解決方案是在畫布上打印一個框架,然后使畫布可滾動。 我的例子和你的一樣,你只需要像我一樣使用Scrolable_Frame

from subprocess import Popen, PIPE, STDOUT
from threading import Thread
import tkinter as tk
import logging
import time
import sys

info = logging.getLogger(__name__).info
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(message)s")

platform = windows = 'mswin'

# define dummy subprocess to generate some output
cmd = [sys.executable or "python", "-u", "-c", """
import itertools, time

for i in itertools.count():
    print(i)
    time.sleep(0.5)
exit()
""", "exit"]

class OStream(Thread):

    enable_print = True

    def handel_line(self, line):
        if platform == windows:
            # Windows uses: "\r\n" instead of "\n" for new lines.
            line = line.replace(b"\r\n", b"\n")
        if self.enable_print:
            info("got: %r", line)
        if self.stream_print is not None:
            self.stream_print(line)

    def stop(self):
        self.alive = False

    def run(self):
        while self.alive:
            for s in self.ostrams:
                line = s.stdout.readline()
                if line:
                    self.handel_line(line)
            time.sleep(0.2)
        info("OStream Exit")

    def pipe_proc(self, stream):
        self.ostrams.append(stream)

    def stream_callback(self, func):
        self.stream_print = func

    def __init__(self):
        self.ostrams = []
        self.alive = True
        self.stream_print = None
        Thread.__init__(self)

class Scrolable_Frame(tk.Frame):

    def get(self):
        return self.interior

    def __init__(self, master):
        tk.Frame.__init__(self, master)

        self.scroll = tk.Scrollbar(self)
        self.scroll.pack(side=tk.RIGHT, fill=tk.Y)
        self.canvas = tk.Canvas(
            self, bd=0, highlightthickness=0,
            yscrollcommand=self.scroll.set)
        self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=tk.TRUE)
        self.scroll.config(command=self.canvas.yview)

        self.canvas.xview_moveto(0)
        self.canvas.yview_moveto(0)

        self.interior = tk.Frame(self.canvas)
        interior_id = self.canvas.create_window(
            0, 0, window=self.interior, anchor=tk.NW
        )

        def _configure_interior(_):
            size = (self.interior.winfo_reqwidth(), self.interior.winfo_reqheight())
            self.canvas.config(scrollregion="0 0 %s %s" % size)
            if self.interior.winfo_reqwidth() != self.canvas.winfo_width():
                self.canvas.config(width=self.interior.winfo_reqwidth())
        self.interior.bind('<Configure>', _configure_interior)

        def _configure_canvas(_):
            if self.interior.winfo_reqwidth() != self.canvas.winfo_width():
                self.canvas.itemconfigure(interior_id, width=self.canvas.winfo_width())
        self.canvas.bind('<Configure>', _configure_canvas)

        def _on_mousewheel(event):
            self.canvas.yview_scroll(int(-1 * (event.delta / 120)), 'units')
        self.canvas.bind_all("<MouseWheel>", _on_mousewheel)

class ShowProcessOutputDemo(tk.Tk):
    def __init__(self):
        """Start subprocess, make GUI widgets."""
        tk.Tk.__init__(self)
        self.geometry('300x200+500+300')
        self.protocol("WM_DELETE_WINDOW", self.stop)

        self.proc = Popen(cmd, stdout=PIPE, stderr=STDOUT)

        self.ostream = OStream()
        self.ostream.pipe_proc(self.proc)
        self.ostream.stream_callback(self.add_label)
        self.ostream.start()

        self.exit_button = tk.Button(
            self, text="Stop subprocess", command=self.stop
        )
        self.exit_button.pack(pady=20)

        self.scolable_frame = Scrolable_Frame(self)
        self.scolable_frame.pack(
            expand=True, fill=tk.BOTH, pady=20, padx=20
        )

    def add_label(self, line):
        line_text = 'OStream line content: {0}'.format(line[:-1].decode())
        tk.Label(
            self.scolable_frame.get(), text=line_text
        ).pack(anchor=tk.CENTER, expand=True, fill=tk.X)

    def stop(self):
        """Stop subprocess and quit GUI."""

        self.ostream.stop()

        self.proc.kill()
        self.proc.stdout.close()
        self.proc.wait(timeout=2)

        info("GUI Exit")
        self.quit()


if __name__ == '__main__':
    app = ShowProcessOutputDemo()
    app.mainloop()

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM