やりたいこと
- 前回は一行ずつffmpegからの標準出力が表示されるプログレスバーを作った↓
- これだと,通しで出力を確認することができない.出力内容をコピペすることもできない.
- そこで,コンソールで表示されるときと同じようにテキストボックスに表示させる.
- 完成したもの↓
GUIの内容
- ffmpegで処理中に出力されるメッセージが一行ずつテキストボックスに追加される.
- テキストボックスは一番下の最新行が表示されるよう自動にスクロールする.
- テキストボックスの右側にスクロールバーをつける.
- テキストボックスはキーボードからのキー入力を受け付けない(出力の中身は編集できないようにする)
- ただし,Ctrl+Cは有効として,テキストボックス内の出力をマウスで選択してコピー可能とする.
環境
Windows 10 Pro, Python 3.7, miniconda(conda 4.11.0)
コード
import os, subprocess
import tkinter as tk
from tkinter import ttk, messagebox
import threading
command = "ffmpeg hogehoge" # ffmpegの処理コマンド
def start():
progress = tk.Toplevel()
progress.geometry("750x350")
progress.title("processing...")
progress.grab_set()
progress.focus_set()
bar = ttk.Progressbar(progress,mode='indeterminate')
bar.pack(side = tk.TOP, fill = tk.X)
info = tk.Text(progress)
info.bind("<Key>", lambda e: ctrlEvent(e))
scrollbar = tk.Scrollbar(progress, orient=tk.VERTICAL, command=info.yview)
scrollbar.pack(side = tk.RIGHT, fill = tk.Y)
info["yscrollcommand"] = scrollbar.set
info.pack(side = tk.TOP, anchor = tk.W, fill = tk.BOTH)
bar.start()
def ctrlEvent(event):
if(event.state & 2**2 == 4 and event.keysym=='c' ):
return
else:
return "break"
def process():
p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,shell=True,universal_newlines=True)
for line in p.stdout:
info.insert(tk.END, line)
info.see("end")
try:
outs, errs = p.communicate()
except subprocess.TimeoutExpired:
pass
else:
p.terminate()
bar.stop()
th1 = threading.Thread(target=process)
th1.start()
root = tk.Tk()
button = tk.Button(root, text = "start", command = start)
button.pack()
root.mainloop()
ポイント
テキストボックスは一番下の最新行が表示されるよう自動スクロール
-
.see()
を使う.
info.see("end")
テキストボックスの右側にスクロールバー
- スクロールバーウィジェットを
.pack()
してからテキストウィジェットを.pack()
しないとスクロールバーが正しい位置に表示されない.
テキストボックスはキーボードからのキー入力を受け付けない
- テキストウィジェットを読み取り専用にする方法(
textbox.configure(state='disabled')
)では,ffmpegからの出力も受け付けてくれない. - そこで,任意のキーをbreak関数にバインドして読み取り専用にする.
参考: DelftStack - Tkinter Text ウィジェットを読み取り専用にする方法
ただし,Ctrl+Cは有効
- ただし,上記ページに書かれているように,この方法(
textbox.bind("<Key>", lambda a: "break")
)だとCtrl+Cを受け付けてくれないので,例外として Ctrl+Cをバインドする. -
上記ページに記載されている条件
if(12==event.state and event.keysym=='c' ):
だと,上手くいかなかったので,if(event.state & 2**2 == 4 and event.keysym=='c' ):
に変更した.
参考: Python GUIプログラミング tkinter Canvas state Mod3
def ctrlEvent(event):
if(event.state & 2**2 == 4 and event.keysym=='c' ):
return
else:
return "break"
info.bind("<Key>", lambda e: ctrlEvent(e))
最後までお読みいただきありがとうございました.