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

use*_*788 1 python gtk treeview 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
Run Code Online (Sandbox Code Playgroud)

数据层次结构如下:

> 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
Run Code Online (Sandbox Code Playgroud)

以下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()
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

所以我的问题是:

  1. 如何将数据添加到不同层次结构中具有不同列数的gtk TreeStore
  2. 此外,是否可以显示gtk树存储中每行的列名称

即在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
Run Code Online (Sandbox Code Playgroud)

如果使用树视图无法做到这一点,是否有任何替代方案/解决方法可用于实现上述目标?

Cil*_*yan 6

简短的回答和介绍

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

简单:你做不到.GtkListStore以及GtkTreeStore旨在将数据保存为表.列以固定方式定义,具有索引和数据类型.ListStore和TreeStore之间的唯一区别是在TreeStore中,行具有层次结构.更糟糕的是,GtkTreeView小部件还希望将数据存储为一个表,因为每一行都将无条件地使用其列索引获取单元格,并期望在那里找到一些东西.除非你编写自己的小部件,但你可能不想 (上帝,这个文件长16570行......).

但是,如果您无法编写自己的小部件,您仍然可以编写自己的模型.这会给你一些灵活性.

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

在TreeView中显示数据涉及两个组件:GtkTreeView本身,它在TreeStore中获取数据并显示它们.TreeView小部件没有为每行显示标题的功能.但是有一些技巧可以处理模型和视图之间的数据,这可能会达到预期的效果,尽管不是很好.

基本

因此,TreeView期望在数据表上工作,我们无法改变它.好.但我们仍然可以欺骗它认为数据是一个表,实际上它不是......让我们从视图开始.我们需要至少五列来显示父母的数据.然后,孩子们可以只使用这五个中的三列,所以这很好.

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

父母将使用所有五列,而孩子将仅使用第2,3和4列.当数据不可用于目标单元格时,我们将欺骗模型返回空文本.

创建一个新的TreeModel

本教程中提供了有关在PyGTK中实现自定义GtkTreeModel的一些解释.这是它的示例实现:

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()
Run Code Online (Sandbox Code Playgroud)

结果如下:

自定义GenericTreeModel

在单元格中伪造标题

现在让我们使用模型在每个单元格中添加某种标题,以生成所需的数据.完整代码在这里.

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)

...
Run Code Online (Sandbox Code Playgroud)

结果如下:

GenericTreeModel伪造标头

使用set_cell_data_funcTreeModelFilter

如果您设法将数据放入ListStore或TreeStore,也就是说您找到了一个技巧,以便父项和子项共享相同数量和类型的列,则可以使用GtkTreeCellDataFuncGtkTreeModelFilter来操作数据 .

PyGTK文档提供了单元数据函数树模型过滤器的示例.

例如,使用这些概念添加列标题可能比创建完整的自定义模型更容易.

这是使用TreeCellDataFunc代码.请注意数据输入是如何格式化的,以便子级和父级具有相同数量的数据.这是能够使用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()
Run Code Online (Sandbox Code Playgroud)

GtkTreeModelFilter导致了几乎相同的事情.结果与单元格中的伪标题相同(除了我忘记将标题设置为不可见):

使用CellDataFunc伪装标头

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