Open3
TkEasyGUI
TabGroup と Column の組み合わせでレイアウトの破壊を解消
走り書きです
TkEasyGUI v0.2.73
方法としては、 Column に expand_y=True引数をつけることで今回のものは解消できた。
expand_x=True 引数をつけてもいい
window サイズも指定しなければ以下の画像の様になる

window = eg.Window("", layout=layout, resizable=True)
resizable 引数をつけ画損のサイズを変更できる
ウィンドウのサイズを取得する方法
window.get_size()
タブが表示されず潰れている状態

import TkEasyGUI as eg
# レイアウトの作成
layout1 = [
[eg.Label('ローマ字または()が含まれているか'), eg.Text('', key='-ROMAN_CHECK')],
[eg.Multiline(default_text='', size=(80, 20), key='-TEXT', enable_events=True, expand_y=True)],
[eg.Text('読み込みパス:'), eg.InputText('', key='-TEXT_PATH', disabled=True, expand_x=True)]
]
voicevox_layout = [
[eg.Button('プリセット名コピー', key='-PRESETS_NAME_COPY-'), eg.Button('テンプレートの試聴', color='blue', key='-TMP_LISTENING-')],
[eg.Frame('話速', [[eg.Slider((0.5, 2.0), default=1.0, resolution=0.01, key='-SPEED_SCALE')]]), eg.Frame('音高', [[eg.Slider((-0.15, 0.15), default=0.0, resolution=0.01, key='-PITCH_SCALE')]]), eg.Frame('抑揚', [[eg.Slider((0.0, 2.0), default=1.0, resolution=0.01, key='-INTONATION_SCALE')]]), eg.Frame('音量', [[eg.Slider((0.0, 2.0), default=1.0, resolution=0.01, key='-VOLUME_SCALE')]])],
[eg.Frame('開始無音', [[eg.Slider((0.0, 2.0), default=0.1, resolution=0.01, key='-PRE_PHONEME_LENGTH')]]), eg.Frame('終了無音', [[eg.Slider((0.0, 2.0), default=0.1, resolution=0.01, key='-POST_PHONEME_LENGTH')]])],
[eg.InputText('こんにちは、これはテスト音声です。', key='-LISTENING_TEXT'), eg.Button('試聴', color='blue', key='-LISTENING-')],
]
voicevox_layout_test = [
[eg.Button('プリセット名コピー', key='-PRESETS_NAME_COPY-'), eg.Button('テンプレートの試聴', color='blue', key='-TMP_LISTENING-')],
[eg.Frame('話速', [[eg.Slider((0.5, 2.0), default=1.0, resolution=0.01, key='-SPEED_SCALE')]]), eg.Frame('音高', [[eg.Slider((-0.15, 0.15), default=0.0, resolution=0.01, key='-PITCH_SCALE')]]), eg.Frame('抑揚', [[eg.Slider((0.0, 2.0), default=1.0, resolution=0.01, key='-INTONATION_SCALE')]]), eg.Frame('音量', [[eg.Slider((0.0, 2.0), default=1.0, resolution=0.01, key='-VOLUME_SCALE')]])],
[eg.Frame('開始無音', [[eg.Slider((0.0, 2.0), default=0.1, resolution=0.01, key='-PRE_PHONEME_LENGTH')]]), eg.Frame('終了無音', [[eg.Slider((0.0, 2.0), default=0.1, resolution=0.01, key='-POST_PHONEME_LENGTH')]])],
[eg.InputText('こんにちは、これはテスト音声です。', key='-LISTENING_TEXT'), eg.Button('試聴', color='blue', key='-LISTENING-')],
]
layout2 = [
[eg.TabGroup([
[eg.Tab('test', voicevox_layout, expand_y=True, expand_x=True), eg.Tab('test', voicevox_layout_test, expand_y=True, expand_x=True)]
], size=(500, 500))],
[eg.HSeparator()],
[eg.HSeparator()],
[eg.Text('フォルダのリネーム:'), eg.InputText(key='-OUTPUT_RENAME_DIR', expand_x=True)],
[eg.Button('実行の有効化', key='-DISABLED_FALSE-'), eg.Button('リネームの有効化', key='-RENAME_FALSE-'), eg.Button('読み込みパスの使用', key='-LOAD_PATH-')],
[eg.Button('実行', key='-EXECUTION-', expand_x=True), eg.Button('exoの変更', key='-EXO_EXECUTION-', expand_x=True)],
]
col1 = eg.Column(layout1, key="col1", expand_y=True)
col2 = eg.Column(layout2, key="col2")
layout = [
[col1, eg.VSeparator(), col2]
]
window = eg.Window("", layout=layout, resizable=True, size=(1071, 732))
while True:
event, values = window.read()
if event == eg.WINDOW_CLOSED:
break
# ウィンドウを閉じる
window.close()
タブがうまく常時されている状態

col1 = eg.Column(layout1, key="col1", expand_y=True)
col2 = eg.Column(layout2, key="col2", expand_y=True)
プログレスバー
経過時間とバーの進行状況を分けたコード
処理を開始ボタンを押すと新しくプログレスバーのウィンドウを表示します。
表示したウィンドウにメッセージ、バー、経過時間があります。

import time
import random
import threading
import TkEasyGUI as eg
# キーを定数化(参照しやすくするため)
PROGRESS_BAR_KEY = "-PROGRESSBAR-"
LABEL_KEY = "-LABEL-"
THREAD_DONE_KEY = "-THREAD_DONE-"
def timer_thread(window, is_done):
"""処理中の経過時間を表示するスレッド"""
start_time = time.time()
while not is_done["value"]:
elapsed = time.time() - start_time
window[LABEL_KEY].update(f"{elapsed:.1f}秒経過")
time.sleep(0.1)
def long_process(window, is_done):
"""不確定な処理を模倣"""
try:
# プログレスバー表示を開始
window.un_hide()
window[PROGRESS_BAR_KEY].start()
for i in range(random.randint(10, 20)):
print(f"Processing {i+1}")
print(window[PROGRESS_BAR_KEY].get())
time.sleep(0.3)
finally:
# 処理終了時:プログレスバーを閉じてフラグを更新
window.hide()
is_done["value"] = True
return "処理が完了しました!"
def main():
# プログレスバー用のウィンドウを生成(最初は非表示)
progress_window = eg.Window(
"処理中...",
layout=[
[eg.Text("処理中です。しばらくお待ちください...")],
[eg.Progressbar(key=PROGRESS_BAR_KEY, value_range=(0, 5), mode="indeterminate", expand_x=True)],
[eg.Label("0.0秒経過", key=LABEL_KEY)],
],
no_titlebar=True, # タイトルバーを非表示
grab_anywhere=True, # ウィンドウをドラッグ可能
keep_on_top=True # 常に最前面表示
)
progress_window.hide()
# フラグはdictに入れることで参照渡しが可能になる
is_done = {"value": False}
window = eg.Window("メインウィンドウ", layout=[
[eg.Text("ボタンを押すと処理が始まります")],
[eg.Button("処理を開始", key="-START-", expand_x=True)],
[eg.Button("終了", expand_x=True)],
])
while window.is_alive():
event, values = window.read()
if event in ("終了", eg.WINDOW_CLOSED):
break
if event == "-START-":
is_done["value"] = False # フラグ初期化
# 経過時間スレッドを開始
threading.Thread(target=timer_thread, args=(progress_window, is_done), daemon=True).start()
# lambdaは引数付き関数を遅延実行するために使用
# 疑似処理を別スレッドで開始(処理完了時にTHREAD_DONE_KEYイベント発行)
window.start_thread(lambda: long_process(progress_window, is_done), end_key=THREAD_DONE_KEY)
# 長時間処理が終了したとき
if event == THREAD_DONE_KEY:
eg.popup("処理が完了しました!", "完了")
window.close()
if __name__ == "__main__":
main()
汎用:プログレスバー
ベースコードをGeminiでリファクタリング
プログレスバーを汎用的に使えるようにしたコード
import TkEasyGUI as eg
import time
import threading
# --- 設定 ---
PROGRESS_BAR_KEY = "-progressbar-"
LABEL_KEY = "-label-"
THREAD_DONE_KEY = "-THREAD_DONE-"
# --- ウィンドウ定義 ---
# メインウィンドウ
main_layout = [
[eg.Button("処理を開始 (5秒)", key="-START_5-", expand_x=True)],
[eg.Button("処理を開始 (2秒)", key="-START_2-", expand_x=True)],
]
window = eg.Window('処理テンプレート', main_layout)
# 進捗バーウィンドウ (グローバルに作成し、非表示にしておく)
progress_window = eg.Window(
"処理中...",
layout=[
[eg.Text("処理中です。しばらくお待ちください...")],
[eg.Progressbar(
key=PROGRESS_BAR_KEY,
value_range=(0, 5),
mode="indeterminate",
expand_x=True,
)],
[eg.Label("0.0秒経過", key=LABEL_KEY)],
],
no_titlebar=True,
grab_anywhere=True,
keep_on_top=True
)
progress_window.hide()
# --- ユーティリティ関数 ---
def timer_thread(window, is_done):
"""処理中の経過時間を表示するスレッド"""
start_time = time.time()
# is_doneがTrueになるまでループ
while not is_done["value"]:
elapsed = time.time() - start_time
# ウィンドウが存在し、まだ更新可能かを確認
if window.is_alive():
try:
window[LABEL_KEY].update(f"{elapsed:.1f}秒経過")
except Exception: # ウィンドウが閉じられた場合などに備えて
break
time.sleep(0.1)
def execute_with_progress(target_func, *args, **kwargs):
"""
任意の関数をプログレスバー付きで別スレッドで実行する高階関数。
Args:
target_func (function): 実行したい関数。
*args, **kwargs: target_funcに渡す引数。
"""
# 処理完了フラグ (スレッド間で共有するため辞書を使用)
is_done = {"value": False}
# 経過時間表示スレッドを開始
threading.Thread(
target=timer_thread,
args=(progress_window, is_done),
daemon=True
).start()
# 進捗バーウィンドウを表示
progress_window.un_hide()
progress_bar = progress_window[PROGRESS_BAR_KEY]
progress_bar.start()
try:
# ターゲット関数を実行
target_func(*args, **kwargs)
except Exception as e:
print(f"処理中にエラーが発生しました: {e}")
finally:
# 処理完了
is_done["value"] = True # 経過時間スレッドを停止
progress_bar.stop()
progress_window.hide()
# --- 長時間処理のダミー関数 ---
def my_long_running_task(duration):
"""指定された時間待機するダミーの長時間処理"""
print(f"処理開始: {duration}秒待機します...")
time.sleep(duration)
print("処理完了。")
# --- メインループ ---
while window.is_alive():
event, values = window.read()
if event in eg.WINDOW_CLOSED:
break
# スレッド完了通知 (execute_with_progress内の処理が完了したとき)
if event == THREAD_DONE_KEY:
eg.popup("処理が完了しました!", "完了")
if event == "-START_5-":
# 5秒の処理を別スレッドで実行し、終了後にTHREAD_DONE_KEYを発生させる
window.start_thread(
lambda: execute_with_progress(my_long_running_task, 5),
end_key=THREAD_DONE_KEY
)
if event == "-START_2-":
# 2秒の処理を別スレッドで実行し、終了後にTHREAD_DONE_KEYを発生させる
window.start_thread(
lambda: execute_with_progress(my_long_running_task, 2),
end_key=THREAD_DONE_KEY
)