🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
Tkinter

Tkinterは、GUIツールキットである“Tk”をPythonから利用できるようにした標準ライブラリである。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

3回答

10091閲覧

tkinterのtreeviewにて、大量のデータを表示させる方法

hijiri

総合スコア61

Tkinter

Tkinterは、GUIツールキットである“Tk”をPythonから利用できるようにした標準ライブラリである。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

0グッド

0クリップ

投稿2021/02/21 02:08

tkinterのtreeviewにて、データベースから取得した大量のデータを表示させたいと考えています。

pythom

1import tkinter as tk 2import tkinter.ttk as ttk 3 4class Treelist(tk.Frame): 5 def __init__(self, root): 6 super().__init__(root) 7 self.pack(fill=tk.Y) 8 9 self.tree = ttk.Treeview(self, height=30, show="headings") 10 self.hscrollbar = ttk.Scrollbar(self, orient=tk.HORIZONTAL, command=self.tree.xview) 11 self.scrollbar = ttk.Scrollbar(self, command=self.tree.yview) 12 self.tree.configure(xscrollcommand=lambda f, l: self.hscrollbar.set(f, l)) 13 self.tree.configure(yscrollcommand=lambda f, l: self.scrollbar.set(f, l)) 14 self.scrollbar.pack(side="right", fill="y") 15 self.tree.pack(expand=1, fill=tk.BOTH) 16 self.hscrollbar.pack(fill="x") 17 18 self.state() 19 self.tree_insert() 20 21 def state(self): 22 self.tree.configure(column=(1,2,3)) 23 #ヘッダーテキスト 24 self.tree.heading(1,text="種類") 25 self.tree.heading(2,text="名前") 26 self.tree.heading(3,text="説明") 27 #列の幅 28 self.tree.column(1, width=100, anchor="center", stretch=tk.NO) 29 self.tree.column(2, width=100, anchor="center", stretch=tk.NO) 30 self.tree.column(3, width=200, anchor="center", stretch=tk.NO) 31 32 def tree_insert(self): 33 self.item = () 34 for i in range(10000): 35 self.item = self.item + ((i,"犬","ワンと鳴く"),) 36 for i in range(len(self.item)): 37 self.num = self.item[i][0] 38 self.name = self.item[i][1] 39 self.text = self.item[i][2] 40 self.tree.insert("", "end", values=(self.num, self.name, self.text)) 41 42root = tk.Tk() 43root.geometry('900x750') 44tree = Treelist(root) 45root.mainloop()

self.itemはデータベースから取得して、文字列の一致検索に当てはまるデータを10000件として、仮に置いたものです。今回は、treeviewでの大量データの表示についてお尋ねしたかったので、このように省略させていただきました。

このように、10000件のデータを一気にinsertすると、パソコンの環境次第ではアプリが止まってしまいます。
また、これが1000000件のデータとなると、ハイスペックなパソコンでしか処理が追いつきません。

このような場合、どのようにすれば、メモリをあまり使用せずに表示することができるのでしょうか?

どうぞ宜しくお願いいたします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

0

要点のみに絞りコンパクトなサイズに出来たので、投稿しておきます。


改善したいコード(問題点: 大規模な数になるとパフォーマンスが落ちる)

python

1import tkinter as tk 2from tkinter import ttk 3 4N = 1000 # データの件数 5 6root = tk.Tk() 7tree = ttk.Treeview(root, height=20, show="headings", columns=[1]) 8tree.pack() 9for num in range(N): # XXX: 数が大規模になると時間が掛かる 10 tree.insert("", tk.END, values=(num,)) 11root.mainloop()
  • Treeview の height は、画面内に表示する行数

 表示されてるウィジェットの高さと必ず一致するわけではない事が注意点。

  • columns=[1] は適当な値。

一度に表示する件数を一定数に制限する。

Next ボタンで次のページのデータを読み込む実装にします。

python

1import tkinter as tk 2from tkinter import ttk 3 4N = 1000 # データ件数 5PAGE_SIZE = 10 # 一度に表示する件数 6 7root = tk.Tk() 8tree = ttk.Treeview(root, height=PAGE_SIZE, show="headings", columns=[1]) 9tree.pack() 10 11def update_tree_items(offset=0, size=PAGE_SIZE): 12 tree.delete(*tree.get_children()) 13 14 # 改善点: データの数 N の値に関わらず、一定件数 (PAGE_SIZE) しか insert しない。 15 for num in range(offset, min(N, offset+size)): 16 tree.insert("", tk.END, values=(num,)) 17 18offset = 0 19def next_page(): 20 global offset 21 offset += PAGE_SIZE 22 update_tree_items(offset, PAGE_SIZE) 23 24button = ttk.Button(root, text="Next", command=next_page) 25button.pack() 26 27root.after_idle(update_tree_items, offset, PAGE_SIZE) 28root.mainloop()
  • サンプル提示が目的&コード量を少なくする為、

 前のページ~等々は実装してません。

  • コードを短くするためにグローバル変数にしてますが、

 実際は、関数内であれば nonlocal や、クラスを使ってインスタンス変数に。


スクロールバーによるページ遷移

ページ単位の読込が実装出来れば、
ページ遷移をスクロールバーの位置から求める事で、
大規模なデータをツリービュー内に表示してるように見せかける事が可能です。

python

1import tkinter as tk 2from tkinter import ttk 3 4N = 1000 # データの件数 5PAGE_SIZE = 10 # 一度に表示する件数 6 7root = tk.Tk() 8tree = ttk.Treeview(root, height=PAGE_SIZE, show="headings", columns=[1]) 9tree.pack(fill=tk.BOTH, side=tk.LEFT, expand=tk.YES) 10 11 12def update_tree_items(offset=0, size=PAGE_SIZE): 13 # 表示中の要素を削除 14 tree.delete(*tree.get_children()) 15 16 # offset から表示する要素のみを追加 17 for num in range(offset, min(N, offset+size)): 18 tree.insert("", tk.END, values=(num,)) 19 20 21def yview(command, amount, unit=None): 22 if command == "moveto": 23 # スクロールバー位置から表示するデータの offset を計算 24 # 有効なスクロール範囲は 0.0 ~ 1.0 (範囲外までドラッグすると超えることも) 25 # offset 範囲は 0 ~ N - PAGE_SIZE 26 offset = max(0, min(N-PAGE_SIZE, int(float(amount) * N))) 27 28 # 表示するアイテムを更新 29 update_tree_items(offset, PAGE_SIZE) 30 31 # スクロールバー位置の計算 32 first = offset / N 33 last = (offset + PAGE_SIZE) / N 34 vbar.set(first, last) 35 36 37vbar = ttk.Scrollbar(root, orient=tk.VERTICAL, command=yview) 38vbar.pack(side=tk.RIGHT, fill=tk.Y) 39 40root.after_idle(yview, "moveto", 0.0) 41root.mainloop()

ここから先、キー操作・マウスホィール対応や他のスクロールイベントは
コメントに書いた URL のコードを参考にして下さい。

投稿2021/02/23 14:21

編集2021/02/24 14:02
teamikl

総合スコア8717

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

hijiri

2021/02/24 08:21

丁寧にコードまで示していただきまして、本当にありがとうございます! 教えていただいたものを読み解いてみながら、もっと勉強していきます。
guest

0

※ 質問に対する回答ではありません。
質問のコードで気になった部分があったので

python

1 def tree_insert(self): 2 self.item = () 3 for i in range(10000): 4 # XXX: tuple の連結操作は常に新しいオブジェクトを作成するので、 5 # メモリにやさしくありません。self.item にはリストが適してます。 6 self.item = self.item + ((i,"犬","ワンと鳴く"),) 7 8 # XXX: 不要なインスタンス変数/スライス操作 9 for i in range(len(self.item)): 10 self.num = self.item[i][0] 11 self.name = self.item[i][1] 12 self.text = self.item[i][2] 13 self.tree.insert("", "end", values=(self.num, self.name, self.text))

python

1 def tree_insert(self): 2 items = [] 3 for i in range(10000): 4 items.append((i, "犬", "ワンと鳴く")) 5 6 tree = self.tree 7 for row in items: 8 tree.insert("", "end", values=row) 9 10 # 中の要素を編集したい場合は 11 for num, name, text in items: 12 tree.insert("", "end", values=(num, name, text)) 13 14 # 事前リストを作らず 15 def tree_insert(self): 16 tree_insert = self.tree.insert # ループ内で呼び出すメソッドをローカル変数に入れておく 17 for i in range(10000): 18 tree_insert("", "end", values=(i, "犬", "ワンと鳴く"))

計算量のオーダー自体は変わらないので、大規模なデータの扱いに対する回答ではありません。
明らかに冗長で効率化・改善が可能な部分の指摘です。


間違いではありませんが、引数が一致してる場合はlambda を省略可能です。

diff

1- self.tree.configure(xscrollcommand=lambda f, l: self.hscrollbar.set(f, l)) 2+ self.tree.configure(xscrollcommand=self.hscrollbar.set)

投稿2021/02/23 05:20

編集2021/02/23 05:29
teamikl

総合スコア8717

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

hijiri

2021/02/23 13:55

色々と手ほどきいただき、ありがとうございます。 メソッドをローカル変数に入れとくというのは、目からウロコでした。 そんなことができるんですね。 もう少し勉強してみたいと思います!
teamikl

2021/02/24 00:05

固定の引数も一緒に含めるなら、こんな方法も。 tree_insert = functools.partial(self.tree.insert, "", "end") tree_insert(values=(1, 2, 3)) ループ内で参照するオブジェクトをローカル変数にキャッシュしておくのは、 数万回規模でループを廻すなら多少影響ある程度で、(乱用し過ぎると) 却って冗長になる場面もあるので、コードの読みやすさと相談です。 ちなみに、ここでの効率化は - 事前のリストの構築に1万回、ツリーへのinsertに1万回、 合計2万回の繰り返しをひとまとめに。 - 繰り返し一回当たりの処理の効率化 多少の速度アップは見込めるものの、「計算量オーダー」の観点からは、 処理全体(繰り返しの数)は、データ数に比例している事には変わらないので 大規模なデータの扱いに対しての解という訳ではありません。
guest

0

ベストアンサー

Treeview は大規模なデータの表示に対応してないので、
用途に合う外部ライブラリを探すか、表示するデータの扱いを工夫する必要があります。

具体的なコードは、前の回答以上のものを示せませんが
前提として、

  • 問題点: 全てのデータを insert している
  • 改善策: 表示に必要な部分のみを読み込む実装にする

ページ制にする

1画面に表示できる情報は、画面サイズにより限られているので
必要以上に insert せずに、固定行数のTreeview として運用します。

Treeview のデータの更新手段は2通り

  • ページ切り替えの度に delete/insert する
  • insert した行はそのままで、item メソッド により values 情報を更新する

スクロールバーによるページ遷移

ページ制に出来れば、大規模なデータを表示してるように見せかけるのは、
スクロールバーの移動によるページ遷移を実装します。(スクロールバーのイベントの扱いが難関)

一度に表示してる件数は、数十件のはずなので、
スクロールの度に 全て delete/insert しても、パフォーマンスに影響しませんが、
データベースがネットワーク経由の場合は、クエリ結果をキャッシュする等、
最適化した方が良いケースもあります。(最適化するポイント)

追記: SQL に言及しておくと、SELECT 文に OFFSET, LIMIT 句を使いクエリを構成します。

但し、これで終りではなく、実用化する為には
リサイズ時の表示件数の拡張、マウスホィールやキーボード操作の対応
等といった、雑多な操作への対応も必要です。(手間がかかる部分)

外部ライブラリを使う。他のGUIを選択する。

使ったことはありませんが、実装に当たり参考になるようなものは pandatable
tkinter 以外の GUI だと Qt や Wx が、大規模なデータ表示に対応したウィジェットを提供してます。

クラスの設計に関しては、
Qt(QListWidget/QListView) Wx(ListCtrl/ListView) を参考にした方が良いでしょう。
Qt (QTableView) Wx (dataview) を参考にした方が良いでしょう。
tkinter を使う場合は、ほぼ同等の機能を tkinter へ移植・実装することになります。

投稿2021/02/22 05:43

編集2021/02/23 05:04
teamikl

総合スコア8717

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

hijiri

2021/02/22 23:55

御返事ありがとうございます。 先日教えていただきましたコードを、ツリービュー用に自分なりにいじってみてはいたのですが、どうしてもツリービューのスクロールバーと関数を紐付けることが出来ずに困っておりました。 不躾かとは思いますが、もしよろしければツリービューの場合、どのようなコードで動くのか教えていただけませんでしょうか? また、教えていただきましたGUIやライブラリも、調べてみようと思います!
teamikl

2021/02/23 04:27 編集

分量的に解説は難しいですが、とりあえず自分でもやってみたところまでコードを提示します。 Model クラスは、Qt のものを参考にしました。 https://repl.it/@MiKLTea/TkTableView スクロールの扱いは難しい所で、操作により引数が変化します(see _yview メソッド) ”moveto" 0.0 ~ 1.0 の範囲 (0.0 が一番上、1.0 が一番下、範囲外までドラッグすると 0以下や1以上) "scroll" 1 or -1 "units" (スクロールバー上下の矢印をクリックした時) "scroll" 1 or -1 "pages" (スクロールバー外をクリックした時) ツリービューの場合、 - 要素のクリア tree.delete(*tree.get_children())  この後新たに insert する。(see _update_view メソッド) - 表示件数は tree["height"] で行数を指定。 スクロールを実装する前に、まずはツリービューの表示件数を固定にして、 次の/前の~件を表示機能をボタン等で実装してみましょう。
hijiri

2021/02/23 13:54

わざわざありがとうございます。 教えていただきましたように、まずはボタンの切り替えにて表示する形で考えてみたいと思います。 そのあとに、ぼちぼちと教えていただいたスクロールでの扱いを勉強していきたいと思います。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問