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

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

ただいまの
回答率

89.06%

tkinter キー入力の待受の方法

解決済

回答 3

投稿

  • 評価
  • クリップ 1
  • VIEW 88

tetuhiroshi

score 24

python3とtkinterでテトリスを作っています。

キー入力を判定するために、次のようなプログラムを書きました。

目的は、スペースキーを押すことによる、カウントダウンの開始です。

#グローバル空間

key = ""

def down_key(event):
  global key
  key = event.keysym

def up_key(event):
  global key
  key = ""

def game_main():
  #~~~~
 #一部省略
  #~~~~

  # カウントダウンの待受処理
  # Spaceキーで、カウントダウン開始
  while not key == "Space":
    pass

  count_down() # (他で定義済み)

win = tk.Tk()
canvas = tk.Canvas()

win.bind("<KeyPress>", down_key)
win.bind("<KeyRelease>", up_key)

game_main()
win.mainloop()

このコードを実行したところ、whileループに差し掛かっていることは確認できたのですが、
GUIに対して何度スペースキーを入力しても、ループを抜けることができませんでした。

GUIとwhileの相性が悪いのかもしれないと思ったのですが、どうでしょうか。
また、解決策など教えていただけると幸いです。

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

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 3

checkベストアンサー

+2

このコードだと、game_main() 内でspaceキーを待ち受ける無限ループになってしまい、
win.mainloop() まで到達しないのでそもそもウィンドウすら表示されません。
(ちなみに、   while not key == "Space": の Spaceは spaceの間違いのようです)

tkinterはイベントモデルのようなので、while Trueで無限ループを回すようなコードを書くと
すべての処理がそこでとまってしまいます。

で、スペースキーのハンドリングなのですが、以下のように書くとそれだけで動いてしまいます。
win.bind("<space>",count_down)

ゲーム開始前とゲーム中とでspaceキーの意味が異なると思うので、そのあたりでまだまだ考慮が必要そうですが、手元の環境ではこれで動きました。

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2020/08/01 17:10

    言われてみると、そのとおりでした。
    ありがとうございます。
    そこそこ癖のある(guiはたいていそうだけれど)インターフェースなので、
    混乱してしまいました。
    tkitnerプログラミングは、やっぱり数をこなさないとだめですかね...

    キャンセル

+1

tkinterのキー入力のハンドリングを含むGUIの処理は、メインループwin.mainloop()の中で行なわれます。
なので、提示のコードのようにメインループの外に作ったループの中ではキーの入力は捕捉されないので、`downkey()'も呼ばれません。

この場合はたとえば、downkey()の中で、Spaceが押されたらカウントダウンするなどの処理にするなどすることになるかと。

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2020/08/01 17:17

    回答していただいて、ありがとうございました。
    tkinter、やっぱもう少しちゃんと勉強しないといけませんね...
    精進します!

    キャンセル

0

回答ではなく、紹介のみですが

GUIとwhileの相性が悪いのかもしれないと思ったのですが、どうでしょうか。

GUIに限らず「イベント駆動型プログラミング」との相性が良くないです。

ライブラリ/フレームワーク自身もイベント処理をするループを持つので、
2つのループを同時に実行する方法を考えなければなりません。

whileが必要な場面では、タイマーやスレッドを使った解決策が一般的ですが、
他に、ジェネレータやコルーチンで実装するというアプローチもあります。

イメージ説明

メインスレッド上で、GUIのmainloopを阻害することなく動作します。
コードはメイン処理のみ抜粋。

def main():
    # 省略

    def game_main():
        drawText("Press space key to start")

        # スペースキー入力を待機
        yield from wait_key_event("<space>", canvas)
        clear()

        # カウントダウン
        for num in [3, 2, 1]:
            item = drawText(str(num), fontsize=100)
            yield 1000 # 1秒(1000ms) sleep
            clear(item)
        else:
            drawText("start!")

    after_timed_gen(root, game_main())
    root.mainloop()

質問のコードに適応すると、こんな感じになります。

import tkinter as tk

def after_timed_gen(root, gen, _stop=None):
    # タイマーでジェネレータを一つづつ読み出し
    # yield で返された値を遅延時間に指定する(sleep)
    def next_gen():
        interval = next(gen, _stop)
        if interval is not _stop:
            root.after(interval, next_gen)
    root.after_idle(next_gen)

def count_down(canvas):
    for num in [3, 2, 1]:
        item = canvas.create_text(200, 200, text=str(num), font=("", 50))
        yield 1000 # 1秒sleep
        canvas.delete(item)

def game_main(canvas):
    canvas.create_text(200, 200, text="press space key")

    flag = [True] # 空になったらスペースキーが押されたと判断

    canvas.bind_all("<space>", lambda _: flag.clear())
    while flag:
        yield 500 # 0.5秒sleep
    canvas.unbind_all("<space>")

    canvas.delete("all")
    yield from count_down(canvas) # (他で定義済み)

    canvas.create_text(200, 200, text="done", font=("", 30))


win = tk.Tk()
win.geometry("400x400")
canvas = tk.Canvas()
canvas.pack(fill=tk.BOTH, expand=True)

after_timed_gen(win, game_main(canvas))
win.mainloop() # ↑ タイマーで実行するので、mainloop内から呼び出される

※ ジェネレータ版の実装は、タイマーの精度(多分10ms程度が最小) の問題が有るので、
あまりリアルタイム性の高いゲーム向けではありません。

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

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

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

関連した質問

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