memopy

pythonで作ってみました的なブログ

⑧ ドロップダウンリストの作成【python tkinter sqlite3で家計簿を作る】

⑧ ドロップダウンリストの作成【python tkinter sqlite3で家計簿を作る】

今回は、ttkモジュールを使って、ドロップダウンリスト(コンボボックス)の作成をしてみる。
前回までに作成したGUI
f:id:memopy:20170530222854p:plain
「内訳」の値は内訳テーブル(item)に登録された値を用いるため、直接入力よりドロップダウンリストのほうがユーザーフレンドリーである。
また、明細テーブル(acc_data)への登録は、'食費'などのitem_nameではなく、数値のitem_codeを登録するため、ドロップダウンリストで選択した値に対応するitem_codeの値を求めなければならない。
f:id:memopy:20170601211307p:plain
f:id:memopy:20170530214729p:plain
ドロップダウンリストはTkinterの拡張モジュールであるTkinter.ttkモジュールを用いる。
ちなみに、ttkにおけるウィジェット名は、コンボボックスである。

コンボボックス Combobox

始めに簡単なサンプルスクリプトを記載する。

# モジュールのインポート
import tkinter as tk
import tkinter.ttk as ttk

# ルートフレームの作成
root = tk.Tk()

# コンボボックスの作成(rootに配置,リストの値を編集不可(readonly)に設定)
combo = ttk.Combobox(root, state='readonly')
# リストの値を設定
combo["values"] = ("食費","住宅費","光熱費")
# デフォルトの値を食費(index=0)に設定
combo.current(0)
# コンボボックスの配置
combo.pack()

# ボタンの作成(コールバックコマンドには、コンボボックスの値を取得しprintする処理を定義)
button = tk.Button(text="表示",command=lambda:print(combo.get()))
# ボタンの配置
button.pack()

root.mainloop()

コンボボックスが作成され、「表示」を押下すると、インタプリタコンソールにコンボボックスの値を出力したのが確認できる。
f:id:memopy:20170601213942p:plain

Combboxのオプション(抜粋)
オプション名 入力型式 説明
justify center,left(デフォルト),right テキストの文字寄せ
postcommand コールバック関数 リストが表示される直前の処理
state normal(デフォルト)
readonly
disable
リストの状態
normal:リストの編集可能
readonly:リストの編集不可
disable:非活性(選択不可)
values tuple リストの要素
width integer コンボボックスの幅
Combboxのメソッド(抜粋)
# コンボボックスに値をセットする
combo.set("string")
# コンボボックスの値を読み取る
combo.get()
# コンボボックスの初期値を設定する
combo.current(0) #index = 0に設定
Combboxの値の取得

次に、コンボボックスから出力した値を用いて、データベースのitem_codeを算出する。
やり方は、コンボボックスの値を読み取り、SQLのWHERE句にセットし、該当するitem_codeを取ってくる。

# モジュールのインポート
import tkinter as tk
import tkinter.ttk as ttk
import sqlite3
# --------------------------------
# ボタンが押下されたときのコールバック関数
def getitemcode(item_name):
    # データベースの接続
    c = sqlite3.connect("database.db")
    # SELECT文の発行 WHERE句には、変数item_nameをformat関数で代入する
    item_code = c.execute("""
        SELECT item_code FROM item
        WHERE item_name = '{}'
        """.format(item_name))
    # SELECT文の結果をfetchoneメソッドで1つ表示する。
    # fethoneメソッドはタプルで返ってくるので、index0を取得し出力する。
    print(item_code.fetchone()[0])
# -------------------------------
# GUI部分の作成
root = tk.Tk()

combo = ttk.Combobox(root, state='readonly')
combo["values"] = ("食費","住宅費","光熱費")
combo.current(0)
combo.pack()

# コールバック関数にgetitemcodeを定義
button = tk.Button(text="表示",command=lambda:getitemcode(combo.get()))
button.pack()

root.mainloop()

リストの値に応じたitem_codeを取得できた!
f:id:memopy:20170601222023p:plain
次は、リストの値を内訳テーブル(item)から作成したい。
itemテーブル内にある全てのitem_nameをタプルにすればコンボボックスのvaluesオプションに渡せる。
この処理は新たにcreateitemnameというファンクションを作成した。

# モジュールのインポート
import tkinter as tk
import tkinter.ttk as ttk
import sqlite3
# -------------------------------
# 内訳テーブル(item)にあるitem_nameのタプルを作成する
def createitemname():
    # データベースの接続
    c = sqlite3.connect("database.db")
    # 空の「リスト型」を定義
    li = []
    # SELECT文を発行し、item_nameを取得し、for文で回す
    for r in c.execute("SELECT item_name FROM item"):
        # item_nameをリストに追加する
        li.append(r)
    # リスト型のliをタプル型に変換して、ファンクションに戻す
    return tuple(li)
# -------------------------------
# ボタンが押下されたときのコールバック関数
def getitemcode(item_name):
    c = sqlite3.connect("database.db")
    item_code = c.execute("""
        SELECT item_code FROM item
        WHERE item_name = '{}'
        """.format(item_name))
    print(item_code.fetchone()[0])
# ------------------------------
# GUI部分の作成
root = tk.Tk()

combo = ttk.Combobox(root, state='readonly')
# createitemname関数を用いて、データベース内のitem_nameのタプルを作成し、リストの値に指定する
combo["values"] = createitemname()
combo.current(0)
combo.pack()

button = tk.Button(text="表示",command=lambda:getitemcode(combo.get()))
button.pack()

root.mainloop()

データベース内にあるテーブルからリストの値に追加できた!
f:id:memopy:20170601223633p:plain

最後に、前回まで作成したGUIにコンボボックスの機能を実装する。

# -*- coding: utf-8 -*-

import tkinter as tk
import tkinter.ttk as ttk
import sqlite3

# 空のデータベースを作成して接続する
dbname = "database.db"
c = sqlite3.connect(dbname)
c.execute("PRAGMA foreign_keys = 1")

# 既にデータベースが登録されている場合は、ddlの発行でエラーが出るのでexceptブロックで回避する
try:
    # itemテーブルの定義
    ddl = """
    CREATE TABLE item
    (
       item_code INTEGER PRIMARY KEY 
       AUTOINCREMENT,
       item_name TEXT NOT NULL UNIQUE
    )
     """
    # SQLの発行
    c.execute(ddl)
    # acc_dataテーブルの定義    
    ddl = """
    CREATE TABLE acc_data
    ( 
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        acc_date DATE NOT NULL,
        item_code INTEGER NOT NULL,
        amount INTEGER,
        FOREIGN KEY(item_code) REFERENCES item(item_code)
    )
    """
    # itemテーブルへリファレンスデータの登録
    c.execute(ddl)
    c.execute("INSERT INTO item VALUES(1,'食費')")
    c.execute("INSERT INTO item VALUES(2,'住宅費')")
    c.execute("INSERT INTO item VALUES(3,'光熱費')")
    c.execute("COMMIT")
except:
    pass
# -------------------------------
# 内訳テーブル(item)にあるitem_nameのタプルを作成する
def createitemname():
    # データベースの接続
    c = sqlite3.connect("database.db")
    # 空の「リスト型」を定義
    li = []
    # SELECT文を発行し、item_nameを取得し、for文で回す
    for r in c.execute("SELECT item_name FROM item"):
        # item_nameをリストに追加する
        li.append(r)
    # リスト型のliをタプル型に変換して、ファンクションに戻す
    return tuple(li)
# ------------------------------
# 登録ボタンがクリックされた時にデータをDBに登録するコールバック関数
def create_sql(item_name):

    # データベースに接続
    c = sqlite3.connect("database.db")
    # item_nameをWHERE句に渡してitem_codeを取得する
    item_code = c.execute("""
         SELECT item_code FROM item
         WHERE item_name = '{}'
         """.format(item_name))
    item_code = item_code.fetchone()[0]
    # 日付の読み取り
    acc_data = entry1.get()
    # 金額の読み取り
    amount = entry3.get()

    # SQLを発行してDBへ登録
    try:
        c.execute("""
        INSERT INTO 
        acc_data(acc_date,item_code,amount)
        VALUES('{}',{},{});
        """.format(acc_data,item_code,amount))
        c.execute("COMMIT;")
        print("1件登録しました")
    # ドメインエラーなどにより登録できなかった場合のエラー処理
    except:
        print("エラーにより登録できませんでした")

# rootフレームの設定
root = tk.Tk()
root.title("家計簿アプリ")
root.geometry("300x280")

# メニューの設定
frame = tk.Frame(root,bd=2,relief="ridge")
frame.pack(fill="x")
button1 = tk.Button(frame,text="入力")
button1.pack(side="left")
button2 = tk.Button(frame,text="表示")
button2.pack(side="left")
button3 = tk.Button(frame,text="終了")
button3.pack(side="right")

# 入力画面ラベルの設定
label1 = tk.Label(root,text="【入力画面】",font=("",16),height=2)
label1.pack(fill="x")

# 日付のラベルとエントリーの設定
frame1 = tk.Frame(root,pady=10)
frame1.pack()
label2 = tk.Label(frame1,font=("",14),text="日付")
label2.pack(side="left")
entry1 = tk.Entry(frame1,font=("",14),justify="center",width=15)
entry1.pack(side="left")

# 内訳のラベルとエントリーの設定
frame2 = tk.Frame(root,pady=10)
frame2.pack()
label3 = tk.Label(frame2,font=("",14),text="内訳")
label3.pack(side="left")
# 内訳コンボボックスの作成
combo = ttk.Combobox(frame2, state='readonly',font=("",14),width=13)
combo["values"] = createitemname()
combo.current(0)
combo.pack()

# 金額のラベルとエントリーの設定
frame3 = tk.Frame(root,pady=10)
frame3.pack()
label4 = tk.Label(frame3,font=("",14),text="金額")
label4.pack(side="left")
entry3 = tk.Entry(frame3,font=("",14),justify="center",width=15)
entry3.pack(side="left")

# 登録ボタンの設定
button4 = tk.Button(root,text="登録",
                    font=("",16),
                    width=10,bg="gray",
                    command=lambda:create_sql(combo.get()))
button4.pack()

# メインループ
root.mainloop()

GUIが完成した!
f:id:memopy:20170601225707p:plain
機能試験をしてみる。
f:id:memopy:20170601225939p:plain
登録できているのがわかる。
次に、データベースに正しく登録されているか確認してみる。
f:id:memopy:20170601230849p:plain
正しく登録されていた!!
今回はここまで!
次回は表示部分のGUIを作成したい。

質問や記事の誤りがありましたらコメントください。

2017/6/4追記:sqlite3は、日付型(date型)というデータ型がない。日付として例えばBETWEEN演算子などの処理を認識させるためには、
'YYYY-MM-DD'
'YYYY-MM-DD hh:mm:ss'
の形式で記述しなければならない。
入力形式にYYYY/MM/DDと入力されていたら、replaceメソッドで'/'を'-'に置き換える処理が必要になる。

前の記事
⑦ GUIの実装 sqlite3への登録【python tkinter sqlite3で家計簿を作る】
次の記事
⑨ 表(テーブル)の作成【python tkinter sqlite3で家計簿を作る】