##教材元
https://www.udemy.com/the-python-mega-course/learn/v4/content
##tkinterとsqlite3の練習に簡易ポケモン図鑑作った。
frontend.pyとbackend.pyの2つのファイルを作って、frontend.pyでbackedn.pyをimportする。
appとexeファイルにも変換した。
ポケモンのjsonファイル元:
https://github.com/kotofurumiya/pokemon_data
##backend.py
sqlite3でdatabaseを作る。jsonファイルがロードされてない場合は、ロードしてポケモン図鑑のデータを登録する
#backend.py
#trati特性の思考
#多分trait枠2つつくり、trait1,trait2のボックスどちらに入力しても、dbのtrait12どちらかに存在すればTrueにすればいい。
import sqlite3
import json
def connect():
conn=sqlite3.connect("pokemon.db")
cur=conn.cursor()#, evolve TEXT, type1 TEXT, type2 TEXT, trait TEXT, hidden TEXT, hp INTEGER, atk INTEGER, def INTEGER, spa INTEGER, spd INTEGER, spe INTEGER)"
sql_statement="CREATE TABLE IF NOT EXISTS pokemon (id INTEGER PRIMARY KEY, no TEXT, name TEXT, evolve TEXT, type1 TEXT, type2 TEXT)"
cur.execute(sql_statement)
conn.commit()
conn.close()
#ファイルを選択させて、jsonの絶対パスを受け取る。
#そのパスを使ってloadできる?
def load_json():
conn=sqlite3.connect("pokemon.db")
cur=conn.cursor()
sql_statement="INSERT INTO pokemon VALUES (Null,?,?,?,?,?)"
with open("pokemon_data.json") as data:
cur.execute("Select * from pokemon")
raws = cur.fetchall()
if len(raws)==0:
for i in json.load(data):
sql_tuple=(str(i["no"]),i["name"],"True" if i["evolutions"] else "False",i["types"][0],i["types"][1])
cur.execute(sql_statement, sql_tuple)
conn.commit()
conn.close()
def view():
conn=sqlite3.connect("pokemon.db")
cur=conn.cursor()
cur.execute("Select * from pokemon")
raws = cur.fetchall()
conn.close()
return raws
def insert(no, name, evolve, type1, type2):
conn=sqlite3.connect("pokemon.db")
cur=conn.cursor()
sql_statement="insert into pokemon values (Null,?,?,?,?,?)"
cur.execute(sql_statement, (no,name,evolve, type1, type2))
conn.commit()
conn.close()
#type1のサーチどうするんだ?
#type1のみならそれだけでいいか
def search(no="", name="", evolve="", type1="", type2=""):
conn=sqlite3.connect("pokemon.db")
cur=conn.cursor()
sql_statement="select * from pokemon where no=? or name=? or evolve=? or (type1=? and type2=?)"
if type1 or type2:
#typeが2つため:2回実行
cur.execute(sql_statement, (no,name,evolve,type1,type2))
rows = cur.fetchall()
cur.execute(sql_statement, (no,name,evolve,type2,type1))
rows += cur.fetchall()
conn.close()
return rows
cur.execute(sql_statement, (no,name,evolve,type1,type2))
rows = cur.fetchall()
conn.close()
return rows
def update(id, no, name, evolve, type1, type2):
conn=sqlite3.connect("pokemon.db")
cur=conn.cursor()
sql_statement="update pokemon set no=?, name=?, evolve=?, type1=?,type2=? where id=?"
cur.execute(sql_statement, (no,name,evolve, type1, type2, id))
conn.commit()
conn.close()
def delete(id):
conn=sqlite3.connect("pokemon.db")
cur=conn.cursor()
sql_statement="delete from pokemon where id=?"
cur.execute(sql_statement, (id,))
conn.commit()
conn.close()
#importされたときに自動で下記も実行される。
connect()
load_json()
- 最初にデータベースにテーブルを作り、カラムにデータを登録する際には指定されたデータ型である必要がある。(ただsqlite3はfrontend側から受け取った値が仮にint型でも、テーブル内部のcolumnがTEXT型だろうと、登録してくれる)
- bool値のデータ型がsqlite3にはないっぽい?
- cur.fetchall()はリストが返ってくる。リストの中の要素には検索されたrowがtupleで入ってる。
- windowが開いたときにデータベースに接続して、windowを閉じるときにデータベースとの接続を切るようにしたほうがコードの量は減るよね。これ。半分くらいに多分なる。
##frontend.py
#frontend.py
from tkinter import *
import backend
#command func
#wrap function。引数を渡せないため、wrapの中で実行させる
#<<ListboxSelect>>を受け取る。eventに入る
def get_selected_row(event):
global selected_tuple
try:
index=list1.curselection()[0]
selected_tuple=list1.get(index)
#Entryは0,ENDなのか。Textは"1.0"か
e1.delete(0,END)
e2.delete(0,END)
e3.delete(0,END)
e4.delete(0,END)
e5.delete(0,END)
e1.insert(END, selected_tuple[1])
e2.insert(END, selected_tuple[2])
e3.insert(END, selected_tuple[3])
e4.insert(END, selected_tuple[4])
e5.insert(END, selected_tuple[5])
except IndexError:
pass
def view_command():
list1.delete(0, END)
for row in view():
list1.insert(END, row)#(場所, value)
#rowがendに追加される
def search_command():
list1.delete(0,END)
#これ空白だとNone渡されちゃうのか?するとちょっと不都合だな…
for row in search(no.get(),name.get(),evolve.get(),type1.get(),type2.get()):
list1.insert(END, row)
def add_command():
insert(no.get(),name.get(),evolve.get(),type1.get(),type2.get())
list1.delete(0, END)
list1.insert(END, (no.get(),name.get(),evolve.get(),type1.get(),type2.get()))
#リストボックス内部の選択したもののidを取得する
def delete_command():
delete(selected_tuple[0])
view_command()
def update_command():
update(selected_tuple[0],no.get(),name.get(),evolve.get(),type1.get(),type2.get())
search_command()
def reset_command():
e1.delete(0,END)
e2.delete(0,END)
e3.delete(0,END)
e4.delete(0,END)
e5.delete(0,END)
#window
window = Tk()
#title
window.wm_title("POKeMON Guide 861s")
#l = Label(window, text="")
l1 = Label(window, text="No")
l2 = Label(window, text="Name")
l3 = Label(window, text="Evolve")
l4 = Label(window, text="Type1")
l5 = Label(window, text="Type2")
#grid(row=0,column=0)
l1.grid(row=0,column=0)
l2.grid(row=1,column=0)
l3.grid(row=2,column=0)
l4.grid(row=3,column=0)
l5.grid(row=4,column=0)
#Entry
no=StringVar()
name=StringVar()
evolve=StringVar()
type1=StringVar()
type2=StringVar()
#Entry(window, textvariable=, width=10)
e1=Entry(window, textvariable=no, width=10)
e2=Entry(window, textvariable=name, width=10)
e3=Entry(window, textvariable=evolve, width=10)
e4=Entry(window, textvariable=type1, width=10)
e5=Entry(window, textvariable=type2, width=10)
#gird
e1.grid(row=0,column=1)
e2.grid(row=1,column=1)
e3.grid(row=2,column=1)
e4.grid(row=3,column=1)
e5.grid(row=4,column=1)
#listbox of output
list1 = Listbox(window, height=10, width=50)
list1.grid(row=0, column=2, rowspan=5)
#scrollbar
sb1 = Scrollbar(window)
sb1.grid(row=0, column=3, rowspan=5)
#scrollbar connect with listbox, y軸スクロール
list1.configure(yscrollcommand=sb1.set)
#y軸view will change
sb1.configure(command=list1.yview)
#event, bind(event, func)で→func(event)の形で実行される
list1.bind("<<ListboxSelect>>", get_selected_row)
#Button(window, text="", width=12, command=None)
b1=Button(window, text="View all", width=12, command=view_command)
b2=Button(window, text="Search entry", width=12, command=search_command)
b3=Button(window, text="Add entry", width=12, command=add_command)
b4=Button(window, text="Update", width=12, command=update_command)
b5=Button(window, text="Delete", width=12, command=delete_command)
b6=Button(window, text="Reset entry", width=12, command=reset_command)
b7=Button(window, text="Close", width=12, command=exit)
#grid(row=, column=3)
b1.grid(row=0, column=4)
b2.grid(row=1, column=4)
b3.grid(row=2, column=4)
b4.grid(row=3, column=4)
b5.grid(row=4, column=4)
b6.grid(row=5, column=4)
b7.grid(row=6, column=4)
window.mainloop()
#皮つくった
#sqlite3文つくった
#ボタン用funcを作る
#リストボックスへの選択イベントを関数と紐付け
'''
print(list1.curselection())
print(list1.curselection()[0])
print(list1.get)
print(selected_tuple)
(5,)#
5
<bound method Listbox.get of <tkinter.Listbox object .!listbox>>
(6, '5', 'リザード', 'True', 'ほのお', '')
'''
-
list1.configure(yscrollcommand=sb1.set)
はListboxのインスタンスlist1とScrollBarのインスタンスsb1を紐づけた。 -
sb1.configure(command=list1.yview)
でスクロールバーを動かすと、listboxのy軸(縦方向)の見るものが変わる。configure
は全てのウィジェット(Text,Listbox,Entryなど)に共通で存在。動きを紐つけることができる - Buttonを押したときの動作は
command=func
のfuncの部分に関数名を指定する。しかし、この際引数を指定できない。そのため引数な必要な場合、実際に実行した関数を別の関数で包んであげる(wrap_functionと呼ばれるっぽい)。この包んだ別の関数をButtonのcommand=
に渡してやればいい。 -
bind(event, func)
でfunc(event)
の形式で実行することができる。<<ListboxSelect>>
はリストボックス内部で選択されたときのイベント。get_selected_row()
の引数にこのイベントが渡される。参考元:https://python.keicode.com/advanced/tkinter-widget-listbox.php -
get_selected_row()
は自分で定義したもの。渡されたeventを元に、
list1.curselection()
でリストボックス内部で選択されたrowのindex部分を含むtupleを返す。tupleなのでlist1.curselection()[0]
で選択列のlistbox内部のindexを得られる。 -
list1.get(index)
で対象のrowに存在する値をtupleで受け取れる。list1はListBoxのインスタンス。list1.get()
のみだとListbox内部の値全てが返ってくる。 - jupyterlabでこのコードを実行すると、
window.destroy
が働かない。.pyにしてterminalで動かせば普通に動く。
##おまけ スタンドアローン化する
pip install pyinstaller
pyinstaller --onefile --windowed frontend.py
pyinstallerがない場合pip install pyinstaller
する。
backend.pyとfrontend.pyを同じディレクトリにおく。同ディレクトリ上で上記コードを実行すれば、appを作れる。以外の簡単なんやね。exeファイルも作れる。