質問をすることでしか得られない、回答やアドバイスがある。

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

新規登録して質問してみよう
ただいま回答率
85.31%
並列処理

複数の計算が同時に実行される手法

Tkinter

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

Python

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

Q&A

解決済

1回答

6126閲覧

GUIアプリの処理中にプログレスバーを表示したい。

shunhamm

総合スコア22

並列処理

複数の計算が同時に実行される手法

Tkinter

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

Python

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

1グッド

1クリップ

投稿2021/05/06 03:39

前提・実現したいこと

PythonのTkinterを使ってGUIでファイル等を選択し、処理を行うことが出来るアプリを作っています。この処理中にユーザーに処理中であることを知らせる為、プログレスバーを利用しようとしています。しかし、下記のコードにある通りプログレスバーを扱おうとすると進捗状況が反映出来なかったり、アプリが停止してしまいます。原因や解決策が分かる方に教えていただきたいです。

該当のソースコード

Python

1from tkinter import * 2from tkinter import ttk 3import time 4import tkinter as tk 5 6def button_click(): 7 pbval.set(pbval.get() + 1) 8 root.update() 9 10win = tk.Tk() 11win.title("TEST") 12win.geometry("300x700") 13time.sleep(1) 14win.destroy() 15 16#プログレスバー作成 17root = tk.Tk() 18ttk.Style().theme_use('classic') 19root.title('Progress') 20 21root.columnconfigure(0, weight=1) 22root.rowconfigure(0, weight=1) 23 24# Frame 25frame1 = ttk.Frame(root, padding=10) 26frame1.grid(sticky=(N, W, S, E)) 27frame1.columnconfigure(0, weight=1) 28frame1.rowconfigure(0, weight=1) 29 30pbval = IntVar(value=0) 31pb = ttk.Progressbar( 32 frame1, 33 orient=HORIZONTAL, 34 variable=pbval, 35 maximum=5, 36 length=200, 37 mode='determinate') 38pb.grid(row=0, column=0, sticky=(N, E, S, W)) 39 40 41def ok_click(): 42 def button_click(): 43 pbval.set(pbval.get() + 1) 44 root.update() 45 46 button_click() 47 time.sleep(1) 48 49 button_click() 50 time.sleep(1) 51 52 button_click() 53 time.sleep(1) 54 55 button_click() 56 time.sleep(1) 57 58 button_click() 59 time.sleep(1) 60 61 62 63ok_click() 64 65root.mainloop() 66win.mainloop()

試したこと

・.destroyを使って入力窓を消してから処理中にプログレスバーを表示させる → 入力窓が最初から消えてしまい失敗。

person😄を押しています

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

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

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

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

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

hide5stm

2021/05/06 04:31

回答できなくて申し訳ないですが、 1. 想定している動きがわからないです、300x700のwindowを表示して、そこの上のボタンが押されたらprogressバーが表示されるみたいな感じでしょうか。 2. GUIプログラミングの場合、mainloop()でイベント待ちに入るのが正しいフレームワークの使い方なので、time.sleep()を使ってしまうと、mainloop()に戻れなくなるので、time.sleep()使ってはまずいはずです。 3. あと、mainloop()が2つある状況はまずいはずです。tkとttkのmainloop()を統合するのか、片方しか使わないようにするか。など1つのmainloop()でイベントを待つようにする必要があると思います。
beginerdesu

2021/05/06 05:38

ご質問ありがとうございます。 1.おっしゃる通りです。もう少し詳しく説明すると300*700のwindow上で処理に用いるエクセルファイルを選択しokボタンを押すとok_clickが実行されprogressバーが表示されるようにしたいと考えております。また、実際はok_click中にエクセルファイルを開く挙動や書き込み、保存のような挙動を追加する予定で、その際の進行度合いを確認できるようにprogressバーを作成しようとした次第です。 2.time.sleep()は上記のようにエクセルファイルを開く挙動などを再現するために便宜的に用いたものですので削除いたします。 3.一度統合という線(tk.toplevel)を用いて作成してみます。 言葉足らずで混乱を招き申し訳ございませんでした。その他アドバイスなどございましたらご指摘ください。
guest

回答1

0

ベストアンサー

問題点:
イベントループ内で time.sleep や root.update を使ってはいけません。

厳密には、質問のコードではok_click()は mainloop() 前に呼ばれているので
「イベントループ外」ですが、一般的なケースを想定して。

time.sleep() は、イベント処理を止めてしまいます。

root.update() は、イベントループと同じ処理を行うので、
状況次第ではイベントループが入れ子になってしまい、
イベントが意図した順に処理されないようなことが起こり得ます。

質問のコードでは問題にはなりませんが、
update() 後に何か行う場合は、潜在的な問題になるので、非推奨です。

描画の更新が目的の場合は、root.update_idletasks() を使います。
大抵の場合はGUIの提供するタイマー(tkinterではafter関数)を使った方が良いです。

解決策:

  • バックグラウンドで行いたい処理は、Thread もしくは Process を使う。
  • プログレスバーの更新は、タイマー(after関数) を使う。
  • 進捗の通知には、Queue を用います(タイマーで読み出して IntVar を更新する)
  • 開始時に ok_click を呼び出したい場合は、イベントループから呼ばれるように
    root.mainloop() 前に root.after_idle(ok_click) とします。

追記: 内容的に、コードの一部を修正して動くような問題ではなく、設計から見直しが必要です。
概要だけでの把握は難しいと思うので、関連の有りそうなサンプルコードを載せます。

 queue を使う際の注意点。(メッセージの頻度について)

 queue を使い、サブスレッドからメインスレッドにメッセージを送るサンプル

 queue を使い、メインスレッドからサブスレッドにメッセージを送るサンプル

※ 過去の質問で作成したコードもあるため、余分な情報も含まれてますが、
関係なさそうな部分は読み飛ばしてください。

関連の有りそうな用語

  • GUI については、イベント駆動型、イベントループ
  • マルチスレッド、排他制御、スレッド間通信 (いくつか方法はありますが、同期Queueを使う方法)

他の問題点

  • win.mainloop() の win は、事前に破棄 (win.destroy) されてます。

tk.Tk() や mainloop() を複数回呼び出すのは、
問題を起こしやすいので、プログラム中では一度のみにして、
2つ目のウィンドウが必要な場合は、tk.Toplevel を使ってください。

投稿2021/05/06 04:53

編集2021/05/07 01:32
teamikl

総合スコア8817

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.31%

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

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

質問する

関連した質問