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

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

ただいまの
回答率

88.83%

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

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 262

saya24

score 141

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

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

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

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

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

from tkinter import *
import tkinter.ttk as ttk
import tkinter.scrolledtext as tksc
import tkinter.messagebox as tkmb
import math

class Apprication(ttk.Frame):



    def __init__(self, app):


        super().__init__(app)
        self.pack(fill=BOTH, expand=True)


        photo = PhotoImage(file="images\\part21.gif")


        btn1 = ttk.Button(self, text="Sub")
        btn1.pack(fill=BOTH, expand=True)
        btn1.focus_set()

        btn2 = ttk.Button(self, text="Quit", command=app.quit)
        btn2.bind('<Return>', lambda _: app.quit())
        btn2.pack(fill=BOTH, expand=True)


        lb1 =Label(self, bg="#FFFFFF", image=photo, relief="sunken", borderwidth=3, takefocus=1)
        lb1.pack(fill = BOTH, expand=True)
        lb1.focus_set()


        if tkmb.askyesno("確認","スケジュールを動作させますか?"):
            photo = PhotoImage(file="images\\taiko-bs.gif")

        self.menu()



    def menu(self):
        menu_top = Menu(app)
        menu_file = Menu(menu_top, tearoff=False)
        menu_open = Menu(menu_top, tearoff=False)

        app.configure(menu=menu_top, bg="#F0FFFF")

        menu_top.add_cascade (label='File(F)', menu=menu_file, underline=0)

        menu_file.add_cascade(label='Open(O)', underline=0, menu=menu_open)
        menu_open.add_command(label='Sub(S)', underline=0)
        menu_file.add_command(label='Quit(Q)',underline=0, command=app.quit)



    # 子画面閉じる
    def closeDialog(self):
        self.dialog.destroy()



if __name__ == '__main__':


    #***********************************
    def next_frame():
        global gif_index
        try:
            # XXX: 次のフレームに移る
            photo.configure(format="gif -index {}".format(gif_index))

            gif_index += 1
        except TclError:
            gif_index = 0
            return next_frame()
        else:
            app.after(100, next_frame) # XXX: アニメーション速度が固定
    #***********************************



    #世間でいうrootをappとしています
    app  = Tk()


    #***********************************
    photo = PhotoImage(file="images\\part21.gif")
    gif_index = 0
    #***********************************




    #実行端末の画面サイズを取得
    ww = app.winfo_screenwidth()
    wh = app.winfo_screenheight()

    app.update_idletasks()

    #フォームサイズを実行端末から導き、ド真中に配置表示
    lw = math.ceil(ww * 0.208)
    lh = math.ceil(wh * 0.277)
    app.geometry(str(lw)+"x"+str(lh)+"+"+str(int(ww/2-lw/2))+"+"+str(int(wh/2-lh/2)) )

    #タイトルを指定
    app.title("Main Menu")

    #フォームの最大化、×ボタン操作を無効化
    app.resizable(0,0)
    app.protocol('WM_DELETE_WINDOW', (lambda: 'pass')())



    menu_top = Menu(app)
    menu_file = Menu(menu_top, tearoff=False)
    menu_open = Menu(menu_top, tearoff=False)

    app.configure(menu=menu_top, bg="#F0FFFF")

    menu_top.add_cascade (label='File(F)', menu=menu_file, underline=0)

    menu_file.add_cascade(label='Open(O)', underline=0, menu=menu_open)
    menu_open.add_command(label='Sub(S)', underline=0) 
    menu_file.add_command(label='Quit(Q)',underline=0, command=app.quit)


    # フレームを作成する
    frame =  Apprication(app)


    #***********************************
    app.after_idle(next_frame)
    #***********************************


    # 格納したTkインスタンスのmainloopで画面を起こす
    app.mainloop()
  • 気になる質問をクリップする

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

0

簡単な対策

  • photo 変数を global宣言 する 
    ※ グローバル変数にする場合、変数名は他と被らない長いものに変えたほうが良い。
  • lb1 の画像を変更する。lb1.configure(image=photo)

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

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


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

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

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

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

 name   type   description 
 self.photo   PhotoImage   GIF画像 
 self.index   int   GIFのフレーム番号 
 self.interval   int   アニメーション速度 
 self.after   function   タイマー 
 self.running   bool   停止フラグ 
 self.cancel   id   タイマーキャンセル用 

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

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

公開メソッドは

 name   description 
 start()   再生開始 
 stop()   停止 
 setImage(filepath)   画像の変更 
 setInterval(num)   再生速度の変更 (ms) 
from tkinter import *

class GifLabel(Label):
    def __init__(self, master, gif_file=None, interval=100, auto_start=True, *argv, **kw):
        super().__init__(master, *argv, **kw)
        self.index = 0
        self.photo = None
        self.interval = 0
        self.running = False
        self.cancel = None
        self.setImage(gif_file)
        self.setInterval(interval)
        if auto_start:
            self.after_idle(self.start)

    def setImage(self, filepath):
        if filepath:
            self.photo = PhotoImage(file=filepath)
            self.configure(image=self.photo)

    def setInterval(self, interval):
        if interval > 0:
            self.interval = interval

    def stop(self):
        self.running = False
        if self.cancel:
            self.after_cancel(self.cancel)
            self.cancel = None

    def start(self):
        if not self.running:
            self.running = True
            self._tick()

    def _tick(self):
        self.cancel = self.after(self.interval, self._updateIndex)

    def _updateIndex(self):
        if not self.photo:
            return self.stop()
        try:
            self.photo.configure(format="gif -index {}".format(self.index))
            self.index += 1
        except TclError:
            self.index = 0
            self._updateIndex()
        else:
            self._tick()


if __name__ == "__main__":
    import tkinter.messagebox as tkmb

    root = Tk()
    label = GifLabel(root)
    label.pack(fill=BOTH, expand=True)

    if tkmb.askyesno("確認","スケジュールを動作させますか?"):
        label.setImage("images\\A.gif")
    else:
        label.setImage("images\\B.gif")
    root.after_idle(label.start)

    root.mainloop()

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/07/05 09:14

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

    本当に助かりました。
    重ねてお礼を申し上げます。

    キャンセル

  • 2020/07/05 17:01 編集

    変数名の衝突だったみたいですね。変数のスコープがグローバルだったのかな・・

    ----
    グローバル変数について、この回答の冒頭(簡単な解決策)で少し触れましたが、補足

    > 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

    キャンセル

  • 2020/07/06 07:54

    色々とありがとうございます。
    インスタンス変数を利用していました。self.lb1というようにwidgetのインスタンスそのものをフォーム違いの全く別のwidgetで利用していました。
    初歩的なミスですみません。

    こちらのIDEがVS2017と記憶頂き、型検出のツールを当方目線でご紹介頂きまして、どれだけ親切な方なんでしょう。頭があがりません。

    早速利用させて頂きます、ありがとうございました。

    キャンセル

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

  • ただいまの回答率 88.83%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る