python - 将不同长度的列添加到 gtk TreeStore(Treeview)

标签 python treeview gtk pygtk

我想使用 gtk Treeview(带有模型 gtk Treestore)显示两级分层数据

数据格式如下:

**First(parent)** level
col_a, col_b, col_c, col_d, col_e
val_a, val_b, val_c, val_d, val_e

**Second(child)** level
col_x, col_y, col_z
val_x, val_y, val_z

数据的层次结构如下:

> val_a1, val_b1, val_c1, val_d1, val_e1
       val_x1, val_y1, val_z1
       val_x2, val_y2, val_z2

> val_a2, val_b2, val_c2, val_s2, val_e2
       val_x3, val_y3, val_z3

> val_a3, val_b3, val_c3, val_d3, val_e3

> val_a4, val_b4, val_c4, val_d4, val_e4
       val_x4, val_y4, val_z4
       val_x5, val_y5, val_z5

下面的pygtk代码是我试过的(修改了gtk教程的代码)

import pygtk
pygtk.require('2.0')
import gtk

data = [
    [('val_a1', 'val_b1', 'val_c1', 'val_d1', 'val_e1'), ('val_x1', 'val_y1', 'val_z1'), ('val_x2', 'val_y2', 'val_z2')],
    [('val_a2', 'val_b2', 'val_c2', 'val_d2', 'val_e2'), ('val_x3', 'val_y3', 'val_z3')],
    [('val_a3', 'val_b3', 'val_c3', 'val_d3', 'val_e3')],
    [('val_a4', 'val_b4', 'val_c4', 'val_d4', 'val_e4'), ('val_x4', 'val_y4', 'val_z4'), ('val_x5', 'val_y5', 'val_z5')],
]

class BasicTreeViewExample:

    def delete_event(self, widget, event, data=None):
        gtk.main_quit()
        return False

    def __init__(self):
        self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
        self.window.set_title("Basic TreeView Example")
        self.window.set_size_request(200, 200)
        self.window.connect("delete_event", self.delete_event)
        self.treestore = gtk.TreeStore(str, str, str, str, str)
        for detail in data:
        for index, elem in enumerate(detail):
            if index == 0:
                piter = self.treestore.append(None, elem)
            else:
                self.treestore.append(piter, elem)

        self.treeview = gtk.TreeView(self.treestore)
        for i in range(5):
            tvcolumn = gtk.TreeViewColumn('Column %s' % (i))
            self.treeview.append_column(tvcolumn)
            cell = gtk.CellRendererText()
            tvcolumn.pack_start(cell, True)
            tvcolumn.add_attribute(cell, 'text', i)
        self.window.add(self.treeview)
        self.window.show_all()

def main():
    gtk.main()

if __name__ == "__main__":
    tvexample = BasicTreeViewExample()
    main()

但是,当我尝试运行上面的代码时出现以下错误:

Traceback (most recent call last):
  File "test.py", line 55, in <module>
    tvexample = BasicTreeViewExample()
  File "test.py", line 33, in __init__
    self.treestore.append(piter, detail[index])
ValueError: row sequence has wrong length

所以我的问题是:

  1. 如何将数据添加到具有不同层级中不同列数的 gtk TreeStore
  2. 此外,是否可以在 gtk treestore 中显示每一行的列名

即在 Treeview 中我想看到如下输出:

  col_a,  col_b,  col_c,  col_d,  col_e
> val_a1, val_b1, val_c1, val_d1, val_e1
       col_x,  col_y,  col_z
       val_x1, val_y1, val_z1

       col_x,  col_y,  col_z
       val_x2, val_y2, val_z2

  col_a,  col_b,  col_c,  col_d,  col_e
> val_a2, val_b2, val_c2, val_s2, val_e2
       col_x,  col_y,  col_z
       val_x3, val_y3, val_z3

  col_a,  col_b,  col_c,  col_d,  col_e
> val_a3, val_b3, val_c3, val_d3, val_e3

  col_a,  col_b,  col_c,  col_d,  col_e
> val_a4, val_b4, val_c4, val_d4, val_e4
       col_x, col_y, col_z
       val_x4, val_y4, val_z4

       col_x, col_y, col_z
       val_x5, val_y5, val_z5

如果使用 TreeView 无法做到这一点,是否有任何替代/变通方法可以实现上述目标?

最佳答案

简短的回答和介绍

如何向 gtk.TreeStore 添加不同层次结构中具有不同列数的数据?

很简单:你不能。 GtkListStore 和 GtkTreeStore 被设计用来保存 数据作为表格。列以固定方式定义,包含索引和数据 类型。 ListStore 和 TreeStore 之间的唯一区别是在 TreeStore,行具有层次结构。更糟糕的是,GtkTreeView 小部件还 期望数据存储为表,因为每一行都将无条件获取 使用它们的列索引的单元格,并期望在那里找到一些东西。 除非您编写自己的小部件,but you probably don't want to (天哪,这个文件有 16570 行长……)。

但是,如果您不能编写自己的小部件,您仍然可以编写自己的小部件 模型。这将为您提供一些灵 active 。

此外,是否可以显示 gtk.TreeStore 中每一行的列名?

在 TreeView 中显示数据涉及两个组件:GtkTreeView 本身, 获取 TreeStore 中的数据并显示它们。 TreeView 小部件没有 具有显示每行标题的功能。但是有一些技巧 处理模型和 View 之间的数据,这可能会以所需的方式结束 效果,虽然可能不是那么好。

基础知识

因此,TreeView 期望在数据表上工作,我们无法更改它。 好的。但我们仍然可以欺骗它,让它认为数据是一张表,而实际上 它不是...让我们从 View 开始。我们至少需要五列来显示 parent 的数据。然后 children 只能使用其中的三列 这五个,这样就可以了。

请注意,模型的列并不总是映射到 TreeView 中的列。 它们实际上映射到单元格渲染器的某些属性。例如,您可以 在模型中有一列定义行的背景颜色, 或定义要显示的图标的列。 View 中的列只是一种方式 对齐单元格渲染器组,可能在标题下。但是在这里,让我们 假设所有值都是文本,应该在其 自己的专栏。

parent 将使用所有五列,而 child 将仅使用第 2、3 列 和 4. 然后我们将欺骗模型在数据不存在时返回空文本 可用于目标细胞。

创建一个新的 TreeModel

关于在 PyGTK 中实现自定义 GtkTreeModel 的一些解释是 可用 this tutorial . 这是 sample implementation of it :

import pygtk
pygtk.require('2.0')
import gtk

data = [
    [('val_a1', 'val_b1', 'val_c1', 'val_d1', 'val_e1'), ('val_x1', 'val_y1', 'val_z1'), ('val_x2', 'val_y2', 'val_z2')],
    [('val_a2', 'val_b2', 'val_c2', 'val_d2', 'val_e2'), ('val_x3', 'val_y3', 'val_z3')],
    [('val_a3', 'val_b3', 'val_c3', 'val_d3', 'val_e3')],
    [('val_a4', 'val_b4', 'val_c4', 'val_d4', 'val_e4'), ('val_x4', 'val_y4', 'val_z4'), ('val_x5', 'val_y5', 'val_z5')],
]

class MyTreeModel(gtk.GenericTreeModel):

    # The columns exposed by the model to the view
    column_types = (str, str, str, str, str)

    def __init__(self, data):
        gtk.GenericTreeModel.__init__(self)
        self.data = data

    def on_get_flags(self):
        """
            Get Model capabilities
        """
        return gtk.TREE_MODEL_ITERS_PERSIST

    def on_get_n_columns(self):
        """
            Get number of columns in the model
        """
        return len(self.column_types)

    def on_get_column_type(self, n):
        """
            Get data type of a specified column in the model
        """
        return self.column_types[n]

    def on_get_iter(self, path):
        """
            Obtain a reference to the row at path. For us, this is a tuple that
            contain the position of the row in the double list of data.
        """
        if len(path) > 2:
            return None # Invalid path
        parent_idx = path[0]
        if parent_idx >= len(self.data):
            return None # Invalid path
        first_level_list = self.data[parent_idx]
        if len(path) == 1:
            # Access the parent at index 0 in the first level list
            return (parent_idx, 0)
        else:
            # Access a child, at index path[1] + 1 (0 is the parent)
            child_idx = path[1] + 1
            if child_idx >= len(first_level_list):
                return None # Invalid path
            else:
                return (parent_idx, child_idx)

    def on_get_path(self, iter_):
        """
            Get a path from a rowref (this is the inverse of on_get_iter)
        """
        parent_idx, child_idx = iter_
        if child_idx == 0:
            return (parent_idx, )
        else:
            (parent_idx, child_idx-1)

    def on_get_value(self, iter_, column):
        """
            This is where the view asks for values. This is thus where we
            start mapping our data model to a fake table to present to the view
        """
        parent_idx, child_idx = iter_
        item = self.data[parent_idx][child_idx]
        # For parents, map columns 1:1 to data
        if child_idx == 0:
            return item[column]
        # For children, we have to fake some columns
        else:
            if column == 0 or column == 4:
                return "" # Fake empty text
            else:
                return item[column-1] # map 1, 2, 3 to 0, 1, 2.

    def on_iter_next(self, iter_):
        """
            Get the next sibling of the item pointed by iter_
        """
        parent_idx, child_idx = iter_
        # For parents, point to the next parent
        if child_idx == 0:
            next_parent_idx = parent_idx + 1
            if next_parent_idx < len(self.data):
                return (next_parent_idx, 0)
            else:
                return None
        # For children, get next tuple in the list
        else:
            next_child_idx = child_idx + 1
            if next_child_idx < len(self.data[parent_idx]):
                return (parent_idx, next_child_idx)
            else:
                return None

    def on_iter_has_child(self, iter_):
        """
            Tells if the row referenced by iter_ has children
        """
        parent_idx, child_idx = iter_
        if child_idx == 0 and len(self.data[parent_idx]) > 1:
            return True
        else:
            return False

    def on_iter_children(self, iter_):
        """
            Return a row reference to the first child row of the row specified
            by iter_. If iter_ is None, a reference to the first top level row
            is returned. If there is no child row None is returned.
        """
        if iter_ is None:
            return (0, 0)
        parent_idx, child_idx = iter_
        if self.on_iter_has_child(iter_):
            return (parent_idx, 1)
        else:
            return None

    def on_iter_n_children(self, iter_):
        """
            Return the number of child rows that the row specified by iter_
            has. If iter_ is None, the number of top level rows is returned.
        """
        if iter_ is None:
            return len(self.data)
        else:
            parent_idx, child_idx = iter_
            if child_idx == 0:
                return len(self.data[parent_idx]) - 1
            else:
                return 0

    def on_iter_nth_child(self, iter_, n):
        """
            Return a row reference to the nth child row of the row specified by
            iter_. If iter_ is None, a reference to the nth top level row is
            returned.
        """
        if iter_ is None:
            if n < len(self.data):
                return (n, 0)
            else:
                return None
        else:
            parent_idx, child_idx = iter_
            if child_idx == 0:
                if n+1 < len(self.data[parent_idx]):
                    return (parent_idx, n+1)
                else:
                    return None
            else:
                return None

    def on_iter_parent(self, iter_):
        """
            Get a reference to the parent
        """
        parent_idx, child_idx = iter_
        if child_idx == 0:
            return None
        else:
            return (parent_idx, 0)

class BasicTreeViewExample:

    def delete_event(self, widget, event, data=None):
        gtk.main_quit()
        return False

    def __init__(self):
        self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
        self.window.set_title("Basic TreeView Example")
        self.window.set_size_request(200, 200)
        self.window.connect("delete_event", self.delete_event)
        # Create the model with data in it
        self.model = MyTreeModel(data)
        self.treeview = gtk.TreeView(self.model)
        for i in range(5):
            tvcolumn = gtk.TreeViewColumn('Column %s' % (i))
            self.treeview.append_column(tvcolumn)
            cell = gtk.CellRendererText()
            tvcolumn.pack_start(cell, True)
            tvcolumn.add_attribute(cell, 'text', i)
        self.window.add(self.treeview)
        self.window.show_all()

def main():
    gtk.main()

if __name__ == "__main__":
    tvexample = BasicTreeViewExample()
    main()

结果:

Custom GenericTreeModel

在单元格中伪造标题

现在让我们使用生成的模型在每个单元格中添加某种标题 所需的数据。 Full code is here .

class MyTreeModel(gtk.GenericTreeModel):

    # The columns exposed by the model to the view
    column_types = (str, str, str, str, str)
    # Column headers
    parent_headers = ("P.Col 1", "P.Col 2", "P.Col 3", "P.Col 4", "P.Col 5")
    child_headers = ("C.Col 1", "C.Col 2", "C.Col 3")

    ...

    def on_get_value(self, iter_, column):
        """
            This is where the view asks for values. This is thus where we
            start mapping our data model to a fake table to present to the view
        """
        parent_idx, child_idx = iter_
        item = self.data[parent_idx][child_idx]
        # For parents, map columns 1:1 to data
        if child_idx == 0:
            return self.markup(item[column], column, False)
        # For children, we have to fake some columns
        else:
            if column == 0 or column == 4:
                return "" # Fake empty text
            else:
                # map 1, 2, 3 to 0, 1, 2.
                return self.markup(item[column-1], column-1, True)

    def markup(self, text, column, is_child):
        """
            Produce a markup for a cell with a title and a text
        """
        headers = self.child_headers if is_child else self.parent_headers
        title = headers[column]
        return "<b>%s</b>\n%s"%(title, text)

    ...

class BasicTreeViewExample:

    def __init__(self):
        ...
        self.treeview = gtk.TreeView(self.model)
        self.treeview.set_headers_visible(False)
        for i in range(5):
            ...
            tvcolumn.pack_start(cell, True)
            tvcolumn.add_attribute(cell, 'markup', i)

...

结果:

GenericTreeModel faking headers

使用 set_cell_data_funcTreeModelFilter

如果您设法将数据放入 ListStore 或 TreeStore,那么 就是说你找到一个技巧让 parent 和 child 分享相同的金额 和列的类型,然后您可以使用 GtkTreeCellDataFuncGtkTreeModelFilter .

PyGTK 文档提供了 Cell Data Functions 的示例 和 Tree Model Filters .

例如,使用这些概念添加列标题可能比 创建一个完整的自定义模型。

这是 code using TreeCellDataFunc . 请注意数据输入是如何格式化的,以便 child 和 parent 能够 相同数量的数据。这是能够使用 GtkTreeStore 的条件。

import pygtk
pygtk.require('2.0')
import gtk

data = [
    [('val_a1', 'val_b1', 'val_c1', 'val_d1', 'val_e1'), ('', 'val_x1', 'val_y1', 'val_z1', ''), ('', 'val_x2', 'val_y2', 'val_z2', '')],
    [('val_a2', 'val_b2', 'val_c2', 'val_d2', 'val_e2'), ('', 'val_x3', 'val_y3', 'val_z3', '')],
    [('val_a3', 'val_b3', 'val_c3', 'val_d3', 'val_e3')],
    [('val_a4', 'val_b4', 'val_c4', 'val_d4', 'val_e4'), ('', 'val_x4', 'val_y4', 'val_z4', ''), ('', 'val_x5', 'val_y5', 'val_z5', '')],
]

class BasicTreeViewExample:

    parent_headers = ("P.Col 1", "P.Col 2", "P.Col 3", "P.Col 4", "P.Col 5")
    child_headers = ("C.Col 1", "C.Col 2", "C.Col 3")

    def delete_event(self, widget, event, data=None):
        gtk.main_quit()
        return False

    def __init__(self):
        self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
        self.window.set_title("Basic TreeView Example")
        self.window.set_size_request(200, 200)
        self.window.connect("delete_event", self.delete_event)
        self.treestore = gtk.TreeStore(str, str, str, str, str)
        for detail in data:
            for index, elem in enumerate(detail):
                if index == 0:
                    piter = self.treestore.append(None, elem)
                else:
                    self.treestore.append(piter, elem)

        self.treeview = gtk.TreeView(self.treestore)
        for i in range(5):
            tvcolumn = gtk.TreeViewColumn('Column %s' % (i))
            self.treeview.append_column(tvcolumn)
            cell = gtk.CellRendererText()
            tvcolumn.pack_start(cell, True)
            # Delegate data fetching to callback
            tvcolumn.set_cell_data_func(cell, self.cell_add_header, i)
        self.window.add(self.treeview)
        self.window.show_all()

    def cell_add_header(self, treeviewcolumn, cell, model, iter_, column):
        text = model.get_value(iter_, column)
        if model.iter_parent(iter_) is None:
            # This is a parent
            title = self.parent_headers[column]
            markup = "<b>%s</b>\n%s"%(title, text)
        else:
            # We have a child
            if column == 0 or column == 4:
                # Cell is not used by child, leave it empty
                markup = ""
            else:
                title = self.child_headers[column-1]
                markup = "<b>%s</b>\n%s"%(title, text)
        cell.set_property('markup', markup)

def main():
    gtk.main()

if __name__ == "__main__":
    tvexample = BasicTreeViewExample()
    main()

GtkTreeModelFilter 导致几乎相同的事情。结果是一样的 比在单元格中伪造标题(除了我忘记将标题设置为不可见):

Faking headers with CellDataFunc

我希望这对你和其他有同样问题的人有帮助!

关于python - 将不同长度的列添加到 gtk TreeStore(Treeview),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28856425/

相关文章:

c - 如何在 gtk 中刷新图像?

c - 如何禁用 GtkColorButton?

c - GTK+ 文档拼写错误?对 gtk_combo_box_text_append 的 undefined reference

python - 如何修改 tf.nn.embedding_lookup() 的返回张量?

python - 导入错误 : cannot import name 'app'

c# - 访问 TreeView 中的父节点?

asp.net - 如何将 SiteMap 绑定(bind)到动态创建的 TreeView?

tkinter - 从 tkinter 中的 TreeView 获取数据

python - 在 macOS 上将 Python 3 安装到/usr/bin/

python - 每次运行python代码时都必须导入excel文件吗?