Python tkinter GUIプログラミング テーブル情報取得

 今日も見に来てくださって、ありがとうございます。石川さんです。

 今日は、データベースオラクルへ接続、そのスキーマのテーブル情報を取得してTreeviewへ表示するプログラムにチャレンジしました。いや~、けっこう時間がかかりましたねぇ。Treeviewを思ったように操作するのが難しかったなぁ。

 Oracleのバージョンは18c Express Edition、PythonはAnaconda3です。

出来上がりイメージ

 今回のソースを実行して、「load table」ボタンをクリックすると、こんな感じのウィンドウになります。左側のテーブル一覧を選択すると右側に項目の一覧が表示されます。

できあがりイメージ

ソースコード

import cx_Oracle as db
import tkinter as tk
import tkinter.ttk as ttk

LOGIN_USER = "ユーザー名をここに入力"
LOGIN_PASSWORD = "パスワードをここに入力"
LOGIN_HOST = "localhost:1521/xepdb1"

class Application(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("Table viewer sample for ORACLE XE 18c")

        self.conn = db.connect(LOGIN_USER, LOGIN_PASSWORD, LOGIN_HOST)
        self.cur = self.conn.cursor()
        
        self.style = ttk.Style(self)
        self.style.configure("Treeview", rowheight=30)
        
        columns = ("#1", "#2")
        self.table_list = ttk.Treeview(self, show="headings", columns=columns, selectmode="browse")
        self.table_list.heading("#1", text="TABLE_NAME")
        self.table_list.column("#1", width=125)
        self.table_list.heading("#2", text="COMMENTS")
        self.table_list.column("#2", width=300)
        self.table_list.grid(row=0, column=0, rowspan=2, sticky=tk.NSEW)
        self.scroll_bar = ttk.Scrollbar(self, command=self.table_list.yview)
        self.table_list.configure(yscroll=self.scroll_bar.set)
        self.scroll_bar.grid(row=0, column=1, rowspan=2, sticky=tk.NS)
        self.table_list.bind("<<TreeviewSelect>>", self.show_table)
        
        columns = ("#1","#2","#3")
        self.table_info = ttk.Treeview(self, show="headings", columns=columns)
        self.table_info.heading("#1", text="COLUMN_NAME")
        self.table_info.heading("#2", text="COMMENTS")
        self.table_info.heading("#3", text="DATA_TYPE")
        self.table_info.grid(row=0, column=2, sticky=tk.NSEW)
        
        self.load_button = ttk.Button(self, text="load table", command=self.load_table)
        self.load_button.grid(row=1, column=2)
        
        self.grid_rowconfigure(0, weight=1)

    def load_table(self):
        self.table_list.delete(*self.table_list.get_children())
        self.table_info.delete(*self.table_info.get_children())
        sql = ("SELECT TABLE_NAME, COMMENTS"
               "  FROM USER_TAB_COMMENTS"
               " WHERE TABLE_TYPE = 'TABLE'")
        results = self.cur.execute(sql)
        for row in results:
            self.table_list.insert("", tk.END, values=row)
        self.table_list.selection_set(self.table_list.get_children()[0])
    
    def show_table(self, event):
        sel = self.table_list.selection()
        table_name = self.table_list.item(sel[0])["values"][0]
        sql = ("SELECT T1.COLUMN_NAME, T1.COMMENTS,"
               "  CASE WHEN T2.DATA_TYPE IN ('TIMESTAMP(6)','DATE') THEN T2.DATA_TYPE"
               "       WHEN T2.DATA_TYPE IN ('CHAR','VARCHAR2') THEN T2.DATA_TYPE ||'('||T2.DATA_LENGTH||')'"
               "       WHEN T2.DATA_TYPE = 'NUMBER' THEN T2.DATA_TYPE || "
               "         CASE WHEN T2.DATA_PRECISION IS NULL AND T2.DATA_SCALE IS NULL THEN ''"
               "              WHEN T2.DATA_PRECISION IS NOT NULL AND NVL(T2.DATA_SCALE,0) = 0 THEN '('||T2.DATA_PRECISION||')'"
               "              WHEN T2.DATA_PRECISION IS NOT NULL AND T2.DATA_SCALE IS NOT NULL THEN '('||T2.DATA_PRECISION||'.'||T2.DATA_SCALE||')'"
               "              WHEN T2.DATA_PRECISION IS NULL THEN '(.'||T2.DATA_SCALE||')'"
               "              END"
               "       END"
               "  FROM USER_COL_COMMENTS T1, USER_TAB_COLUMNS T2"
               " WHERE T1.TABLE_NAME = T2.TABLE_NAME"
               "   AND T1.COLUMN_NAME = T2.COLUMN_NAME"
               "   AND T1.TABLE_NAME = :table_name")
        results = self.cur.execute(sql, table_name=table_name)
        self.table_info.delete(*self.table_info.get_children())
        for row in results:
            self.table_info.insert("", tk.END, values=row)
        
if __name__ == "__main__":
    application = Application()
    application.mainloop()

解説

 Oracleへの接続にはおなじみのcx_Oracleを利用しました。cx_Oracleのセットアップについては、こちらを参考にしてください。

 5~7行目の接続情報はご自分の環境にあわせて変更してください。Oracle18c Express Editionをデフォルトインストールしたぼくの環境では、このLOGIN_HOSTで接続できました。もしかしたらlocalhostをローカルPCのIPアドレスに変更する必要があるかも知れません。

 14、15行目の初期化処理で、Oracleへの接続とカーソル取得しています。21行目でTreeviewを作成しています。showオプションは”tree”と”heading”が指定できますが、組み合わせで以下のように変わってきます。

  • “tree” カラム#0を表示します。
  • “headings” ヘッダ行を表示します。
  • “tree headings” カラム#0とヘッダ行の両方を表示します。(デフォルト値)
  • “” カラム#0もヘッダ行も表示しない。

 selectmodeは、”extended”、”browse”、”none”が選択可能で、以下のような意味があります。

  • “extended” 複数アイテムが選択可能。(デフォルト値)
  • “browse” 単一のアイテムが選択可能。
  • “none” 選択状態は変わらない。

 ちなみに、このTreeviewの行間、ぼくの環境Windows10では低く設定されていて文字が切れてしまっていたので、17、18行目のtkk.Styleを使って”Treeview”のrowheightを30にセットしています。

 スクロールバーは27、28行目で作成してセットしています。イディオムだと思って覚えてしまいましょう。30行目でバインドしているのは、仮想イベントの<<TreeviewSelect>>です。セレクションが変更されたときに発生します。ここでは、show_tableをバインドしています。これで選択されたテーブルの情報を隣のTreeviewに表示しています。

 42行目のself.grid_rowconfigure(0, weight=1)ですが、ウィンドウのサイズを変更したときに、行がどれくらいの割合で広がるか、というのを設定しています。指定しない場合のweightは0にセットされています。これによりgridで指定された0行目がウィンドウのサイズ変更に伴って変更されます。ちなみに、ウィジェットにgridを適用するときにstickyを指定しない場合は大きさは変わらないので注意が必要です。

 45、46行目では、Treeviewの項目を削除しています。すべての項目を取得するのに、self.table_list.get_children()を利用しています。このメソッドはすべての項目をタプルで戻します。self.table_list.delete()の引数は、削除したい項目をすべて渡す必要があるため、先頭に「*」アスタリスクを付けて、タプルのアンパックをしています。タプルのままでは動作しませんので、注意が必要です。

 47~49行目はSQL文を定義しています。複数文字列をインデントを無視した形で改行するのに丸括弧「()」を使っています。丸括弧の中はインデント無視できるのですよね。ちなみにここで指定しているテーブルのUSER_TAB_COMMENTSは、オラクルのデータディクショナリで、テーブルに設定されているコメントを参照することができるビューになります。

 SQL文の実行は50行目です。51行目で結果セットを順番に取り出すforループを記述しています。データを取得して、ループして一行ずつ処理する、このパターンもよく使いますので、覚えておくと便利です。

 Treeviewへのデータの追加はinsert()メソッドです。valuesパラメータを使うことで複数項目を一気にセットすることができます。

 あと、53行目は、テーブルを取得したときに、1行目を選択させて、テーブル情報を右側に表示するために追加しました。1行目を選択するのに結構悩みましたよ~。selection_set()メソッドへ渡すパラメータは項目名なのですよね。0とか-1とかを指定してもエラーになります。そこで、ここでは、self.table_list.get_children()[0]を使って最初の項目を取得しています。この0を-1に変更すると最後の項目、任意の行の場合も数値を変更するだけで対応可能ですね。

 56行目、sel = self.table_list.selection()は、選択中のアイテム名を取得しています。取得結果は複数の場合もあるのでタプルで戻ってきます。ここでは一項目しか取得できない設定ですので、sel[0]と指定すれば、選択中の項目が利用可能になります。57行目のtable_name = self.table_list.item(sel[0])["values"][0]で、Treeviewにセットされた値を取得することができます。

まとめ

 いや~、時間かかったなぁ。あ、二つ目のSQLの説明を忘れてました。ま、そんなに難しくないし、見ればわかるか。Treeviewの扱いに慣れるのには、ちょっと時間がかかりそうですね。またサンプル作ってみますね。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です


The reCAPTCHA verification period has expired. Please reload the page.