🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
Tkinter

Tkinterは、GUIツールキットである“Tk”をPythonから利用できるようにした標準ライブラリである。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

1回答

5247閲覧

Pythonのプログラムが実行される順番で困っています

退会済みユーザー

退会済みユーザー

総合スコア0

Tkinter

Tkinterは、GUIツールキットである“Tk”をPythonから利用できるようにした標準ライブラリである。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

0グッド

0クリップ

投稿2021/03/28 07:28

編集2021/03/28 10:33

前提・実現したいこと

PythonのTkinterで、以下のような機能を実現したいです。

  1. ボタンを押す
  2. ラベルを書き換える(歌詞を表示する)
  3. 音楽を再生する

発生している問題・エラーメッセージ

ボタンを押した後、音楽が鳴り終わった後にラベルが変更されてしまうので、ラベルを変更してから音楽が流れるようにしたい。

現状目標
1ボタンを押すボタンを押す
2音楽の再生が始まるラベルを書き換える
3音楽の再生が終わる音楽の再生を始める
ラベルが変更される音楽の再生を終える

該当のソースコード

ボタンを押すとplay_gameが呼び出されます。play_gameの中では、ラベルを変更する関数change_labelをthreadを用いて呼び出し、さらに音楽を再生する関数play_musicを呼び出しています。しかし、実際には音楽の再生→ラベルの変更の順で呼び出されてしまいます。

play_gameの中のlyric[1]には歌詞(ラベルを書き換える文字)が、lyric[2] lyric[3]には音楽の再生したい部分の開始位置と終了位置(ミリ秒)が、lyric[5]にはファイル名が入っています。

Python

1import sqlite3 2from pydub import AudioSegment 3from pydub.playback import play 4import tkinter as tk 5import threading 6 7 8def play_game(): 9 cur = conn.cursor() 10 cur.execute('SELECT * FROM lyrics ORDER BY RANDOM() LIMIT 1;') 11 lyric = cur.fetchone() 12 thread = threading.Thread(target=change_label, args=([lyric[1]])) 13 thread.start() # change_labelをthreadで呼び出し 14 play_music(lyric[2], lyric[3], lyric[5]) # play_musicを呼び出し 15 16 17def change_label(sub): # ラベルを変更する関数 18 sub_tk.set(sub) 19 20 21def play_music(start, end, title): # 音楽を再生する関数 22 sound = AudioSegment.from_mp3('./asset/mp3s/' + title + '.mp3') 23 sound = sound[start:end] 24 play(sound) 25 26 27dbname = 'data.db' 28conn = sqlite3.connect(dbname) 29 30root = tk.Tk() 31root.title('Label&Music') 32root.geometry("300x300") 33 34sub_tk = tk.StringVar() # ここにラベルを書き換える文字をセットする 35sub_tk.set('ここに歌詞が出力されます') 36 37subLabel = tk.Label(root, textvariable=sub_tk) # 書き換えるラベル 38subLabel.pack() 39 40playButton = tk.Button(root, text='再生') 41playButton['command'] = play_game # ボタンを押すとplay_game()を呼び出し 42playButton.pack() 43 44root.mainloop() 45 46conn.close() 47

試したこと

threadを試したのですがうまくいきませんでした。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

問題点:
関数の実行が終わったタイミングでGUIの更新が入る為、
イベントハンドラとして呼び出される関数内に、時間の掛かる処理があると、
描画の更新や他のイベントは遅延してしまいます。

暫定的な回避策としては、音楽再生前に
root.update_idletasks() を呼び出して、明示的に表示を更新する事が出来ます。

但し、メインスレッド(GUIのイベントループ内)で音楽再生をすると、
音楽再生中は他の処理が滞る事になるので、解決策としては
別スレッドで音楽再生をし、メインスレッドでGUIの更新する方法を検討してください。

別スレッドからのGUIの同時更新は、本来は安全な操作ではありません。

Tkinter の場合は、別スレッドから操作しても問題なく動くことが多いのですが、
稀なケースで問題になる場合がある為、あまりお勧めは出来ません。

python

1 2import time 3from threading import Thread 4import tkinter as tk 5from tkinter import ttk 6import logging 7 8# マルチスレッドの場合は、print ではなく logging が便利 9logger = logging.getLogger(__name__) 10 11# ログでどのスレッドで実行されているかを確認する設定 12logging.basicConfig( 13 level=logging.DEBUG, 14 format="[%(threadName)-10s] %(message)s") 15 16 17# このサンプルコードで記述の簡略化の為にグローバルに配置してますが、 18# 実際のコードでは適切な場所に配置してください。 19root = tk.Tk() 20var = tk.StringVar() 21 22def play_music(): 23 # XXX: 処理をブロックするコード 24 logger.info("play music start") 25 time.sleep(3) # 音楽を再生の替わりに 3秒スリープ 26 logger.info("play music end") 27 28 # 注意点: 別スレッドでこの関数を実行していて 29 # ここで、GUI の更新をしたい場合は、通知を行い (hint: queue) 30 # イベントループが動いているメインスレッド側から実行するようにします。 31 # 複数のスレッドから同時にGUIを操作するのは、スレッドセーフではありません。 32 33def play1(): 34 """音楽再生が終わってからラベルが変更、の実演""" 35 logger.info("play1") 36 var.set("label1") 37 play_music() # XXX: ラベルの反映は play1 関数が終わってから 38 39def play2(): 40 """音楽再生前にラベルを変更、の実演""" 41 logger.info("play2") 42 var.set("label2") 43 root.update_idletasks() # <--- ここでラベルの描画更新 44 play_music() # XXX: 但し、この間GUIは操作を受け付けない 45 46def play3(): 47 logger.info("play3") 48 var.set("label3") 49 # 時間の掛かる処理は別スレッドで実行する 50 Thread(target=play_music, daemon=True).start() 51 # スレッドの終了を待たずに、play3関数を抜けます 52 # → イベントループは停滞せずに、直ぐにラベルの描画更新が行われる 53 54ttk.Label(root, textvar=var).pack() 55ttk.Button(root, text="1", command=play1).pack() 56ttk.Button(root, text="2", command=play2).pack() 57ttk.Button(root, text="3", command=play3).pack() 58 59# イベントループが動き続けてることを確認する為に、 60# GUI のプログレスバーのアニメーションを表示します。 61# イベントループが止まる場合は、プログレスバーのアニメーションも止まる。 62bar = ttk.Progressbar(root, mode="indeterminate") 63bar.pack() 64bar.start() 65 66root.mainloop()

投稿2021/03/28 12:20

teamikl

総合スコア8715

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2021/03/28 13:14

丁寧かつ複数の解決策をありがとうございます。 root.update_idletasks()を書くことで解決できました。 また、音楽を再生する側の方を別スレッドにすることでも無事解決できました。 私は初心者で、スレッドに関してはググった時に見つけた解決策の1つをコピーしただけで、よく分かっていませんでした。スレッドの使い方からloggingのことまで教えていただき、本当に助かりました。ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問