Open3

TkEasyGUI

しろしろ

TabGroupColumn の組み合わせでレイアウトの破壊を解消

走り書きです

TkEasyGUI v0.2.73

方法としては、 Columnexpand_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
        )