13. Widgets de árvore e lista
A Gtk.TreeView
e seus widgets associados são uma maneira extremamente poderosa de exibir dados. Eles são usados em conjunto com um Gtk.ListStore
ou Gtk.TreeStore
e fornecem uma maneira de exibir e manipular dados de várias maneiras, incluindo:
Atualizações automáticas quando os dados são adicionados, removidos ou editados
Suporte a arrastar e soltar
Ordenação de dados
Incorporação de widgets, como caixas de seleção, barras de progresso, etc.
Colunas reordenáveis e redimensionáveis
Filtragem de dados
Com o poder e a flexibilidade de um Gtk.TreeView
vem a complexidade. Geralmente, é difícil para os desenvolvedores iniciantes serem capazes de utilizá-lo corretamente devido ao número de métodos necessários.
13.1. O modelo
Cada Gtk.TreeView
possui um Gtk.TreeModel
, o qual contém os dados exibidos pelo TreeView. Cada Gtk.TreeModel
pode ser usado por mais de um Gtk.TreeView
. Por exemplo, isso permite que os mesmos dados subjacentes sejam exibidos e editados de duas maneiras diferentes ao mesmo tempo. Ou os 2 modos de exibição podem exibir colunas diferentes dos mesmos dados do modelo, da mesma forma que duas consultas SQL (ou “views”) podem mostrar campos diferentes da mesma tabela de banco de dados.
Embora você possa teoricamente implementar seu próprio Model, você normalmente usará as classes de modelo Gtk.ListStore
ou Gtk.TreeStore
. Gtk.ListStore
contém linhas simples de dados, e cada linha não tem filhos, enquanto Gtk.TreeStore
contém linhas de dados, e cada linha pode ter linhas filhas.
Ao construir um modelo, você deve especificar os tipos de dados para cada coluna que o modelo contém.
store = Gtk.ListStore(str, str, float)
Isso cria um armazenamento de lista com três colunas, duas colunas de string e uma coluna flutuante.
A adição de dados ao modelo é feita usando Gtk.ListStore.append()
ou Gtk.TreeStore.append()
, dependendo de qual tipo de modelo foi criado.
Para um Gtk.ListStore
:
treeiter = store.append(["The Art of Computer Programming",
"Donald E. Knuth", 25.46])
Para um Gtk.TreeStore
você deve especificar uma linha existente para anexar a nova linha, usando um Gtk.TreeIter
, ou None para o nível superior da árvore:
treeiter = store.append(None, ["The Art of Computer Programming",
"Donald E. Knuth", 25.46])
Ambos os métodos retornam uma instância Gtk.TreeIter
, que aponta para a localização da linha recém-inserida. Você pode recuperar um Gtk.TreeIter
chamando Gtk.TreeModel.get_iter()
.
Depois que os dados foram inseridos, você pode recuperar ou modificar dados usando o iterador de árvore e o índice de coluna.
print(store[treeiter][2]) # Prints value of third column
store[treeiter][2] = 42.15
Assim como no objeto interno list
do Python, você pode usar len()
para obter o número de linhas e usar fatias para recuperar ou definir valores.
# Print number of rows
print(len(store))
# Print all but first column
print(store[treeiter][1:])
# Print last column
print(store[treeiter][-1])
# Set last two columns
store[treeiter][1:] = ["Donald Ervin Knuth", 41.99]
Iterar sobre todas as linhas de um modelo de árvore é muito simples também.
for row in store:
# Print values of all columns
print(row[:])
Tenha em mente que, se você usar Gtk.TreeStore
, o código acima irá apenas iterar sobre as linhas do nível superior, mas não os filhos dos nós. Para iterar sobre todas as linhas, use Gtk.TreeModel.foreach()
.
def print_row(store, treepath, treeiter):
print("\t" * (treepath.get_depth() - 1), store[treeiter][:], sep="")
store.foreach(print_row)
Além de acessar valores armazenados em um Gtk.TreeModel
com o método list-like mencionado acima, também é possível usar as instâncias Gtk.TreeIter
ou Gtk.TreePath
. Ambos fazem referência a uma linha específica em um modelo de árvore. Pode-se converter um caminho para um iterador chamando Gtk.TreeModel.get_iter()
. Como Gtk.ListStore
contém apenas um nível, ou seja, nós não têm nenhum nó filho, um caminho é essencialmente o índice da linha que você deseja acessar.
# Get path pointing to 6th row in list store
path = Gtk.TreePath(5)
treeiter = liststore.get_iter(path)
# Get value at 2nd column
value = liststore.get_value(treeiter, 1)
No caso de Gtk.TreeStore
, um caminho é uma lista de índices ou uma string. O formulário de string é uma lista de números separados por dois pontos. Cada número refere-se ao deslocamento nesse nível. Assim, o caminho “0” refere-se ao nó raiz e o caminho “2:4” refere-se ao quinto filho do terceiro nó.
# Get path pointing to 5th child of 3rd row in tree store
path = Gtk.TreePath([2, 4])
treeiter = treestore.get_iter(path)
# Get value at 2nd column
value = treestore.get_value(treeiter, 1)
Instâncias de Gtk.TreePath
podem ser acessadas como listas, len(treepath)
retorna a profundidade do item treepath
está apontando para, e treepath[i]
retorna o índice do filho no nível i.
13.2. A visão
Embora existam vários modelos diferentes para escolher, há apenas um widget de visualização para lidar. Funciona com a lista ou com o armazenamento em árvore. Configurar um Gtk.TreeView
não é uma tarefa difícil. Ele precisa de um Gtk.TreeModel
para saber de onde recuperar seus dados, seja passando-o para o construtor Gtk.TreeView
, ou chamando Gtk.TreeView.set_model()
.
tree = Gtk.TreeView(model=store)
Uma vez que o widget Gtk.TreeView
possua um modelo, ele precisará saber como exibir o modelo. Ele faz isso com colunas e renderizadores de célula. headers_visible
controla se exibe cabeçalhos de coluna.
Os renderizadores de célula são usados para desenhar os dados no modelo de árvore de uma maneira específica. Existem vários renderizadores de célula que vêm com o GTK+, por exemplo Gtk.CellRendererText
, Gtk.CellRendererPixbuf
e Gtk.CellRendererToggle
. Além disso, é relativamente fácil escrever um renderizador personalizado criando uma subclasse de Gtk.CellRenderer
e adicionando propriedades com GObject.Property()
.
Um Gtk.TreeViewColumn
é o objeto que usa Gtk.TreeView
para organizar as colunas verticais na visualização em árvore e conter um ou mais renderizadores de células. Cada coluna pode ter um title
que ficará visível se o Gtk.TreeView
estiver mostrando os cabeçalhos das colunas. O modelo é mapeado para a coluna usando argumentos nomeados com propriedades do renderizador como identificadores e índices das colunas do modelo como argumentos.
renderer = Gtk.CellRendererPixbuf()
column = Gtk.TreeViewColumn(cell_renderer=renderer, icon_name=3)
tree.append_column(column)
Argumentos posicionais podem ser usados para o título da coluna e o renderizador.
renderer = Gtk.CellRendererText()
column = Gtk.TreeViewColumn("Title", renderer, text=0, weight=1)
tree.append_column(column)
Para renderizar mais de uma coluna de modelo em uma coluna de visão, você precisa criar uma instância Gtk.TreeViewColumn
e usar Gtk.TreeViewColumn.pack_start()
para adicionar as colunas de modelo a ela.
column = Gtk.TreeViewColumn("Title and Author")
title = Gtk.CellRendererText()
author = Gtk.CellRendererText()
column.pack_start(title, True)
column.pack_start(author, True)
column.add_attribute(title, "text", 0)
column.add_attribute(author, "text", 1)
tree.append_column(column)
13.3. A seleção
A maioria dos aplicativos precisará não apenas lidar com a exibição de dados, mas também receber eventos de entrada dos usuários. Para fazer isso, basta obter uma referência a um objeto de seleção e conectar-se ao sinal “changed”.
select = tree.get_selection()
select.connect("changed", on_tree_selection_changed)
Em seguida, para recuperar dados para a linha selecionada:
def on_tree_selection_changed(selection):
model, treeiter = selection.get_selected()
if treeiter is not None:
print("You selected", model[treeiter][0])
Você pode controlar quais seleções são permitidas chamando Gtk.TreeSelection.set_mode()
. Gtk.TreeSelection.get_selected()
não funciona se o modo de seleção estiver definido como Gtk.SelectionMode.MULTIPLE
, use Gtk.TreeSelection.get_selected_rows()
.
13.4. Classificação
A classificação é um recurso importante para as visualizações em árvore e é suportada pelos modelos de árvore padrão (Gtk.TreeStore
e Gtk.ListStore
), que implementam a interface Gtk.TreeSortable
.
13.4.1. Classificando clicando em colunas
Uma coluna de um Gtk.TreeView
pode ser facilmente ordenada com uma chamada para Gtk.TreeViewColumn.set_sort_column_id()
. Depois, a coluna pode ser ordenada clicando no cabeçalho.
Primeiro precisamos de um simples Gtk.TreeView
e um Gtk.ListStore
como modelo.
model = Gtk.ListStore(str)
model.append(["Benjamin"])
model.append(["Charles"])
model.append(["alfred"])
model.append(["Alfred"])
model.append(["David"])
model.append(["charles"])
model.append(["david"])
model.append(["benjamin"])
treeView = Gtk.TreeView(model=model)
cellRenderer = Gtk.CellRendererText()
column = Gtk.TreeViewColumn("Title", renderer, text=0)
O próximo passo é ativar a classificação. Note que o column_id (0
no exemplo) refere-se à coluna do modelo e não à coluna do TreeView.
column.set_sort_column_id(0)
13.4.2. Definindo uma função de classificação personalizada
Também é possível definir uma função de comparação personalizada para alterar o comportamento de classificação. Como exemplo, criaremos uma função de comparação que classifica maiúsculas e minúsculas. No exemplo acima, a lista classificada parecia com:
alfred
Alfred
benjamin
Benjamin
charles
Charles
david
David
A lista classificada com distinção entre maiúsculas e minúsculas será semelhante a:
Alfred
Benjamin
Charles
David
alfred
benjamin
charles
david
Em primeiro lugar, é necessária uma função de comparação. Esta função obtém duas linhas e tem que retornar um inteiro negativo se o primeiro deve vir antes do segundo, zero se eles forem iguais e um inteiro positivo se o segundo vier antes do primeiro.
def compare(model, row1, row2, user_data):
sort_column, _ = model.get_sort_column_id()
value1 = model.get_value(row1, sort_column)
value2 = model.get_value(row2, sort_column)
if value1 < value2:
return -1
elif value1 == value2:
return 0
else:
return 1
Então a função sort deve ser definida por Gtk.TreeSortable.set_sort_func()
.
model.set_sort_func(0, compare, None)
13.5. Filtragem
Ao contrário da classificação, a filtragem não é tratada pelos dois modelos que vimos anteriormente, mas pela classe Gtk.TreeModelFilter
. Esta classe, como Gtk.TreeStore
e Gtk.ListStore
, é uma Gtk.TreeModel
. Ele age como uma camada entre o modelo “real” (a Gtk.TreeStore
ou a Gtk.ListStore
), ocultando alguns elementos para a view. Na prática, ele fornece o Gtk.TreeView
com um subconjunto do modelo subjacente. Instâncias de Gtk.TreeModelFilter
podem ser empilhadas umas sobre as outras, para usar múltiplos filtros no mesmo modelo (da mesma forma que você usaria cláusulas “AND” em uma requisição SQL). Eles também podem ser encadeados com instâncias Gtk.TreeModelSort
.
Você pode criar uma nova instância de Gtk.TreeModelFilter
e dar a ela um modelo para filtrar, mas a maneira mais fácil é gerá-lo diretamente do modelo filtrado, usando o método Gtk.TreeModel.filter_new()
método.
filter = model.filter_new()
Da mesma forma que funciona a função de classificação, o Gtk.TreeModelFilter
usa de uma função “visibility”, que, dada uma linha do modelo subjacente, retornará um booleano indicando se essa linha deve ser filtrada ou não. É definido por Gtk.TreeModelFilter.set_visible_func()
:
filter.set_visible_func(filter_func, data=None)
A alternativa para uma função de “visibilidade” é usar uma coluna booleana no modelo para especificar quais linhas filtrar. Escolha qual coluna com Gtk.TreeModelFilter.set_visible_column()
.
Vejamos um exemplo completo que usa a pilha inteira Gtk.ListStore
– Gtk.TreeModelFilter
– Gtk.TreeModelFilter
— Gtk.TreeView
.
1import gi
2
3gi.require_version("Gtk", "3.0")
4from gi.repository import Gtk
5
6# list of tuples for each software, containing the software name, initial release, and main programming languages used
7software_list = [
8 ("Firefox", 2002, "C++"),
9 ("Eclipse", 2004, "Java"),
10 ("Pitivi", 2004, "Python"),
11 ("Netbeans", 1996, "Java"),
12 ("Chrome", 2008, "C++"),
13 ("Filezilla", 2001, "C++"),
14 ("Bazaar", 2005, "Python"),
15 ("Git", 2005, "C"),
16 ("Linux Kernel", 1991, "C"),
17 ("GCC", 1987, "C"),
18 ("Frostwire", 2004, "Java"),
19]
20
21
22class TreeViewFilterWindow(Gtk.Window):
23 def __init__(self):
24 super().__init__(title="Treeview Filter Demo")
25 self.set_border_width(10)
26
27 # Setting up the self.grid in which the elements are to be positioned
28 self.grid = Gtk.Grid()
29 self.grid.set_column_homogeneous(True)
30 self.grid.set_row_homogeneous(True)
31 self.add(self.grid)
32
33 # Creating the ListStore model
34 self.software_liststore = Gtk.ListStore(str, int, str)
35 for software_ref in software_list:
36 self.software_liststore.append(list(software_ref))
37 self.current_filter_language = None
38
39 # Creating the filter, feeding it with the liststore model
40 self.language_filter = self.software_liststore.filter_new()
41 # setting the filter function, note that we're not using the
42 self.language_filter.set_visible_func(self.language_filter_func)
43
44 # creating the treeview, making it use the filter as a model, and adding the columns
45 self.treeview = Gtk.TreeView(model=self.language_filter)
46 for i, column_title in enumerate(
47 ["Software", "Release Year", "Programming Language"]
48 ):
49 renderer = Gtk.CellRendererText()
50 column = Gtk.TreeViewColumn(column_title, renderer, text=i)
51 self.treeview.append_column(column)
52
53 # creating buttons to filter by programming language, and setting up their events
54 self.buttons = list()
55 for prog_language in ["Java", "C", "C++", "Python", "None"]:
56 button = Gtk.Button(label=prog_language)
57 self.buttons.append(button)
58 button.connect("clicked", self.on_selection_button_clicked)
59
60 # setting up the layout, putting the treeview in a scrollwindow, and the buttons in a row
61 self.scrollable_treelist = Gtk.ScrolledWindow()
62 self.scrollable_treelist.set_vexpand(True)
63 self.grid.attach(self.scrollable_treelist, 0, 0, 8, 10)
64 self.grid.attach_next_to(
65 self.buttons[0], self.scrollable_treelist, Gtk.PositionType.BOTTOM, 1, 1
66 )
67 for i, button in enumerate(self.buttons[1:]):
68 self.grid.attach_next_to(
69 button, self.buttons[i], Gtk.PositionType.RIGHT, 1, 1
70 )
71 self.scrollable_treelist.add(self.treeview)
72
73 self.show_all()
74
75 def language_filter_func(self, model, iter, data):
76 """Tests if the language in the row is the one in the filter"""
77 if (
78 self.current_filter_language is None
79 or self.current_filter_language == "None"
80 ):
81 return True
82 else:
83 return model[iter][2] == self.current_filter_language
84
85 def on_selection_button_clicked(self, widget):
86 """Called on any of the button clicks"""
87 # we set the current language filter to the button's label
88 self.current_filter_language = widget.get_label()
89 print("%s language selected!" % self.current_filter_language)
90 # we update the filter, which updates in turn the view
91 self.language_filter.refilter()
92
93
94win = TreeViewFilterWindow()
95win.connect("destroy", Gtk.main_quit)
96win.show_all()
97Gtk.main()