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

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

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

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

Python

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

Q&A

解決済

1回答

1751閲覧

2つのGIFアニメーションの切替えを TkInter上で達成したい。

saya24

総合スコア221

Tkinter

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

Python

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

0グッド

0クリップ

投稿2020/06/18 03:11

編集2020/06/18 03:43

以下Pythonのコードは 同じ環境にimagesフォルダがありこの中に二つのGIFファイル(taiko-bs.gifとpart21.gif)があることを前提とした作りになっています。(それさえあれば 開発環境がございましたら 動作を確認頂けます)

こちらのサイトでご支援を頂きましたこともあり、一つのGIFアニメーションについて、動作画像を表示できる状態となったのですが

なんとか 確認ダイアログの応答結果を受けて GIFアニメーションの切り替えを達成したいと考えています。 

確認ダイアログ部分、自分なりにチャレンジしましたが、ダイアログに応答したあと GIFアニメーションが表示されない事象を招いています。

どういった対策を施せば 目的が達成されるでしょうか?
何から何まですみません、差し支えなければ ご教示をお願い致します。

Python

1from tkinter import * 2import tkinter.ttk as ttk 3import tkinter.scrolledtext as tksc 4import tkinter.messagebox as tkmb 5import math 6 7class Apprication(ttk.Frame): 8 9 10 11 def __init__(self, app): 12 13 14 super().__init__(app) 15 self.pack(fill=BOTH, expand=True) 16 17 18 photo = PhotoImage(file="images\part21.gif") 19 20 21 btn1 = ttk.Button(self, text="Sub") 22 btn1.pack(fill=BOTH, expand=True) 23 btn1.focus_set() 24 25 btn2 = ttk.Button(self, text="Quit", command=app.quit) 26 btn2.bind('<Return>', lambda _: app.quit()) 27 btn2.pack(fill=BOTH, expand=True) 28 29 30 lb1 =Label(self, bg="#FFFFFF", image=photo, relief="sunken", borderwidth=3, takefocus=1) 31 lb1.pack(fill = BOTH, expand=True) 32 lb1.focus_set() 33 34 35 if tkmb.askyesno("確認","スケジュールを動作させますか?"): 36 photo = PhotoImage(file="images\taiko-bs.gif") 37 38 self.menu() 39 40 41 42 def menu(self): 43 menu_top = Menu(app) 44 menu_file = Menu(menu_top, tearoff=False) 45 menu_open = Menu(menu_top, tearoff=False) 46 47 app.configure(menu=menu_top, bg="#F0FFFF") 48 49 menu_top.add_cascade (label='File(F)', menu=menu_file, underline=0) 50 51 menu_file.add_cascade(label='Open(O)', underline=0, menu=menu_open) 52 menu_open.add_command(label='Sub(S)', underline=0) 53 menu_file.add_command(label='Quit(Q)',underline=0, command=app.quit) 54 55 56 57 # 子画面閉じる 58 def closeDialog(self): 59 self.dialog.destroy() 60 61 62 63if __name__ == '__main__': 64 65 66 #*********************************** 67 def next_frame(): 68 global gif_index 69 try: 70 # XXX: 次のフレームに移る 71 photo.configure(format="gif -index {}".format(gif_index)) 72 73 gif_index += 1 74 except TclError: 75 gif_index = 0 76 return next_frame() 77 else: 78 app.after(100, next_frame) # XXX: アニメーション速度が固定 79 #*********************************** 80 81 82 83 #世間でいうrootをappとしています 84 app = Tk() 85 86 87 #*********************************** 88 photo = PhotoImage(file="images\part21.gif") 89 gif_index = 0 90 #*********************************** 91 92 93 94 95 #実行端末の画面サイズを取得 96 ww = app.winfo_screenwidth() 97 wh = app.winfo_screenheight() 98 99 app.update_idletasks() 100 101 #フォームサイズを実行端末から導き、ド真中に配置表示 102 lw = math.ceil(ww * 0.208) 103 lh = math.ceil(wh * 0.277) 104 app.geometry(str(lw)+"x"+str(lh)+"+"+str(int(ww/2-lw/2))+"+"+str(int(wh/2-lh/2)) ) 105 106 #タイトルを指定 107 app.title("Main Menu") 108 109 #フォームの最大化、×ボタン操作を無効化 110 app.resizable(0,0) 111 app.protocol('WM_DELETE_WINDOW', (lambda: 'pass')()) 112 113 114 115 menu_top = Menu(app) 116 menu_file = Menu(menu_top, tearoff=False) 117 menu_open = Menu(menu_top, tearoff=False) 118 119 app.configure(menu=menu_top, bg="#F0FFFF") 120 121 menu_top.add_cascade (label='File(F)', menu=menu_file, underline=0) 122 123 menu_file.add_cascade(label='Open(O)', underline=0, menu=menu_open) 124 menu_open.add_command(label='Sub(S)', underline=0) 125 menu_file.add_command(label='Quit(Q)',underline=0, command=app.quit) 126 127 128 # フレームを作成する 129 frame = Apprication(app) 130 131 132 #*********************************** 133 app.after_idle(next_frame) 134 #*********************************** 135 136 137 # 格納したTkインスタンスのmainloopで画面を起こす 138 app.mainloop() 139

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

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

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

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

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

guest

回答1

0

ベストアンサー

簡単な対策

  • photo 変数を global宣言 する

※ グローバル変数にする場合、変数名は他と被らない長いものに変えたほうが良い。

  • lb1 の画像を変更する。lb1.configure(image=photo)

で出来ると思いますが、グローバル変数を増やすのは好ましくないのと、
一つのファイルしかアニメーション出来ないという制限にもなる為、
実際のアプリケーションに組み込むには、もう少し改善が必要そうです。

前回は、コードが長くなり、焦点が解り難くなるかと思い
アニメーション自体の動作サンプルとして、その辺りは省いてました。


改善案: def next_frame(): で参照している外部変数や定数をまとめて
再利用可能な形のクラスにします。

関数 next_frame() で使われている変数・定数のリストアップし、

  • "photo" ... PhotoImage
  • "gif_index" ... GIFのフレーム番号
  • 100 ... アニメーション速度の定数。gifファイルにより変更する必要がある。
  • "root.after" ここは、ウィジェットなら何でも良い

これらをインスタンス変数にしたクラスを作ります。
追記: 再生を止められるように停止フラグも追加。(running, cancel)

nametypedescription
self.photoPhotoImageGIF画像
self.indexintGIFのフレーム番号
self.intervalintアニメーション速度
self.afterfunctionタイマー
self.runningbool停止フラグ
self.cancelidタイマーキャンセル用

注意点は、photoオブジェクト
これは、ローカル変数にすると関数の実行が終わった後には破棄されてしまい、画像が表示されないので、
グローバル変数もしくはインスタンス変数にする必要があります。

しかし、グローバル変数にする場合、(一般的に)コードの再利用が難しくなる為、
インスタンス変数とする為に、再利用可能なクラス化という運びになります。
(他にクロージャという手段もありますが、まずはクラス化から慣れていくと良いです)

公開メソッドは

namedescription
start()再生開始
stop()停止
setImage(filepath)画像の変更
setInterval(num)再生速度の変更 (ms)

python

1 2from tkinter import * 3 4class GifLabel(Label): 5 def __init__(self, master, gif_file=None, interval=100, auto_start=True, *argv, **kw): 6 super().__init__(master, *argv, **kw) 7 self.index = 0 8 self.photo = None 9 self.interval = 0 10 self.running = False 11 self.cancel = None 12 self.setImage(gif_file) 13 self.setInterval(interval) 14 if auto_start: 15 self.after_idle(self.start) 16 17 def setImage(self, filepath): 18 if filepath: 19 self.photo = PhotoImage(file=filepath) 20 self.configure(image=self.photo) 21 22 def setInterval(self, interval): 23 if interval > 0: 24 self.interval = interval 25 26 def stop(self): 27 self.running = False 28 if self.cancel: 29 self.after_cancel(self.cancel) 30 self.cancel = None 31 32 def start(self): 33 if not self.running: 34 self.running = True 35 self._tick() 36 37 def _tick(self): 38 self.cancel = self.after(self.interval, self._updateIndex) 39 40 def _updateIndex(self): 41 if not self.photo: 42 return self.stop() 43 try: 44 self.photo.configure(format="gif -index {}".format(self.index)) 45 self.index += 1 46 except TclError: 47 self.index = 0 48 self._updateIndex() 49 else: 50 self._tick() 51 52 53if __name__ == "__main__": 54 import tkinter.messagebox as tkmb 55 56 root = Tk() 57 label = GifLabel(root) 58 label.pack(fill=BOTH, expand=True) 59 60 if tkmb.askyesno("確認","スケジュールを動作させますか?"): 61 label.setImage("images\A.gif") 62 else: 63 label.setImage("images\B.gif") 64 root.after_idle(label.start) 65 66 root.mainloop()

前回の回答に、クラス化してgithub に載せてたサンプルコードのリンクがあるので、
それも参考にしてみて下さい。(動画再生のアプローチは同じです、コードの詳細は少し異なりますが)

投稿2020/06/18 05:02

teamikl

総合スコア8664

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

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

saya24

2020/06/18 08:52

クラスを作成頂きまして 誠にありがとうございます。 丸々をコピペして 思ったとおりの動作が達成されていることを確認させて頂きました。ばっちりです。 現在 自分の力不足で 適用に手間どっておりますが、間違いなく 価値の有効なクラスです、本当になんとお礼を申し上げてよいやら。 重ねてお礼を申し上げます。
saya24

2020/06/18 15:32

お気付きかも知れませんが、私が毎回示している例のとおり、現在取り組んでいるモジュールのつくりは、ルート直にウィジェットを貼っているわけではなく、アプリケーションクラスで生成されるフレーム上に問題のラベルも貼っています。 いかんせんこの形式でかなり開発が進んでしまっていて、かつ自分の応用力がなくteamiklさんのご提案をいまだ当てはめられないでいます。 ご提示頂きましたコードの.after_idleは やはりrootにしか使えないメソッドなのでしょうか? フレームには適用できないものでしょうか、私が示したアプリケーションクラス内で使うことはできますでしょうか??? 差し支えなければご教示をお願いします。
teamikl

2020/06/18 16:30

after等のユーティリティ関数は、大抵のクラス(StringVar や PhotoImage *以外*)で使えます。 bindみたいな立ち位置のメソッドです。 tkinter 内部に、Wm と Misc というクラスが有り、 全てのウィジェットは Misc を継承してます。bind や after といったメソッドはこちらに属してて Tk と Toplevel は、MiscとWm を継承してて、Wmは title や geometryといったメソッドを持ちます。 root(とダイアログ等のToplevel)にしか使えないのは後者。after は前者なので、 Frameを継承したクラス内なら、self.after_idle とすることが出来ます。 上記の例では特に after_idle を使う影響はないので、 直接 start() を呼び出しても大丈夫でした。コードの呼び出し先を辿っていくと start() -> _tick() -> after()呼び出しとなる為、gifのアニメーションが始まるのは、 after() で登録した関数が処理される mainloop() が始まってからなのには代わりありません。
saya24

2020/06/18 23:31

ありがとうございます。何から何まですみません。せっかく作っていただいたクラスが無駄にならないようがんばります。
saya24

2020/06/19 00:29

アドバイスを頂いて本当によかったです、意図も簡単にできました。この御恩一生忘れません!
saya24

2020/07/04 13:51

お心あたりがございましたら教えてください。 アプリ立上げ時のフォームに、作って頂きましたクラスから生成されたラベル(GifLabel)を貼っています。 そのフォームAは開きっぱなしにして 別フォームBをダイアログとして開いて そちらである程度の操作を経まして フォームBを閉じる → 引き続き もとの立上げ時から開いているフォームAで 操作を行いて setImage(resource_path(xxxxxxxx))のメソッドを実行すると    AttributeError:'Label' object has no attribute 'setImage' になることがあります。 Aフォームは 開かれ続けており GIFも表示され続けているのに.... 必ずしもそうなるわけでなく、事象を掴みキれていないのですが、 アプリにまだデータが入っていない段階で生じる傾向があり、いいかえれば 何かの処理をが経ている・経ていないで違いが生じているに違いありません。ただ今のところ それが分かりません。 該当フォーム・GIFのラベルはが表示され続けているのに、属性がなくなってしまうケースは どういう時なのでしょう? 現況 hasattr関数で、setImage属性が存在しているときのみ setImage(resource_path(xxxxxxxx))を利用させて頂いており もったいない感じがして.... お分かりになるようであれば ご教示ください
teamikl

2020/07/04 18:35

呼び出し部分のコードがないと解りませんが、 setImage がないのは単純に該当のウィジェットがGifLabelになってない場合 通常の Label ウィジェットの setImage を呼び出そうとしてます。 print(type(label)) # 確認方法: setImage 前にlabelの型を表示してみる label.setImage(resource_path(xxxxxxxx)) そこから、"label" に該当する部分が 何処で初期化されるかを辿ってみてください。 予想される原因は、エラーが出る対象のラベルでは GifLabel クラスを使っていない等です。
saya24

2020/07/04 22:52

貴重なご見解をありがとうございます。早速確認致します。
saya24

2020/07/04 23:27

おぉ〜っといつの間に!!! tkinter.Labelクラスで表示されました。 立上げ時にsetImageを利用の際は最初は __main__.GifLabelクラスで表示されたのですが、いつの間に!!! 表示され続けていても、という意識に誤りがあるのかもしれません。 どこかでLabelとして表示されなおされていないか確認しますね。 非常に良いアドバイスで助かりました。
saya24

2020/07/05 00:14

ありがとうございました。 ご見解が功を奏して、問題を特定できました。 いくつかのフォームから構成されているアプリであると申し上げておりましたが、フォームちがいで全く同じWidget名にしてしまっている事が問題でした。 本当に助かりました。 重ねてお礼を申し上げます。
teamikl

2020/07/05 08:08 編集

変数名の衝突だったみたいですね。変数のスコープがグローバルだったのかな・・ ---- グローバル変数について、この回答の冒頭(簡単な解決策)で少し触れましたが、補足 > photo 変数を global宣言 する > ※ グローバル変数にする場合、変数名は他と被らない長いものに変えたほうが良い 「global宣言」は、大きなプログラムでは極力避けたほうが良いです。 global変数自体は有用な場合もあるので一概に全てダメとは言えませんが、 変数名が衝突するリスク(今回のような問題)に繋がります。 少なくとも 関数内で「global宣言」が必要になった場合、 クラスを使ってるなら、その変数はインスタンス変数に出来るはずです。 この点、サンプルコードやQ&Aの問答の中での短いコードでは、衝突が起こり難く グローバル変数にした方が手っ取り早いことも有り、疎かになりがちなので、 参考にしたコードを実際に適応する際は、変数のスコープは再考した方が良いでしょう。 ---- デバッグ方法について 因みに、この様なクラスの異なる変数名の衝突は、 pylint や mypy 等を導入して型検査を徹底すると、実行前に検出できます。 コード側で型宣言等が必要で、簡単にツールだけ導入とはいかないので、今後の参考に。名前紹介のみ。 Lint 等といった定番の静的解析ツールが幾つかあります。大抵のIDEで対応してるはずです PyLint - VS2017 https://docs.microsoft.com/ja-jp/visualstudio/python/linting-python-code?view=vs-2017 MyPy - VS2017 https://docs.microsoft.com/ja-jp/visualstudio/python/editing-python-code-in-visual-studio?view=vs-2017
saya24

2020/07/05 22:54

色々とありがとうございます。 インスタンス変数を利用していました。self.lb1というようにwidgetのインスタンスそのものをフォーム違いの全く別のwidgetで利用していました。 初歩的なミスですみません。 こちらのIDEがVS2017と記憶頂き、型検出のツールを当方目線でご紹介頂きまして、どれだけ親切な方なんでしょう。頭があがりません。 早速利用させて頂きます、ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

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

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

質問する

関連した質問