我正在将Gtk.TreeView
和Gtk.TreeStore
用作分层数据的模型。例如,让我们将音乐数据库分为三个级别:<艺术家> / 专辑 / 标题。我想使用文本搜索字段过滤这棵树。例如,在搜索字段中输入“五”应沿路径“汉克·马文/心跳/五音”给出结果。
我的理解是,我需要创建一个回调函数并使用Gtk.TreeModelFilter.set_visible_func()
注册它。问题在于,使“五个孩子”这一行可见并不足以使其显示出来,我还必须将其所有父项都设置为可见。但是,这将需要我遍历该树直至其根,并主动使该节点沿该路径可见,这不适合回调模式。
我看到使这种逻辑与回调模式一起使用的一种方法是检查回调函数中的整个子树,但是这样每个叶子节点将被检查3次。即使使用这样一棵浅树也可以接受性能下降,但这种hack给我带来了鸡皮ump,我想避免使用它:
def visible_callback(self, model, iter, _data=None):
search_query = self.entry.get_text().lower()
if search_query == "":
return True
text = model.get_value(iter, 0).lower()
if search_query in text:
return True
# Horrible hack
for i in range(model.iter_n_children(iter)):
if self.visible_callback(model, model.iter_nth_child(iter, i)):
return True
return False
在GTK中过滤树视图的预期方法是什么? (我的示例是用Python编写的,但是可以解决GTK的任何语言绑定问题。)
答案 0 :(得分:1)
最后,我想出了一个解决方案,由于我在互联网上没有找到任何使用TreeStore
而不是ListStore
的树视图过滤示例,因此我将解决方案发布在此处例如:
#! /usr/bin/python
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('Pango', '1.0')
from gi.repository import Gtk
from gi.repository import Pango
from gi.repository import GLib
import signal
HIERARCHICAL_DATA = {
"Queen": {
"A Kind of Magic": [ "Who Wants to Live Forever", "A Kind of Magic" ],
"The Miracle": [ "Breakthru", "Scandal" ]
},
"Five Finger Death Punch": {
"The Way of the Fist": [ "The Way of the Fist", "The Bleeding" ],
},
"Hank Marvin": {
"Heartbeat": [ "Oxygene (Part IV)", "Take Five" ]
}
}
ICONS = [ "stock_people", "media-optical", "sound" ]
class TreeViewFilteringDemo(Gtk.Window):
EXPAND_BY_DEFAULT = True
SPACING = 10
# Controls whether the row should be visible
COL_VISIBLE = 0
# Text to be displayed
COL_TEXT = 1
# Desired weight of the text (bold for matching rows)
COL_WEIGHT = 2
# Icon to be displayed
COL_ICON = 3
def __init__(self):
# Set up window
Gtk.Window.__init__(self, title="TreeView filtering demo")
self.set_size_request(500, 500)
self.set_position(Gtk.WindowPosition.CENTER)
self.set_resizable(True)
self.set_border_width(self.SPACING)
# Set up and populate a tree store
self.tree_store = Gtk.TreeStore(bool, str, Pango.Weight, str)
self.add_nodes(HIERARCHICAL_DATA, None, 0)
# Create some boxes for laying out the different controls
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=self.SPACING)
vbox.set_homogeneous(False)
hbox = Gtk.Box(Gtk.Orientation.HORIZONTAL, spacing=self.SPACING)
hbox.set_homogeneous(False)
vbox.pack_start(hbox, False, True, 0)
self.add(vbox)
# A text entry for filtering
self.search_entry = Gtk.Entry()
self.search_entry.set_placeholder_text("Enter text here to filter results")
self.search_entry.connect("changed", self.refresh_results)
hbox.pack_start(self.search_entry, True, True, 0)
# Add a checkbox for controlling subtree display
self.subtree_checkbox = Gtk.CheckButton("Show subtrees of matches")
self.subtree_checkbox.connect("toggled", self.refresh_results)
hbox.pack_start(self.subtree_checkbox, False, False, 0)
# Use an internal column for filtering
self.filter = self.tree_store.filter_new()
self.filter.set_visible_column(self.COL_VISIBLE)
self.treeview = Gtk.TreeView(model=self.filter)
# CellRenderers for icons and texts
icon_renderer = Gtk.CellRendererPixbuf()
text_renderer = Gtk.CellRendererText()
# Put the icon and the text into a single column (otherwise only the
# first column would be indented according to its depth in the tree)
col_combined = Gtk.TreeViewColumn("Icon and Text")
col_combined.pack_start(icon_renderer, False)
col_combined.pack_start(text_renderer, False)
col_combined.add_attribute(text_renderer, "text", self.COL_TEXT)
col_combined.add_attribute(text_renderer, "weight", self.COL_WEIGHT)
col_combined.add_attribute(icon_renderer, "icon_name", self.COL_ICON)
self.treeview.append_column(col_combined)
# Scrolled Window in case results don't fit in the available space
self.sw = Gtk.ScrolledWindow()
self.sw.add(self.treeview)
vbox.pack_start(self.sw, True, True, 0)
# Initialize filtering
self.refresh_results()
def add_nodes(self, data, parent, level):
"Create the tree nodes from a hierarchical data structure"
if isinstance(data, dict):
for key, value in data.items():
child = self.tree_store.append(parent, [True, key, Pango.Weight.NORMAL, ICONS[level]])
self.add_nodes(value, child, level + 1)
else:
for text in data:
self.tree_store.append(parent, [True, text, Pango.Weight.NORMAL, ICONS[level]])
def refresh_results(self, _widget = None):
"Apply filtering to results"
search_query = self.search_entry.get_text().lower()
show_subtrees_of_matches = self.subtree_checkbox.get_active()
if search_query == "":
self.tree_store.foreach(self.reset_row, True)
if self.EXPAND_BY_DEFAULT:
self.treeview.expand_all()
else:
self.treeview.collapse_all()
else:
self.tree_store.foreach(self.reset_row, False)
self.tree_store.foreach(self.show_matches, search_query, show_subtrees_of_matches)
self.treeview.expand_all()
self.filter.refilter()
def reset_row(self, model, path, iter, make_visible):
"Reset some row attributes independent of row hierarchy"
self.tree_store.set_value(iter, self.COL_WEIGHT, Pango.Weight.NORMAL)
self.tree_store.set_value(iter, self.COL_VISIBLE, make_visible)
def make_path_visible(self, model, iter):
"Make a row and its ancestors visible"
while iter:
self.tree_store.set_value(iter, self.COL_VISIBLE, True)
iter = model.iter_parent(iter)
def make_subtree_visible(self, model, iter):
"Make descendants of a row visible"
for i in range(model.iter_n_children(iter)):
subtree = model.iter_nth_child(iter, i)
if model.get_value(subtree, self.COL_VISIBLE):
# Subtree already visible
continue
self.tree_store.set_value(subtree, self.COL_VISIBLE, True)
self.make_subtree_visible(model, subtree)
def show_matches(self, model, path, iter, search_query, show_subtrees_of_matches):
text = model.get_value(iter, self.COL_TEXT).lower()
if search_query in text:
# Highlight direct match with bold
self.tree_store.set_value(iter, self.COL_WEIGHT, Pango.Weight.BOLD)
# Propagate visibility change up
self.make_path_visible(model, iter)
if show_subtrees_of_matches:
# Propagate visibility change down
self.make_subtree_visible(model, iter)
return
win = TreeViewFilteringDemo()
win.connect("delete-event", Gtk.main_quit)
win.show_all()
# Make sure that the application can be stopped from the terminal using Ctrl-C
GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, Gtk.main_quit)
Gtk.main()