【Tkinter開発】第5回 Tkinterでファイルを指定されたら読み込む【クラス間のデータやり取り】

クラス間でデータのやり取りをする
前回までにMenuクラス内でファイルを指定できるようにした。
実際のファイル読み込みはTablesクラス内で処理したいので、読み込んだファイルパス文字列はTablesクラスに渡す。
これをするために、クラス間の共通変数を導入する。
クラス間共通変数propsの導入
クラス間の共通変数propsはTablesクラスとMenuクラスをそれぞれ呼び出しているApplicaitonクラス内のメンバ変数として定義する。
このメンバ変数の名前はpropsとしたが、使われていなければ名前は何でもよい。
ただし、propsの型は辞書型、リスト型、クラスのインスタンスにする必要がある。(つまり、数値、文字列等のスカラーがNG)
この辺の処理はPythonの「参照」の仕様を利用しており、Tkinterの仕様とは関係ない。
今回は辞書型を利用するが、規模の大きいアプリケーションならば、クラスのインスタンスを利用するのが安全である。
まずはTablesクラス
class Tables(tk.Frame):
def __init__(self, master: tk.Tk, props: dict):
super().__init__(master)
self.props = props
# テーブルの作成
self.create_table()
...中略...Menuクラス
class Menu(tk.Menu):
def __init__(self, master: tk.Tk, props: dict):
super().__init__(master)
self.props = props
# メニューバー
menubar = tk.Menu(self.master)
...中略...Applicationクラス
class Application(tk.Frame):
def __init__(self, master: tk.Tk):
super().__init__(master)
self.title = "Tkinter Application"
master.geometry("800x600")
master.title(self.title)
# クラス間で共通で使われる変数
props: dict = {}
Menu(self.master, props)
self.mainframe = tk.Frame(self.master)
self.mainframe.grid_rowconfigure(0, weight=1)
self.mainframe.grid_columnconfigure(0, weight=1)
self.mainframe.pack()
self.tables = Tables(self.mainframe, props)
self.tables.grid()これでクラス間でデータのやり取りができるようになった。
例:使い方としては以下のようにする。(MenuクラスからTablesクラスにデータを送りたいとき)
class Menu:
...中略...
def action(self):
values = 1
self.props["values"] = valuesMenuクラスで定義されたaction()関数が実行されたときに、propsに新たなキー"values"として値をセットする。
class Tables:
...中略...
def recieve(self):
values = self.props.get("values")Tablesクラスで定義されたrecieve()関数が実行されると、self.propsにキー"values"を指定して値を取り出せる。
(キー"values"が定義されていない可能性を考慮してget()で取り出している)
これはPythonの参照の仕様を利用しており、self.props変数自体を “self.props = {}" のように再定義してしまうと、利用できなくなってしまうので注意が必要である。
複雑すぎて理解が追いつかないんだけど、、
今はpropsをこのように定義すると、propsの中身を引き継げると思ってくれれば大丈夫です
ファイルパスを取得したら、Tablesクラスにデータを渡す
さっそく上記の機能を使って、ファイルダイアログでファイルパスを取得したら、Tablesクラスに値を渡して開けるようにする。
class Menu(tk.Menu):
...中略...
def open_filedialog(self):
# ファイルフィルタ
file_type = [("XMLファイル", "*.xml"), ("", "*")]
# 最初に開くフォルダ
initial_directory_path = os.path.abspath(os.path.dirname(__file__))
file_name = filedialog.askopenfilename(
filetypes=file_type, initialdir=initial_directory_path
)
# ファイルを開いたら, イベントを発生させる
if file_name != "":
self.props["open_file_name"] = file_name
self.master.event_generate("<<OpenFile>>")ファイルダイアログでファイルが指定された場合、self.propsに"open_file_name"にファイルパスをセットする。
そして、独自イベントとして"<<OpenFile>>"を発生させる。
これにより、Tablesクラスはファイルが指定されたことを検知できるようになり、self.propsからファイルパスを取り出す。
Tablesクラスは以下のようにする。
class Tables(tk.Frame):
def __init__(self, master: tk.Tk, props: dict):
...中略...
# テーブルデータの詳細ディスプレイ
self.create_display()
# アプリケーション全体で独自イベントを捉える
self.master.bind_all("<<OpenFile>>", lambda _: self.open_file())
...中略...
def open_file(self):
# ファイルパスの取得
file_name = self.props.get("open_file_name")
print(file_name)tkinterで、イベントの検知はbind()関数またはbind_all()関数を利用する。
(bind_all()はtk.Tkインスタンスの階層をまたいで検知できる。)
“<<OpenFile>>"イベントを検知したらself.open_file()関数を呼び出す。
関数内では先程定義された"open_file_name"キーの値を取り出し、ファイルパスを取得できる。
ここまで実装するとファイルダイアログでファイルを指定すると、ファイルパスがprintされるはずである。
Tkinterにはクラス間でデータをやり取りする仕組みは用意されてないのかしら
いろいろ調べたけどTkinterにはクラス間でデータをやり取りする機能(tk.Tkインスタンスを経由したデータやり取り機能)がないようなので、このようなやり方にしたよ
他にはクリップボードを使ったやり方があるんだけど、もっと複雑なのでやめたました、、、
XMLデータを読み込んで表に表示させる
XMLデータを読み込んでパースする
ファイルパスが取得できるようになったので、Tablesクラスのopen_file()を書き換えて、XMLデータをパースする。
パースする処理は別途_parse_xml_data()関数に書いている。
import tkinter as tk
from tkinter import ttk
import xml.etree.ElementTree as ET
class Tables(tk.Frame):
...中略...
def open_file(self):
# ファイルパスの取得
file_name = self.props.get("open_file_name")
xml_data = self._parse_xml_data(file_name)
self.import_table_data(data=xml_data)
def _parse_xml_data(self, file_path):
xml_data = []
# ElementTreeオブジェクトの作成
tree = ET.parse(file_path)
root = tree.getroot()
# ルート以下のchannelを取得
channel = root.find(".//channel")
# channel以下のitemを取得
for item in channel.findall(".//item"):
# item以下の情報を取得する
title = item.find(".//title")
title_data = title.text if title is not None else ""
description = item.find(".//description")
description_data = description.text if description is not None else ""
link = item.find(".//link")
link_data = link.text if link is not None else ""
date = item.find(".//pubDate")
date_data = date.text if date is not None else ""
xml_data.append([title_data, description_data, link_data, date_data])
return xml_data主題から外れるので、_perse_xml_data()関数の説明は割愛する。(Python標準のxmlパーサを使っている。)
RSS用のXMLファイルなので、それ以外の用途に使いたい場合はアレンジしてみてください。
open_file()関数内のimport_table_data()はすでに定義されており、この関数にデータを渡すと表内にデータを挿入できる。
サンプルデータ行の削除
最後に、__init__()で事前に入れていたサンプルデータはいらないので、この辺の処理を削除すればOK。
class Tables(tk.Frame):
def __init__(self, master: tk.Tk, props: dict):
super().__init__(master)
self.props = props
# テーブルの作成
self.create_table()
# サンプルデータの追加
...この辺を削除...
# テーブルデータの詳細ディスプレイ
self.create_display()
# アプリケーション全体で独自イベントを捉える
self.master.bind_all("<<OpenFile>>", lambda _: self.open_file())
...中略...起動すると事前データがないので、真っ白になる。

ファイルの「ファイルを開く」を押すとファイルダイアログが開き、ファイルを指定できる。

ファイルを開くと、、、

ようやく読み込みまで出来ました
ファイルを読み込んで表示させるまで結構大変ね、、
でもGUIアプリっぽくなったわね
というわけで、次回は表のデータをCSVファイルとして出力する機能を実装します
あとは今までやってきたことの応用で出来そうね











ディスカッション
コメント一覧
まだ、コメントがありません