V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
sungo
V2EX  ›  Python

Python tkinter 如何实现自动滚动的文本框

  •  
  •   sungo · 362 天前 · 1575 次点击
    这是一个创建于 362 天前的主题,其中的信息可能已经有所发展或是发生改变。

    具体来说,我输出一系列结果,随着输出行数增加,能够自动随着输出不断滚动到最新。但是当我需要查看之前的输出结果时(此时输出还在进行中),能够往上查看,当我把滚轮或者进度条滚动到最低点时,自动滚动又重新激活。

    以下是多次问了 poe 后,有点能用的代码。但是还是不能完全实现上面的要求。有高人能指导下吗?

    
    import tkinter as tk
    from tkinter import messagebox
    import threading
    import queue
    import time
    
    def down():
        for i in range(1, 100):
            output_queue.put(f"Downloading attachment {i}\n")
            time.sleep(0.3)
        return
    
    def on_download_click():
    
        try:
            download_thread = threading.Thread(target=down, args=())
            download_thread.start()
        except Exception as e:
            messagebox.showerror("消息", "该日期段没有附件下载")
    
    
    
    def update_output_text():
        # 检查队列是否有新的输出内容
        while not output_queue.empty():
            output_text.insert(tk.END, output_queue.get())
    
        # 如果用户没有手动滚动,或者手动滚动到底部,则自动滚动到底部
        if not fm_main.manually_scrolled or output_text.yview()[1] == 1.0:
            output_text.see(tk.END)
    
        # 通过调用 after 方法实现定时刷新
        fm_main.after(100, update_output_text)
    
    def on_scroll(*args):
        # 判断滚动条是否在底部
        scroll_position = scrollbar.get()
        if scroll_position[1] == 1.0:
            fm_main.manually_scrolled = False
        else:
            fm_main.manually_scrolled = True
    
    
    
    if __name__ == '__main__':
        #    os.chdir(path)
        #    os.system("ls *.zip |xargs -n1 unzip && rm *.zip")
    
        fm_main = tk.Tk()
        fm_main.title("邮件附件批量下载_v1.0")
        fm_main.geometry('600x300')
    
        fm_main.resizable(0, 0)  # 设置窗口 Continuation of the modified code:
    
    
        # 下载按钮
        btn1 = tk.Button(fm_main,
                         text="下载",
                         font=("Arial", 13),
                         width=25,
                         height=2,
                         command=on_download_click)
        btn1.pack()
    
        scrollbar = tk.Scrollbar(fm_main)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
    
        output_text = tk.Text(fm_main, font=("Arial", 12), width=60, height=10)
        output_text.pack(side=tk.LEFT, fill=tk.BOTH)
    
        output_text.config(yscrollcommand=scrollbar.set)
        scrollbar.config(command=output_text.yview)
    
        # ... 其他代码 ...
    
        # 创建队列对象用于线程间通信
        output_queue = queue.Queue()
    
        # 启动定时刷新函数
        fm_main.after(100, update_output_text)
    
        # 设置滚动条手动滚动的回调函数
        output_text.bind("<MouseWheel>", on_scroll)
        output_text.bind("<Button-4>", on_scroll)
        output_text.bind("<Button-5>", on_scroll)
    
        # 标记手动滚动状态的变量
        fm_main.manually_scrolled = False
    
        fm_main.mainloop()
    
    8 条回复    2024-02-01 10:52:33 +08:00
    EngAPI
        1
    EngAPI  
       362 天前
    import tkinter as tk
    from tkinter import messagebox
    import threading
    import queue
    import time

    def down():
    for i in range(1, 100):
    output_queue.put(f"下载附件 {i}\n")
    time.sleep(0.3)
    return

    def on_download_click():
    try:
    download_thread = threading.Thread(target=down, args=())
    download_thread.start()
    except Exception as e:
    messagebox.showerror("错误", "该日期段没有附件下载")

    def update_output_text():
    while not output_queue.empty():
    output_text.insert(tk.END, output_queue.get())

    # 检查滚动条是否在底部
    scroll_position = scrollbar.get()
    if not fm_main.manually_scrolled or scroll_position[1] == 1.0:
    output_text.see(tk.END)

    fm_main.after(100, update_output_text)

    def on_scroll(*args):
    # 判断滚动条是否在底部
    scroll_position = scrollbar.get()
    if scroll_position[1] == 1.0:
    fm_main.manually_scrolled = False
    else:
    fm_main.manually_scrolled = True

    if __name__ == '__main__':
    fm_main = tk.Tk()
    fm_main.title("邮件附件批量下载_v1.0")
    fm_main.geometry('600x300')
    fm_main.resizable(0, 0)

    btn1 = tk.Button(fm_main, text="下载", font=("Arial", 13), width=25, height=2, command=on_download_click)
    btn1.pack()

    scrollbar = tk.Scrollbar(fm_main)
    scrollbar.pack(side=tk.RIGHT, fill=tk.Y)

    output_text = tk.Text(fm_main, font=("Arial", 12), width=60, height=10)
    output_text.pack(side=tk.LEFT, fill=tk.BOTH)

    output_text.config(yscrollcommand=scrollbar.set)
    scrollbar.config(command=output_text.yview)

    output_queue = queue.Queue()
    fm_main.after(100, update_output_text)

    output_text.bind("<MouseWheel>", on_scroll)
    output_text.bind("<Button-4>", on_scroll)
    output_text.bind("<Button-5>", on_scroll)

    fm_main.manually_scrolled = False

    fm_main.mainloop()

    问的 chatgpt3.5 的,你看看能运行么?
    sungo
        2
    sungo  
    OP
       362 天前 via Android
    应该不行,我回头试试看。chatgpt3.5 问了几次,要不能自动滚,要不滚不了。不知道 4.0 的能不能行
    NoOneNoBody
        3
    NoOneNoBody  
       362 天前
    为何不把最新放在前面?
    看你的需求,新>旧,难度时间正序排列是强需求?

    tkinter 我很少写,pyqt 的话
    这里有两个状态,自动滚动和手动滚动,你需要一个状态方式(例如状态机或状态判断)控制两者切换,触发事件是什么,例如 mouse down+mouse up ,以及手动滚动时的事件,所以至少需要三个事件响应函数,以及一个自动滚动函数
    sungo
        4
    sungo  
    OP
       361 天前 via Android
    倒序排列也可以。之所以正序排列是直接让 poe 把输出转换成 gui 的,也没想到那么多。其实这些都不是啥必须的需求,只是想解决这个常见的问题。
    nerkeler
        5
    nerkeler  
       361 天前 via Android
    每次写入绑定滚动拦,默认滚动到最后一列?
    sungo
        6
    sungo  
    OP
       360 天前 via Android
    @nerkeler 输出是从上往下输出的,当然希望不停刷新显示最后一列,也就是最新的输出了
    sungo
        7
    sungo  
    OP
       330 天前
    最后解决了问题,两个方案,一是用 pyqt5 ,设置文本框只读,自动就达成目的。
    sungo
        8
    sungo  
    OP
       330 天前
    方案二
    ```python
    import tkinter as tk
    from tkinter import messagebox
    import threading
    import queue
    import time


    def down():
    for i in range(1, 100):
    output_queue.put(f"Downloading attachment {i}\n")
    time.sleep(0.3)
    return


    def on_download_click():
    try:
    download_thread = threading.Thread(target=down, args=())
    download_thread.start()
    except Exception as e:
    messagebox.showerror("消息", "该日期段没有附件下载")


    def update_output_text():
    # 检查队列是否有新的输出内容
    while not output_queue.empty():
    output_text.insert(tk.END, output_queue.get())
    # 如果用户没有手动滚动,或者手动滚动到底部,则自动滚动到底部
    if not fm_main.manually_scrolled or output_text.yview()[1] == 1.0:
    output_text.see(tk.END)

    # 通过调用 after 方法实现定时刷新
    fm_main.after(100, update_output_text)


    def on_scroll(*args):
    # 判断滚动条是否在底部
    scroll_position = scrollbar.get()
    if scroll_position[1] == 1.0:
    fm_main.manually_scrolled = False
    else:
    fm_main.manually_scrolled = True


    def on_mouse_release(*args):
    on_scroll()


    def on_key_press(event):
    if event.keysym in ["Up", "Down", "Left", "Right"]:
    on_scroll()


    if __name__ == "__main__":
    # os.chdir(path)
    # os.system("ls *.zip |xargs -n1 unzip && rm *.zip")

    fm_main = tk.Tk()
    fm_main.title("邮件附件批量下载_v1.0")
    fm_main.geometry("600x300")

    fm_main.resizable(0, 0) # 设置窗口 Continuation of the modified code:

    # 下载按钮
    btn1 = tk.Button(
    fm_main,
    text="下载",
    font=("Arial", 13),
    width=25,
    height=2,
    command=on_download_click,
    )
    btn1.pack()

    scrollbar = tk.Scrollbar(fm_main)
    scrollbar.pack(side=tk.RIGHT, fill=tk.Y)

    output_text = tk.Text(fm_main, font=("Arial", 12), width=60, height=10)
    output_text.pack(side=tk.LEFT, fill=tk.BOTH)

    output_text.config(yscrollcommand=scrollbar.set)
    scrollbar.config(command=output_text.yview)

    # ... 其他代码 ...

    # 创建队列对象用于线程间通信
    output_queue = queue.Queue()

    # 启动定时刷新函数
    fm_main.after(100, update_output_text)

    # 设置滚动条手动滚动的回调函数
    output_text.bind("<MouseWheel>", on_scroll)
    output_text.bind("<Button-4>", on_scroll)
    output_text.bind("<Button-5>", on_scroll)

    scrollbar.bind("<MouseWheel>", on_scroll)
    scrollbar.bind("<Button-4>", on_scroll)
    scrollbar.bind("<Button-5>", on_scroll)

    scrollbar.bind("<B1-Motion>", on_scroll)
    scrollbar.bind("<ButtonRelease-1>", on_mouse_release)

    # 方向键事件绑定
    fm_main.bind("<Up>", on_key_press)
    fm_main.bind("<Down>", on_key_press)
    fm_main.bind("<Left>", on_key_press)
    fm_main.bind("<Right>", on_key_press)

    # 标记手动滚动状态的变量
    fm_main.manually_scrolled = False

    fm_main.mainloop()
    ```
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5332 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 40ms · UTC 07:55 · PVG 15:55 · LAX 23:55 · JFK 02:55
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.