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.
For a Gtk.ListStore
:
treeiter = store.append(["The Art of Computer Programming",
"Donald E. Knuth", 25.46])
For a Gtk.TreeStore
you must specify an existing row to append the new row to,
using a Gtk.TreeIter
, or None for the top level of the tree:
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[:])
Keep in mind, that if you use Gtk.TreeStore
, the above code will only
iterate over the rows of the top level, but not the children of the nodes.
To iterate over all rows, 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)
Once the Gtk.TreeView
widget has a model, it will need to know how to
display the model. It does this with columns and cell renderers.
headers_visible
controls whether
it displays column headers.
Cell renderers are used to draw the data in the tree model in a specific way.
There are a number of cell renderers that come with GTK+, for instance
Gtk.CellRendererText
, Gtk.CellRendererPixbuf
and
Gtk.CellRendererToggle
.
In addition, it is relatively easy to write a custom renderer yourself by
subclassing a Gtk.CellRenderer
, and adding properties with
GObject.Property()
.
A Gtk.TreeViewColumn
is the object that Gtk.TreeView
uses to
organize the vertical columns in the tree view and hold one or more cell renderers.
Each column may have a title
that will
be visible if the Gtk.TreeView
is showing column headers. The model
is mapped to the column by using keyword arguments with properties of the
renderer as the identifiers and indexes of the model columns as the arguments.
renderer = Gtk.CellRendererPixbuf()
column = Gtk.TreeViewColumn(cell_renderer=renderer, icon_name=3)
tree.append_column(column)
Positional arguments can be used for the column title and renderer.
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
First of all a comparison function is needed. This function gets two rows and has to return a negative integer if the first one should come before the second one, zero if they are equal and a positive integer if the second one should come before the first one.
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()
In the same way the sorting function works, the Gtk.TreeModelFilter
uses a “visibility” function, which, given a row from the underlying model, will return a boolean indicating if this row should be filtered out or not. It’s set by Gtk.TreeModelFilter.set_visible_func()
:
filter.set_visible_func(filter_func, data=None)
The alternative to a “visibility” function is to use a boolean column in the model to specify which rows to filter. Choose which column with Gtk.TreeModelFilter.set_visible_column()
.
Vejamos um exemplo completo que usa a pilha inteira Gtk.ListStore
– Gtk.TreeModelFilter
– Gtk.TreeModelFilter
— Gtk.TreeView
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | import gi gi.require_version("Gtk", "3.0") from gi.repository import Gtk # list of tuples for each software, containing the software name, initial release, and main programming languages used software_list = [ ("Firefox", 2002, "C++"), ("Eclipse", 2004, "Java"), ("Pitivi", 2004, "Python"), ("Netbeans", 1996, "Java"), ("Chrome", 2008, "C++"), ("Filezilla", 2001, "C++"), ("Bazaar", 2005, "Python"), ("Git", 2005, "C"), ("Linux Kernel", 1991, "C"), ("GCC", 1987, "C"), ("Frostwire", 2004, "Java"), ] class TreeViewFilterWindow(Gtk.Window): def __init__(self): super().__init__(title="Treeview Filter Demo") self.set_border_width(10) # Setting up the self.grid in which the elements are to be positioned self.grid = Gtk.Grid() self.grid.set_column_homogeneous(True) self.grid.set_row_homogeneous(True) self.add(self.grid) # Creating the ListStore model self.software_liststore = Gtk.ListStore(str, int, str) for software_ref in software_list: self.software_liststore.append(list(software_ref)) self.current_filter_language = None # Creating the filter, feeding it with the liststore model self.language_filter = self.software_liststore.filter_new() # setting the filter function, note that we're not using the self.language_filter.set_visible_func(self.language_filter_func) # creating the treeview, making it use the filter as a model, and adding the columns self.treeview = Gtk.TreeView(model=self.language_filter) for i, column_title in enumerate( ["Software", "Release Year", "Programming Language"] ): renderer = Gtk.CellRendererText() column = Gtk.TreeViewColumn(column_title, renderer, text=i) self.treeview.append_column(column) # creating buttons to filter by programming language, and setting up their events self.buttons = list() for prog_language in ["Java", "C", "C++", "Python", "None"]: button = Gtk.Button(label=prog_language) self.buttons.append(button) button.connect("clicked", self.on_selection_button_clicked) # setting up the layout, putting the treeview in a scrollwindow, and the buttons in a row self.scrollable_treelist = Gtk.ScrolledWindow() self.scrollable_treelist.set_vexpand(True) self.grid.attach(self.scrollable_treelist, 0, 0, 8, 10) self.grid.attach_next_to( self.buttons[0], self.scrollable_treelist, Gtk.PositionType.BOTTOM, 1, 1 ) for i, button in enumerate(self.buttons[1:]): self.grid.attach_next_to( button, self.buttons[i], Gtk.PositionType.RIGHT, 1, 1 ) self.scrollable_treelist.add(self.treeview) self.show_all() def language_filter_func(self, model, iter, data): """Tests if the language in the row is the one in the filter""" if ( self.current_filter_language is None or self.current_filter_language == "None" ): return True else: return model[iter][2] == self.current_filter_language def on_selection_button_clicked(self, widget): """Called on any of the button clicks""" # we set the current language filter to the button's label self.current_filter_language = widget.get_label() print("%s language selected!" % self.current_filter_language) # we update the filter, which updates in turn the view self.language_filter.refilter() win = TreeViewFilterWindow() win.connect("destroy", Gtk.main_quit) win.show_all() Gtk.main() |