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

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

ただいまの
回答率

88.59%

tkinterを使ったクイズゲームで、画像をランダムに表示させたい

解決済

回答 1

投稿

  • 評価
  • クリップ 1
  • VIEW 1,246

teburi

score 9

前提・実現したいこと

クイズゲームを作成しています。
取り込んだcsvの要素をランダムに選び、同じ要素を答えさせるゲームはいったん完成したため
新たにランダムに植物の画像を表示し、その植物の名前を当てるゲームを考えています。
※csvは以下のようなイメージです。
[{'name': 'ススキ', 'family': 'イネ科', '画像': 'ine.jpg'}, 
{'name': 'コムギ', 'family': 'イネ科', '画像': 'komugi.jpg'},…

ラベルに画像を表示させる(まだ固定ファイル名)ことはなんとかできたのですが、
1.その画像をリサイズする処理がうまくいきません。
リサイズ処理のみ動かした場合、うまくいったプログラムを移植したのですが・・・
2.画像をランダムに選択するため、画像読み込みの部分を関数init_view(tki)内に入れたいのですが
テスト的に固定ファイル名を指定した状態でも、まともに表示できません。

特に2に関してはラベルの理解が甘いのだと思うのですが、調べてはみたものの
ラベルの扱いも画像表示の手法も人によってさまざまで、前に進まなくなってきました。

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

1.について
_tkinter.TclError: image specification must contain an odd number of elements

該当のソースコード

import random
import csv
import tkinter, tkinter.messagebox
from tkinter import *
from tkinter import ttk

tki = None
rdo = None
b_options = None
rdo_var = None
answer = None

def init_view(tki):
    global rdo, b_options, rdo_var, answer

    # 上記DictReaderで読み込んだ辞書リストから、問題用に、ひとつの辞書を呼び出す
    p_plant = random.choice(rows)
    print(p_plant)
    p_name = p_plant["name"]

    # 問題用の植物と同じ科(family)の植物をリストにして、
    # その中から問題用の植物を削除。
    # 残りからひとつを選ぶ(=その植物が答えになる)

    q_list = []
    while len(q_list) < 2:
        q_list = [x['name'] for x in rows if x['family'] == p_plant["family"]]
    q_list.remove(p_plant["name"])
    answer = random.choice(q_list)

    # 答えとは異なる科(family)の植物をリスト化し、そこから3つ選ぶ
    b_list = [x['name'] for x in rows if x['family'] != p_plant["family"]]
    b_options = random.sample(b_list , 3)
    b_options.append(str(answer))
    random.shuffle(b_options)

    # 配置済みの子ウィジェットが存在すれば、削除しておく
    children = tki.winfo_children()
    for child in children:
        # print("type of widget is : " + str(type(child)))
        if str(type(child)) == "<class 'tkinter.Radiobutton'>":
            child.destroy()
        elif str(type(child)) == "<class 'tkinter.Button'>":
            child.destroy()
        elif str(type(child)) == "<class 'tkinter.Label'>":
            child.destroy()

    # ラジオボタンのラベルをリスト化する
    # rdo_txt = random.sample(b_options , k=4) 
    # ラジオボタンの状態
    rdo_var = tkinter.IntVar()

    # ラジオボタンを動的に作成して配置
    for i in range(4):
        rdo = tkinter.Radiobutton(tki, value=i, variable=rdo_var, text=b_options[i]) 
        rdo.place(x=50, y=80 + (i * 24))

    # ボタン作成    
    btn = tkinter.Button(tki, text='答え合わせ', command=btn_click)
    btn.place(x=100, y=270)


#    以前画像を使わない4択問題で使っていたコード
#    var = tkinter.StringVar()
#    var.set(p_name + "と同じ科の植物はどれでしょう")
#    label = tkinter.Label(tki, image=icon,textvariable=var, compound="top",width=100, )
#    label.pack()

# ボタンクリックイベント
def btn_click():
    num = rdo_var.get()

    if b_options[num] == str(answer):
        tkinter.messagebox.showinfo('結果' , "正解!")
    else:
        tkinter.messagebox.showinfo("結果" , '不正解!正解は' + answer)

    # 表示内容の再初期化
    init_view(tki)

with open('plant.csv', newline='') as csvfile:
    reader = csv.DictReader(csvfile)
    rows = []
    for row in reader:
        rows.append(row)

root = Tk()
root.geometry('300x400')
root.title('ラジオボタン')
frame1 = ttk.Frame(root)
frame1.grid()

# ここが困っている部分です
icon = PhotoImage(file="aburana_f.gif")
label1 = ttk.Label(frame1 , image=icon , text="てきすと", compound="top")
label1.grid(row=1,column=1)

root.mainloop()

試したこと

1.について
img = Image.open("daikon_f.jpg")  # イメージを開く
resize_image = img.resize((200, int(200 * img.size[1] / img.size[0])))  # 画像のリサイズ

2.について
icon = PhotoImage(file="aburana_f.gif")
label1 = ttk.Label(frame1 , image=icon , text="てきすと", compound="top")
label1.grid(row=1,column=1)

ボタン作成の下に入れ、多少いじりましたが、ウィンドウは出るものの、画像もボタンも何も表示されません。

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

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

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

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 1

checkベストアンサー

+1

Labelに画像を動的に張り付ける方法ですが、以下のようにすることで可能となります。Labelに張り付けるのはPhotoImageでなければいけない為、Imageで加工した後、PhotoImageのコンストラクタに引数として渡して利用します。動的に画像の変更ができれば、ランダムに指定することも可能でしょう。
※既にインストールされていたのかもしれませんが、Pillow(PIL)が必要です。($ pip install pillow)

from tkinter import ttk
from PIL import Image, ImageTk

img = Image.open("aburana_f.gif")
resize_img = img.resize((200, int(200 * img.size[1] / img.size[0])))
icon = ImageTk.PhotoImage(resize_img)

icon_label = ttk.Label(tki, compound="top")
icon_label.place(x=10, y=240)

# 動的に画像とテキストをセット
icon_label.configure(image=icon, text="アイコンラベル")
icon_label.image = icon

ここで、icon_label.configure(image=icon, text="アイコンラベル")に続けてicon_label.image = iconとするのは冗長で無意味に思えるのですが、こうしないと画像が表示できませんでした。

質問中にご提示のコードは既存のラジオボタンやボタンをplaceメソッドを用いて画面上の位置を決めているので、Labelも同様にplaceメソッドで配置するよう修正追加してみた例が以下です。画像の切り替えが動的にできるかどうか、交互に表示するデモ動作になっています。

import random
import csv
import tkinter, tkinter.messagebox
from tkinter import *
from tkinter import ttk
from PIL import Image, ImageTk

#tki = None
root = None
rdo = None
b_options = None
rdo_var = None
answer = None
icon_label = None
toggle = None

def init_view(tki):
    global rdo, b_options, rdo_var, answer, icon_label, toggle

    # 上記DictReaderで読み込んだ辞書リストから、問題用に、ひとつの辞書を呼び出す
    p_plant = random.choice(rows)
    print(p_plant)
    p_name = p_plant["name"]

    # 問題用の植物と同じ科(family)の植物をリストにして、
    # その中から問題用の植物を削除。
    # 残りからひとつを選ぶ(=その植物が答えになる)

    q_list = []
    while len(q_list) < 2:
        q_list = [x['name'] for x in rows if x['family'] == p_plant["family"]]
    q_list.remove(p_plant["name"])
    answer = random.choice(q_list)

    # 答えとは異なる科(family)の植物をリスト化し、そこから3つ選ぶ
    b_list = [x['name'] for x in rows if x['family'] != p_plant["family"]]
    b_options = random.sample(b_list , 3)
    b_options.append(str(answer))
    random.shuffle(b_options)

    # 配置済みの子ウィジェットが存在すれば、削除しておく
    children = tki.winfo_children()
    for child in children:
        print("type of widget is : " + str(type(child)))
        if str(type(child)) == "<class 'tkinter.Radiobutton'>":
            child.destroy()
        elif str(type(child)) == "<class 'tkinter.Button'>":
            child.destroy()
        elif str(type(child)) == "<class 'tkinter.Label'>":
            child.destroy()

    # ラジオボタンのラベルをリスト化する
    # rdo_txt = random.sample(b_options , k=4) 
    # ラジオボタンの状態
    rdo_var = tkinter.IntVar()

    # ラジオボタンを動的に作成して配置
    for i in range(4):
        rdo = tkinter.Radiobutton(tki, value=i, variable=rdo_var, text=b_options[i]) 
        rdo.place(x=50, y=80 + (i * 24))

    # ボタン作成    
    btn = tkinter.Button(tki, text='答え合わせ', command=btn_click)
    #btn.place(x=100, y=270)
    btn.place(x=100, y=200)

    # img = Image.open("aburana_f.gif")
    # デモ - 確認用に、画像を交互に表示
    toggle = not toggle
    fname = "aburana_f.gif" if toggle else "kikyo_f.gif"
    img = Image.open(fname)
    resize_img = img.resize((200, int(200 * img.size[1] / img.size[0])))
    icon = ImageTk.PhotoImage(resize_img)
    label = tkinter.Label(tki, compound="top")
    label.place(x=10, y=240)

    # 動的に画像とテキストをセット
    label.configure(image=icon, text="アイコンラベル: " + fname)
    label.image = icon

#    以前画像を使わない4択問題で使っていたコード
#    var = tkinter.StringVar()
#    var.set(p_name + "と同じ科の植物はどれでしょう")
#    label = tkinter.Label(tki, image=icon,textvariable=var, compound="top",width=100, )
#    label.pack()

# ボタンクリックイベント
def btn_click():
    num = rdo_var.get()

    if b_options[num] == str(answer):
        tkinter.messagebox.showinfo('結果' , "正解!")
    else:
        tkinter.messagebox.showinfo("結果" , '不正解!正解は' + answer)

    # 表示内容の再初期化
    init_view(root)

with open('plant.csv', newline='') as csvfile:
    reader = csv.DictReader(csvfile)
    rows = []
    for row in reader:
        rows.append(row)

root = Tk()
root.geometry('300x400')
root.title('ラジオボタン')
init_view(root)

root.mainloop()


Python 3.8 32ビット版 + Pillow 7.0.0で、Windows 10(64ビット)上にて確認しました。

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2020/01/29 02:28

    ランダムに画像をLabelに表示させるとき、configure メソッドは必要だと思いますが、toggle変数は単に交互に画像を切り替え表示させるためのフラグ変数として使っているだけなので、あまり気にしないでください。
    toggle = not toggle
    fname = "aburana_f.gif" if toggle else "kikyo_f.gif"

    のコード部分が分かりづらいということでしたら、
    if toggle == True:
    toggle = False
    fname = "aburana_f.gif"
    else:
    toggle = True
    fname = "kikyo_f.gif"

    のような意味です。

    キャンセル

  • 2020/01/29 04:17

    見直してみると、Labelを生成する前段に「子ウィジェットを削除(destroy)する」ので、「ラベル未生成の場合のみ、生成する」のは判定自体が無駄で、少し不適切でした。RadioButtonやButtonの扱いと同じようになるよう、コードを修正させていただきました。

    キャンセル

  • 2020/01/29 23:49

    さらなるコメント、また全体まで修正いただき恐れ入ります。全体像も大枠理解できたと思います。修正いただいたコードをもとに、画像をランダムに表示させる(正確にはランダムに選択したディクショナリから画像を表示する)こともできました。さらにラベルとウィジェットについて勉強し、表示を整えていきたいと思います。まことにありがとうございました。

    キャンセル

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

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

関連した質問

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