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

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

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

HTML5の<canvas>要素用のタグです。CanvasはHTML5から導入された、二次元の図形描写が可能な要素です。

button

HTMLで用いる<button>タグです。

Python

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

Q&A

解決済

1回答

2246閲覧

Pythonでボタンクリック時に処理がうまく行われない

Austin1020

総合スコア3

canvas

HTML5の<canvas>要素用のタグです。CanvasはHTML5から導入された、二次元の図形描写が可能な要素です。

button

HTMLで用いる<button>タグです。

Python

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

0グッド

1クリップ

投稿2021/07/01 21:13

前提・実現したいこと

パイソンでゲーム(麻雀)を作ろうとしています。
ゲーム選択画面から、プレイ画面への遷移がうまくいかないです。
下記のコードを実行しているのですが、ボタンを一回クリックしても反応がなく、2,3回連続でクリックするとようやく反応します。ボタンをクリックした際にすぐ反応するようにしたいのですが、どのようにすればよろしいでしょうか?
解答よろしくお願いいたします。

該当のソースコード

import tkinter as tk import random import PIL from PIL import Image,ImageTk #風リスト kaze_list = ['東', '南', '西', '北'] #名前リスト name_list = ['しょうき', 'ウレハ', 'ちょげ', 'こうたろう'] #点数リスト point_list = [25000, 25000, 25000, 25000] #局 kyoku_list = ['東1局', '東2局', '東3局', '東4局', '南1局', '南2局', '南3局', '南4局', '西1局', '西2局', '西3局', '西4局'] index = 0 def ttl_play_click_btn(): global index index = 1 def ttl_setting_click_btn(): global index index = 1 def ttl_rule_click_btn(): global index index = 1 #ゲームスタート画面 def game_start(): fnt = "HG行書体" #タイトルを表示 title_label = tk.Label(root, anchor = tk.W, text="~麻雀~", font=(fnt,100)) title_label.place(x=340, y=160) #ボタン play_game_btn = tk.Button(root, text="ゲーム開始", font=(fnt,40), command=ttl_play_click_btn) play_game_btn.place(x=455, y=400) player_setting_btn = tk.Button(root, text="プレイヤー設定", font=(fnt,30), command=ttl_setting_click_btn) player_setting_btn.place(x=455, y=520) check_rule_btn = tk.Button(root, text="ルール確認", font=(fnt,40), command=ttl_rule_click_btn) check_rule_btn.place(x=455, y=620) def game_bg(): #風表示 #東 label = tk.Label(root, anchor=tk.W, width=2, text=kaze_list[0], font=("HG行書体",18)) label.place(x=455, y=435) #南 label = tk.Label(root, anchor=tk.W, width=2, text=kaze_list[1], font=("HG行書体",18)) label.place(x=695, y=435) #西 label = tk.Label(root, anchor=tk.W, width=2, text=kaze_list[2], font=("HG行書体",18)) label.place(x=695, y=275) #北 label = tk.Label(root, anchor=tk.W, width=2, text=kaze_list[3], font=("HG行書体",18)) label.place(x=455, y=275) #宅の画像 taku_img = Image.open('img/taku_center.png') taku_img = taku_img.resize((270,190),Image.ANTIALIAS) taku_img = ImageTk.PhotoImage(taku_img) cv.create_image(590,370, image=taku_img) #ゲームのメイン処理 def game_main(): global index, yama if index == 0: game_start() elif index == 1: game_bg() root.after(100, game_main) #なんかの処理 root = tk.Tk() root.title("麻雀") root.resizable(False,False) cv = tk.Canvas(root, width=1200, height=800,bg="#28A772") cv.pack() game_main() root.mainloop()

試したこと

クリックを数回すると画面が変わるので、コードは間違ってないのかな?と思っています。

ここに問題に対して試したことを記載してください。

補足情報(FW/ツールのバージョンなど)

Python 3.9です。
ここにより詳細な情報を記載してください。

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

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

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

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

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

guest

回答1

0

ベストアンサー

ボタンを一回クリックしても反応がなく、2,3回連続でクリックするとようやく反応します。

ボタンのクリック自体は反応していますが、
プログラムの構造的な問題の為、

python

1# ゲームのメイン処理 2 3def game_main(): 4 if index == 0: 5 game_start() # タイトル画面のウィジェットを生成 6 7 # 100ms (0.1秒) 毎に呼び出す 8 root.after(100, game_main)
  • まず、前提の確認として game_main 関数は、タイマーで 0.1 秒毎に呼び出されます。
  • game_start 関数でタイトル画面のウィジェット類を生成。
  • game_start 関数も、index が変更するまで毎秒10回近く呼び出される。
  • ウィジェット類は、同じ位置に表示されてるので気付きにくいと思いますが、

 毎ループ新しいウィジェットを生成し、蓄積され続けます。
⇛ 時間経過で膨大な量のウィジェットが生成される(問題点1)

同様のことが、クリックされた後にも言えて

  • index 変化後、game_bg が毎秒10回近く呼び出されて、

 画像ファイルの読み込みも同様の頻度で行われています(問題点2)


回避策

問題点1 のウィジェットについては、
ウィジェット生成時に name を付けることで、新しいウィジェットが生成されるのを抑制できます。
訂正: 抑制はできますが、試してみて画面にちらつきが発生しました。

python

1# 注意点: 必ず重複しない名前を付ける。 2 3 title_label = tk.Label(root, name="title_label", 4 anchor = tk.W, text="~麻雀~", font=(fnt,100))

デメリットとしては、クラス等にしたときに再利用が出来なくなります。
あまり使わない方法ですが、このような毎回呼び出される処理の中でウィジェット生成を行う場合の、
コードの変更が少なく対応する回避策です。

プログラムの構造の大幅な変更が可能なら、本来は、
ウィジェット生成処理は一度のみしか呼び出さないような構造にしたほうが良いです。

追記(代案)

python

1# ウィジェットは外部で予め作成しておく。(変更点: 親は root ではなくキャンバス) 2fnt = "HG行書体" 3title_label = tk.Label(cv, anchor = tk.W, text="~麻雀~", font=(fnt,100)) 4play_game_btn = tk.Button(cv, text="ゲーム開始", font=(fnt,40), command=ttl_play_click_btn) 5player_setting_btn = tk.Button(cv, text="プレイヤー設定", font=(fnt,30), command=ttl_setting_click_btn) 6check_rule_btn = tk.Button(cv, text="ルール確認", font=(fnt,40), command=ttl_rule_click_btn) 7 8#ゲームスタート画面 place ではなく create_windowでキャンバスへ配置 9def game_start(): 10 cv.delete("game_start") 11 kw = {"tag": "game_start", "anchor": "nw"} 12 cv.create_window(340, 160, window=title_label, **kw) 13 cv.create_window(455, 400, window=play_game_btn, **kw) 14 cv.create_window(455, 520, window=player_setting_btn, **kw) 15 cv.create_window(455, 620, window=check_rule_btn, **kw) 16

問題点2 について

python

1# プログラムの冒頭で予め読み込んでおく (※ PhotoImage は root = tk.Tk() 以降) 2taku_img = Image.open('img/taku_center.png') 3taku_img = taku_img.resize((270,190),Image.ANTIALIAS) 4taku_img = ImageTk.PhotoImage(taku_img) 5 6# game_bg 内 7 8 cv.delete("TAKU_IMG") 9 cv.create_image(590,370, image=taku_img, tag="TAKU_IMG") 10
  • 画像ファイルの読み込みを、game_bg 関数外に出します。
  • game_bg 関数は、繰り返し呼ばれるので、画像生成時にタグを付けて、削除できるようにする。

これも、本来であれば create_image を一度のみ呼び出すだけでよいのですが、
プログラムの構造が繰り返し呼ばれる関数内の為、
前の画像を削除・新たに画像を配置という回避措置を取ります。

ちなみに、ゲーム関連のライブラリであれば、1フレーム毎の描画なので
毎回ループ内で新たに描画するような構造が成立するのですが、
tkinter 等の GUI ライブラリは、フレーム単位の描画ではなく、
単にタイマー処理で重ねて表示される事になります。
その為、キャンバスへの描画では毎回 delete してから create のような冗長な処理が発生する。

投稿2021/07/02 01:11

編集2021/07/02 01:37
teamikl

総合スコア8760

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

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

Austin1020

2021/07/02 18:52 編集

丁寧な回答ありがとうございます。 ①今回クリックの時に反応が悪かったのは、同じ処理を繰り返しウィジットを繰り返し作成してしまっていたからという認識であっているでしょうか? ②ボタンのウィジェットを作成する際にrootではなくcvを指定したのですがそれによりどう変わるのですか? ③今回のプログラムはafterを使用し何度も繰り返すような構造でつくっているのですが、afterを使い繰り返し処理をせずに同じ動きをすることは可能なのでしょうか? こちらも答えていただけるとありがたいです。よろしくお願いいたします。 当たり前のようなことを質問していたら申し訳ありません。初学者で上記の3点を疑問に思いました、、
teamikl

2021/07/02 22:49 編集

(1) 具体的にどの部分が反応悪かったまでは追ってませんが、 一番時間がかかりそうなのは、画像ファイルを毎回読み込む部分です。 イベント処理 ⇛ タイマーの処理 ⇛ イベント処理 ... のように、 順番に実行されるので game_mainで時間のかかる処理をすると、 その分クリック等のイベントの反応は遅れることになります。 (2) ウィジェットの親子関係・内部での構造が変わります。 イベントが伝搬する順番だったり、 親ウィジェットを破棄したときに、子も一緒に破棄されるようになったり。 今回の用途では影響ない部分もあるかもしれませんが、 例えば、キャンバスを非表示にしたい場合、 キャンバス上に描画している要素がキャンバスの子ウィジェットならば、 キャンバスのみ非表示にすることで対応できます。 別の親を持つ要素を配置を重ねている状態では、 キャンバスを非表示にしても、その他のウィジェットは残ります。 違いを感じないうちは、そういった用途で使うことがないということなので 特に気にする必要はありませんが、順当に構成するならば、 キャンバスに配置する子要素は、キャンバスを親に生成したほうが都合が良い事が多いです。 (※ 他のGUIライブラリと違い、tkinter 特有の制限で、 ウィジェットの再生性なしに後から親子関係を変えることは出来ません。) (3) スレッドを使う方法もありますが、 用途からするとタイマー(after)を使う方法が正着です。 「コールバックの再帰形式以外で」という意図ならば、 forやwhile でコードを記述する方法はあります。 内部でafterは使いますが、コードは書きやすくなるはず。 詳しくは以下URLを参照して下さい。 関連: pythonでの処理を遅らせる方法(after, time) https://teratail.com/questions/327275 tkinter + ループ処理をしたい https://teratail.com/questions/347369
teamikl

2021/07/03 01:04

(3) について追記、繰り返し自体を使わない方法で、 「画面遷移」といった、画面を切り替える方法があります。 (検索のヒント "tkraise") 懸念としては、プログラムの構造が変わるので、 もし書籍等を参考にコードを書かれてるなら、大幅な変更ですし、 状態繊維(画面切り替え部分)のコードが追い難くなる等の、 他への影響もあります。 tkinter の場合、描画ループは mainloop が担当してるので、 入力のみタイマーで処理するのが良さそうかな。 以下のように、初期化と表示中の index を別ける方法で 何度も描画を削除・生成する部分を抑制できます if index == 0:   ... スタート画面のウィジェットを生成。ここは1度しか実行されない   index = 1 elif index == 1:   ... スタート画面表示中。   ... アニメーション処理があれば必要なパラメータ変化のみ。   ... ボタンが押されるまではここが繰り返される。 elif index == 2:   ... ゲーム開始画面。ここは1度しか実行されない   index = 3 elif index == 3: より発展的な方法だと、上のリンクを参考に「ジェネレーター」を応用すると 初期化のような一度しか行わない処理も、より自然に記述できます。
Austin1020

2021/07/04 16:20

解答ありがとうございます。 とても勉強になりました。 いただいた回答を参考にし、自分でもいろいろ調べながらすすめていきます。 本当にありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.35%

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

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

質問する

関連した質問