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

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

新規登録して質問してみよう
ただいま回答率
85.48%
Matplotlib

MatplotlibはPythonのおよび、NumPy用のグラフ描画ライブラリです。多くの場合、IPythonと連携して使われます。

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Tkinter

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

Q&A

解決済

1回答

1058閲覧

tkinterの画面上に表示したグラフを更新するとテキストボックスのスクロールがかくつく

intnto

総合スコア1

Matplotlib

MatplotlibはPythonのおよび、NumPy用のグラフ描画ライブラリです。多くの場合、IPythonと連携して使われます。

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Tkinter

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

1グッド

0クリップ

投稿2022/08/08 16:15

編集2022/08/09 11:22

前提

●開発環境
・Python 3.10.2
┗(ライブラリ)matplotlib Version: 3.3.4
┗(ライブラリ)tkinter Version: 8.6.6
・OS:Windows10 64bit

tkinterで作成した同一画面上に
・自動スクロールするテキストボックス(TkinterのTextウィジェットを継承して作成した独自のウィジェット)を10個
・matplotlibで作成したグラフを10枚
を表示するアプリを作成しています。

実現したいこと

tkinterとmatplotlibを用いて
・各テキストボックスにそれぞれ異なる1文字以上の文字列を表示する
・テキストボックスは常に一定速度で自動スクロールする
・各グラフは一定時間毎に同じタイミングで画面更新する
という動作をかくつくことなく実現させたいです。
可能でしょうか?
可能であれば該当のソースコードをどのように修正すればよいか教えてください。

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

グラフが更新するタイミングでテキストボックスのスクロール動作がかくつきます

該当のソースコード

python

1import tkinter as tk 2from tkinter import Text 3import matplotlib.pyplot as plt 4from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg 5 6class Sample(tk.Tk): 7 def __init__(self, aGraphNum, aTextBarNum): 8 """_summary_ 9 10 Args: 11 aGraphNum (int): 表示するグラフの数(1以上) 12 aTextBarNum (int): 表示するスクロールバーの数(1以上) 13 """ 14 super().__init__() 15 16 # グラフ 17 self.__mGraph = Graph(self,aGraphNum) 18 19 # テキストバー 20 self.__mTxtB = TextBar(self,aTextBarNum) 21 22 # Threadによる画面更新 23 self.__mGraphUpdIntvl = 5000 24 self.__mGraphUpdCnt = 0 25 26 # tk.Tk.after()による画面更新 27 self.__mTextUpdIntvl = 50 28 29 self.after(self.__mTextUpdIntvl,self.__AfterFnc) 30 31 def __AfterFnc(self): 32 """_summary_ 33 after()による画面更新 34 """ 35 self.__mTxtB.updScreen() 36 self.__mGraphUpdCnt = self.__mGraphUpdCnt + self.__mTextUpdIntvl 37 if self.__mGraphUpdCnt % self.__mGraphUpdIntvl == 0: 38 self.__mGraphUpdCnt = 0 39 self.__mGraph.updScreen() 40 self.after(self.__mTextUpdIntvl,self.__AfterFnc) 41 42class Graph(tk.Frame): 43 """_summary_ 44 グラフ領域管理 45 """ 46 def __init__(self, aMaster, aGraphNum): 47 super().__init__(master=aMaster) 48 49 self.__mFig = plt.figure() 50 self.__mAxes = [] 51 for tNum in range(aGraphNum): 52 # グラフ生成 53 self.__mAxes.append(self.__mFig.add_subplot(1,aGraphNum,tNum+1)) 54 # グラフをtkinterに表示 55 self.__mCanvas = FigureCanvasTkAgg(self.__mFig, self) 56 self.__mCanvas.get_tk_widget().pack(fill=tk.BOTH, expand=True) 57 self.pack() 58 59 def updScreen(self): 60 """_summary_ 61 グラフを更新する 62 """ 63 self.__mCanvas.draw_idle() 64 65class TextBar(tk.Frame): 66 """_summary_ 67 スクロール表示領域管理 68 """ 69 def __init__(self, aMaster, aTextBarNum): 70 super().__init__(master=aMaster) 71 72 # 表示領域作成 73 self.__mCanvas = tk.Canvas(self.master) 74 self.__mText = [] 75 for tNum in range(aTextBarNum): 76 # スクロールバー生成 77 self.__mText.append(Custom_Text(0,(tNum)*20,self.__mCanvas)) 78 self.__mText[tNum].insert("1.0","abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz") 79 80 self.__mCanvas.pack() 81 82 def updScreen(self): 83 """_summary_ 84 スクロールする 85 """ 86 for tTxt in self.__mText: 87 tViewSE = tTxt.xview() 88 tIntv = 0.005 89 tPos:float = tViewSE[0] + tIntv 90 if tViewSE[1] >= 1: 91 # 最後尾なら先頭に戻る 92 tPos = 0.0 93 elif tViewSE[1] + tIntv >= 1.0: 94 # 1以上なら最後尾に移動する 95 tPos = 1.0 96 tTxt.xview_moveto(tPos) 97 98class Custom_Text(Text): 99 """_summary_ 100 テキストボックス 101 """ 102 def __init__(self, aX, aY, aMaster=None): 103 Text.__init__(self, aMaster,width=10,height=1,wrap=tk.NONE) 104 self.place(x=aX, y=aY) 105 106if __name__ == "__main__": 107 tSmpl = Sample(10,10) 108 tSmpl.mainloop()

試したこと

・グラフ(Graph)またはテキストボックス(TextBar)をSampleクラス内に配置したThreadとtk.Tk.after()により更新していたが、tk.Tk.after()でのみ更新するよう変更しました。
⇒グラフ更新のタイミングでカクつき、変更前から変化なし

teamikl👍を押しています

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

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

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

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

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

slemntqe

2022/08/09 01:54

動作するのが重くなる、動きがスムーズではない、うごきがかくかくするという意味であれば「角つく」という表記は利用しません。「かくつく」「カクつく」といった表記をします。「角つく」と表記されると角張ったような表示がされている印象を受けます。
guest

回答1

0

ベストアンサー

大前提として、GUI の描画更新は、メインスレッド側で行わなければなりません。
(※ 正確には、イベントループが稼働しているスレッド。大抵の場合はメインスレッド)

サブスレッドから tkinter の update() を呼び出すのは、安全な操作ではありません。
現状のコードで問題が顕在するかどうかは別ですが、
再現度の低い解決が困難な問題になる傾向があるので、
マルチスレッドでそのような設計は避けたほうが無難です。

カクつきの原因は、
tkinter 等のGUIライブラリは、イベントループと呼ばれるメインループを持ち
その中でユーザ入力、イベント処理、描画の更新等を 順番に 行っています。
途中の過程で僅かでも遅延がある場合、描画処理に遅延が発生し、
GUIがカクつくように見えることになります。

今回のケースで考えられるのは

  • サブスレッドからのメソッド呼び出しによるロック ⇛ メインスレッドからタイマーで描画更新することで解消
  • イベントハンドラ内で時間のかかる処理がある

 ⇛ 主に、定期的に呼ばれる処理での、繰り返し文が時間のかかる処理になってないかに注意
時間のかかる処理がある場合は、分割して処理を行う。
関数自体はすぐ終わるようにして、イベントループへ処理を戻す。

※ 質問のコードでは updateというメソッドをオーバーライドしてますが、
tkinter の Widget は update メソッドを持ち、イベントループの処理を行います。

python

1for _ in range(10000): 2 ... 3# ループが終わるまでGUIは更新されない⇛カクつきの原因に 4 5for _ in range(10000): 6 ... 7 root.update() # 定期的にイベントを処理する事で、カクつきが軽減される場合もあります

時間のかかるループ内で (tkinter の) update() を呼ぶのは、飽くまで応急処置的な手段なので、
まずは問題の根本的な原因から対処しましょう。


スレッドの利用が適切でないので、GUI関連の処理は
GUIの提供するタイマー (tkitner では after) で処理するようにしてみてください。
(サブスレッドからは tkinter 関連のオブジェクトには直接触れないような設計にする)

サブスレッドを必要とするケース、例えば I/O 関連の遅延が発生するかもしれない処理では
サブスレッド側で処理を行い、GUIへの反映は、Queue 等を用いて通達し、
必ずメインスレッド側でGUI関連の操作を行うようにします。

投稿2022/08/09 04:41

編集2022/08/09 04:47
teamikl

総合スコア8664

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

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

intnto

2022/08/09 11:47

ご回答ありがとうございます。 頂いた内容を基に該当コードを修正し動作させてみましたが、グラフ更新のタイミングでテキストボックスのスクロール動作はカクついたままでした。 該当コードは更新済みです、お時間ありましたら再度助言など頂ければと思います。 > 大前提として、GUI の描画更新は、メインスレッド側で行わなければなりません。 > (※ 正確には、イベントループが稼働しているスレッド。大抵の場合はメインスレッド) アドバイスありがとうございます。 > ※ 質問のコードでは updateというメソッドをオーバーライドしてますが、 > tkinter の Widget は update メソッドを持ち、イベントループの処理を行います。 意図したオーバーライドではありません。 コーディングミスです、申し訳ございません。 表示内容を更新するためのメソッドとして用意しただけのため、名前をupdScreen()に修正しました。 > スレッドの利用が適切でないので、GUI関連の処理は > GUIの提供するタイマー (tkitner では after) で処理するようにしてみてください。 > (サブスレッドからは tkinter 関連のオブジェクトには直接触れないような設計にする) スレッドの使用をやめ、画面更新処理をafter()に集約しました。 > サブスレッドを必要とするケース、例えば I/O 関連の遅延が発生するかもしれない処理では > サブスレッド側で処理を行い、GUIへの反映は、Queue 等を用いて通達し、 > 必ずメインスレッド側でGUI関連の操作を行うようにします。 アドバイスありがとうございます。
teamikl

2022/08/09 13:26 編集

Python 3.7.10/win10 anaconda でスムーズに実行できてますが、 カクつきが発生するには他に条件があったりしますか?(IDEでの実行等、) テキストスクロールは正常に動いて、グラフは更新されず枠線のみが描画されています。 ==== コードの全貌を追えてませんが、他の気になる点は pyplot tkinter 等の別GUIに組み込む場合は、pyplot は使わずに matplotlib.figure.Figure を直接扱います https://matplotlib.org/stable/gallery/user_interfaces/embedding_in_tk_sgskip.html pyplot はバックエンドに応じたGUIライブラリの初期化や、独自のイベントループを持ちます。 (内部ではバックエンドのイベントループが稼働) pyplot 内部で扱われる tkinter と、自分のコードで扱う tkinter のイベントループの扱いで 競合が起こる可能性があります。対策 ⇛ どちらか片方のイベントループを使うように構成する。 コード的には、カクつきの原因となる遅延が発生し得るのは、 タイマーで呼ばれる関数内の for ループ2箇所が該当します。もしこの for 文に時間がかかっている場合 for 文が全部終わり切る描画が更新されないので、for文内で self.update() を呼び出すことで 強制的に待機中のイベントを処理するというような応急的な対策は取れます。 (イベントループが入れ子になって他の問題を併発する可能性もある為、一時凌ぎ的な対策です)
intnto

2022/08/11 02:57

> Python 3.7.10/win10 anaconda でスムーズに実行できてますが、 > カクつきが発生するには他に条件があったりしますか?(IDEでの実行等、) > テキストスクロールは正常に動いて、グラフは更新されず枠線のみが描画されています。 IDE(vscode+python)のデバッグ実行で動作を確認していました。 デバッグ実行ではなく、コマンドプロンプトから実行してみたところスムーズに実行することができました。 お付き合いいただきありがとうございました、本件解決しました。 > コードの全貌を追えてませんが、他の気になる点は pyplot > tkinter 等の別GUIに組み込む場合は、pyplot は使わずに matplotlib.figure.Figure を直接扱います アドバイスありがとうございます。 (ちなみに、上記変更は本件でのかくつきには影響ありませんでした。) > タイマーで呼ばれる関数内の for ループ2箇所が該当します。もしこの for 文に時間がかかっている場合 > for 文が全部終わり切る描画が更新されないので、for文内で self.update() を呼び出すことで > 強制的に待機中のイベントを処理するというような応急的な対策は取れます。 アドバイスありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問