【Python】Tkinterによる200行で作るGUIアプリ「英単語ソフト」

# はじめに
こんにちは。
今回はPythonの勉強と英語の勉強を兼ねて、**「英単語ソフト」** を作ってみようと思います。
**GUI** で作成したいので、**tkinter** を使って実装していきます。

![英単語ソフトメイン画面.png](https://qiita-image-store.s3.amazonaws.com/0/309633/17d7c721-9554-b712-a6e3-4d13fd53bc9e.png)
こちらが完成形になります。

仕様的には、カンマ区切りで書かれているテキストデータである **csvファイル** を読み込み、そこにある英単語をランダムで一つ表示し、4択の中からその意味を選んで、テキストボックスに入力し、判定を行うというものです。
正解なら、次の単語ボタンを押すことで、次の英単語がランダムで一つ表示されます。


# 環境
- Windows 10 home
- Python 3.7.1



# 英単語ソフトの制作

## ウィンドウの作成

前回の記事の[**雛形**](https://qiita.com/MichihiroTsukano/items/4d8721aaa59e1c913d9a)を基本に作っていきます。

```py
import tkinter as tk

class Application(tk.Frame):
    def __init__(self,master):
        super().__init__(master)
        self.pack()

        self.master.geometry("380x200")
        self.master.title("英単語ソフト")

def main():
    win = tk.Tk()
    app = Application(master = win)
    app.mainloop()


if __name__ == "__main__":
    main()

```

まずこのプログラムでウィンドウを作っていきます。
ウィンドウサイズは380×200。
タイトルは英単語ソフトにしています。

## csvファイルの読み込み

```py

def WordInput(self):
		f = open("word.csv","r",encoding = "utf-8-sig")

		self.wordlist = list(csv.reader(f))

		f.close()

```

**word.csv**を読み込みます。


## 部品の配置

```py
def widget(self):

		self.txt1 = tk.Entry(self.master, width = 33)
		self.txt1.place(x = 50, y = 30)

		self.txt1.insert(0,self.wordlist[self.num][0])

		self.txt2 = tk.Entry(self.master, width = 33)
		self.txt2.place(x = 50, y = 90)

		self.BtnJudge = tk.Button(self.master, text = "判定", command = self.ClickJudge, width = 10)
		self.BtnJudge.place(x = 110, y = 148)

		self.BtnNext = tk.Button(self.master, text = "次の単語", command = self.Next, width = 10)
		self.BtnNext.place(x = 215, y = 148)

		self.select4()

```

テキストボックス、ボタン、4択を配置していきます。


## 4択の表示

```py
def select4(self):

		rdNum = rd.randint(0,3)
		lbl_x = 280
		lbl_y = 15

		self.Rdlist()

		for i in range(4):

			if i == 0:
				if i == rdNum:
					self.lbl1 = tk.Label(self.master, text = str(i+1) + " . " + self.wordlist[self.num][1])
					self.lbl1.place(x = lbl_x, y = lbl_y + i * 30)
				else:
					self.lbl1 = tk.Label(self.master, text = str(i+1) + " . " + self.wordlist[self.rdlist[i]][1])
					self.lbl1.place(x = lbl_x, y = lbl_y + i * 30)


			if i == 1:
				if i == rdNum:
					self.lbl2 = tk.Label(self.master, text = str(i+1) + " . " + self.wordlist[self.num][1])
					self.lbl2.place(x = lbl_x, y = lbl_y + i * 30)
				else:
					self.lbl2 = tk.Label(self.master, text = str(i+1) + " . " + self.wordlist[self.rdlist[i]][1])
					self.lbl2.place(x = lbl_x, y = lbl_y + i * 30)


			if i == 2:
				if i == rdNum:
					self.lbl3 = tk.Label(self.master, text = str(i+1) + " . " + self.wordlist[self.num][1])
					self.lbl3.place(x = lbl_x, y = lbl_y + i * 30)
				else:
					self.lbl3 = tk.Label(self.master, text = str(i+1) + " . " + self.wordlist[self.rdlist[i]][1])
					self.lbl3.place(x = lbl_x, y = lbl_y + i * 30)


			if i == 3:
				if i == rdNum:
					self.lbl4 = tk.Label(self.master, text = str(i+1) + " . " + self.wordlist[self.num][1])
					self.lbl4.place(x = lbl_x, y = lbl_y + i * 30)
				else:
					self.lbl4 = tk.Label(self.master, text = str(i+1) + " . " + self.wordlist[self.rdlist[i]][1])
					self.lbl4.place(x = lbl_x, y = lbl_y + i * 30)


	def Rdlist(self):
		self.rdlist = list(range(len(self.wordlist)))
		rd.shuffle(self.rdlist)
		self.rdlist.remove(self.wordlist.index(self.wordlist[self.num]))

	def select4_destroy(self):
		self.lbl1.destroy()
		self.lbl2.destroy()
		self.lbl3.destroy()
		self.lbl4.destroy()

```

ここの部分が一番ややこしいところになります。
問題の英単語の正答の意味を4択の中に表示し、それ以外の意味は、読み込んだcsvファイルから正答以外の意味をランダムで3つ表示します。また、正答の意味の位置もランダムで決まります。


## 判定

```py

def Judge(self):
		if self.txt2.get() == self.wordlist[self.num][1]:
			self.marupro()
		else:
			self.batsupro()

	def ClickJudge(self):
		self.Judge()

	def EnterJudge(self, event):
		self.Judge()


	def marupro(self):
		self.canvas.delete("batsu1")
		self.canvas.delete("batsu2")
		self.judgeNum = 1
		#print("正解")
		self.canvas.create_oval(10,10,43,43,outline = "red", width = 5, tag = "maru")

	def batsupro(self):
		#print("不正解")
		self.canvas.create_line(10,10,43,43,fill = "black", width = 5, tag = "batsu1")
		self.canvas.create_line(10,43,43,10,fill = "black", width = 5, tag = "batsu2")
		self.txt2.delete(0,tk.END)

```

判定のプログラムになります。
ここで、正解か不正解を判別し、正解なら、キャンバスに〇を表示し、不正解ならキャンバスに×を表示します。


## 次の問題への処理

```py

def Next(self):
		if self.judgeNum == 1:
			self.canvas.delete("maru")

			self.num = rd.randint(1,len(self.wordlist) - 1)

			self.txt1.delete(0,tk.END)
			self.txt2.delete(0,tk.END)
			self.txt1.insert(0,self.wordlist[self.num][0])

			self.select4_destroy()
			self.select4()

			self.judgeNum = -1

```

正解なら、次の問題が表示されますが、不正解なら、次の問題への処理をできないようにしています。**変数judgeNum**でその部分の制御をしています。

# 完成のプログラム

大まかな部分の説明はしましたが、その他細かな部分のコードを含めた完成のプログラムが以下になります。

```word.py
import tkinter as tk
import random as rd
import csv

class Application(tk.Frame):
	def __init__(self, master):
		super().__init__(master)
		self.pack()

		self.master.geometry("380x200")
		self.master.title("英単語ソフト")

		self.canvas = tk.Canvas(self.master, bg = "white", width = 50, height = 50)
		self.canvas.place(x = 30,y = 135)

		self.WordInput()
		self.SetVar()
		self.widget()


		self.master.bind("<Return>", self.EnterJudge)

	def SetVar(self):
		self.judgeNum = -1
		self.num = rd.randint(0,len(self.wordlist) - 1)


	def WordInput(self):
		f = open("word.csv","r",encoding = "utf-8-sig")

		self.wordlist = list(csv.reader(f))

		f.close()


	#部品の配置↓

	def widget(self):

		self.txt1 = tk.Entry(self.master, width = 33)
		self.txt1.place(x = 50, y = 30)

		self.txt1.insert(0,self.wordlist[self.num][0])

		self.txt2 = tk.Entry(self.master, width = 33)
		self.txt2.place(x = 50, y = 90)

		self.BtnJudge = tk.Button(self.master, text = "判定", command = self.ClickJudge, width = 10)
		self.BtnJudge.place(x = 110, y = 148)

		self.BtnNext = tk.Button(self.master, text = "次の単語", command = self.Next, width = 10)
		self.BtnNext.place(x = 215, y = 148)

		self.select4()#4択


	#部品の配置↑


	#4択の表示↓
	def select4(self):

		rdNum = rd.randint(0,3)
		lbl_x = 280
		lbl_y = 15

		self.Rdlist()

		for i in range(4):

			if i == 0:
				if i == rdNum:
					self.lbl1 = tk.Label(self.master, text = str(i+1) + " . " + self.wordlist[self.num][1])
					self.lbl1.place(x = lbl_x, y = lbl_y + i * 30)
				else:
					self.lbl1 = tk.Label(self.master, text = str(i+1) + " . " + self.wordlist[self.rdlist[i]][1])
					self.lbl1.place(x = lbl_x, y = lbl_y + i * 30)


			if i == 1:
				if i == rdNum:
					self.lbl2 = tk.Label(self.master, text = str(i+1) + " . " + self.wordlist[self.num][1])
					self.lbl2.place(x = lbl_x, y = lbl_y + i * 30)
				else:
					self.lbl2 = tk.Label(self.master, text = str(i+1) + " . " + self.wordlist[self.rdlist[i]][1])
					self.lbl2.place(x = lbl_x, y = lbl_y + i * 30)


			if i == 2:
				if i == rdNum:
					self.lbl3 = tk.Label(self.master, text = str(i+1) + " . " + self.wordlist[self.num][1])
					self.lbl3.place(x = lbl_x, y = lbl_y + i * 30)
				else:
					self.lbl3 = tk.Label(self.master, text = str(i+1) + " . " + self.wordlist[self.rdlist[i]][1])
					self.lbl3.place(x = lbl_x, y = lbl_y + i * 30)


			if i == 3:
				if i == rdNum:
					self.lbl4 = tk.Label(self.master, text = str(i+1) + " . " + self.wordlist[self.num][1])
					self.lbl4.place(x = lbl_x, y = lbl_y + i * 30)
				else:
					self.lbl4 = tk.Label(self.master, text = str(i+1) + " . " + self.wordlist[self.rdlist[i]][1])
					self.lbl4.place(x = lbl_x, y = lbl_y + i * 30)


	def Rdlist(self):
		self.rdlist = list(range(len(self.wordlist)))
		rd.shuffle(self.rdlist)
		self.rdlist.remove(self.wordlist.index(self.wordlist[self.num]))

	def select4_destroy(self):
		self.lbl1.destroy()
		self.lbl2.destroy()
		self.lbl3.destroy()
		self.lbl4.destroy()

	#4択の表示↑


	#判定↓

	def Judge(self):
		if self.txt2.get() == self.wordlist[self.num][1]:
			self.marupro()
		else:
			self.batsupro()

	def ClickJudge(self):
		self.Judge()

	def EnterJudge(self, event):
		self.Judge()


	def marupro(self):
		self.canvas.delete("batsu1")
		self.canvas.delete("batsu2")
		self.judgeNum = 1
		#print("正解")
		self.canvas.create_oval(10,10,43,43,outline = "red", width = 5, tag = "maru")

	def batsupro(self):
		#print("不正解")
		self.canvas.create_line(10,10,43,43,fill = "black", width = 5, tag = "batsu1")
		self.canvas.create_line(10,43,43,10,fill = "black", width = 5, tag = "batsu2")
		self.txt2.delete(0,tk.END)

	#判定↑


	def Next(self):
		if self.judgeNum == 1:
			self.canvas.delete("maru")

			self.num = rd.randint(1,len(self.wordlist) - 1)

			self.txt1.delete(0,tk.END)
			self.txt2.delete(0,tk.END)
			self.txt1.insert(0,self.wordlist[self.num][0])

			self.select4_destroy()
			self.select4()

			self.judgeNum = -1


def main():
	win = tk.Tk()
	app = Application(master = win)
	app.mainloop()


if __name__ == "__main__":

	main()

```

これでプログラムは完成になります。
また、同じフォルダにカンマ区切りのcsvファイル(ファイル名は**word.csv**)を置けば、しっかりと動作すると思います。

```word.csv
例）
battery(名),電池
stage(名),段階
tube(名),管
power(名),電力
ocean(名),海洋
crystal(名),結晶
wire(名),針金
wire(他),配線する
style(名),様式
・・・
```

# 終わりに

csvファイルのデータを覚えたい英単語にしてあげれば、**自分だけの英単語ソフト**を作ることができます！
もし動かなかったりした場合、コメントをください。できるだけ対応します！