如何过滤使用TreeStore(而不是ListStore)的GTK树视图?

时间:2019-05-07 20:05:10

标签: gtk gtk3 pygtk gtktreeview

我正在将Gtk.TreeViewGtk.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的任何语言绑定问题。)

1 个答案:

答案 0 :(得分:1)

最后,我想出了一个解决方案,由于我在互联网上没有找到任何使用TreeStore而不是ListStore的树视图过滤示例,因此我将解决方案发布在此处例如:

Demo screenshot

#! /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()