我想使用 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
所以我的问题是:
- 如何将数据添加到具有不同层级中不同列数的 gtk TreeStore
- 此外,是否可以在 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()
结果:
在单元格中伪造标题
现在让我们使用生成的模型在每个单元格中添加某种标题 所需的数据。 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)
...
结果:
使用 set_cell_data_func
或 TreeModelFilter
如果您设法将数据放入 ListStore 或 TreeStore,那么 就是说你找到一个技巧让 parent 和 child 分享相同的金额 和列的类型,然后您可以使用 GtkTreeCellDataFunc 或 GtkTreeModelFilter .
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 导致几乎相同的事情。结果是一样的 比在单元格中伪造标题(除了我忘记将标题设置为不可见):
我希望这对你和其他有同样问题的人有帮助!
关于python - 将不同长度的列添加到 gtk TreeStore(Treeview),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28856425/