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

时间:2015-03-04 14:02:38

标签: 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中每行的列名称
  3. 即在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
    

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

1 个答案:

答案 0 :(得分:6)

简短答案和简介

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

简单:你不能。 GtkListStore以及GtkTreeStore旨在容纳 数据作为表格。列以固定方式定义,具有索引和数据 类型。 ListStore和TreeStore之间的唯一区别在于 TreeStore,行具有层次结构。更糟糕的是,GtkTreeView小部件也是如此 期望数据存储为表,因为每行都将无条件地获取 使用列索引的单元格,并希望在那里找到一些东西。 除非您编写自己的小部件,but you probably don't want to (上帝,这个文件长16570行......)。

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

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

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

基本

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

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

父母将使用所有五列,而孩子将仅使用第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,那么 就是说你找到了一个技巧,以便父母和孩子分享相同的金额 和列的类型,然后您可以使用a来操作数据 GtkTreeCellDataFuncGtkTreeModelFilter

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

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

这是code using 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()

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

Faking headers with CellDataFunc

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