terça-feira, 14 de abril de 2009

Python: PyQt Model/View

Imagino que todos conheçam o padrão de projeto MVC, o famoso Model/View/Controller. A idéia é separar a visualização (interface com o usuário) do controle (os "comandos" que o usuário executa) e do modelo (os dados e a lógica de negócio).

Uma pequena variação disso é o model/view, quando não é necessário controle, ou o controle está em um outro lugar. O MVC completo é um padrão arquitetural, sendo que o "MV" é um padrão mais localizado. Acredito que isso fique mais claro ao final do post :-).

A partir do Qt 4, alguns widgets passaram a suportar o padrão MV. Os antigos QListWidget, QTableWidget, etc. foram substituídos pelo QListView, QTableView, etc. As classes de modelo foram adicionadas para completar o padrão, e para conectar ambos basta um simples someView.setModel(someModel).

Um exemplo usando algumas classes padrão:

import sys

from PyQt4.QtCore import *
from PyQt4.QtGui import *

app = QApplication([])

view = QListView()
model = QStringListModel("item1 item2 item3 item4 item5".split())

view.setModel(model)

view.show()

sys.exit(app.exec_())

Qualquer mudança no model é refletida automaticamente na view, e é isso que torna o padrão interessante. É possível ter várias views para um model, e também existe o conceito dos delegates, que servem para dar flexibilidade à entrada de dados. Para não me alongar muito, não vou entrar nos detalhes, mas isso tudo pode ser visto com mais profundidade em Qt Model/View Programming.

model.setData(model.index(0), QVariant("aaaa"))



Agora um exemplo de classe que implementa um modelo abstrato. Supondo que eu queira um modelo de uma série de resultados numéricos, como proceder?

O primeiro passo é criar uma classe que implemente uma QAbstractTableModel:

from PyQt4.QtCore import *
from PyQt4.QtGui import *

class ResultTableModel(QAbstractTableModel):
    def __init__(self, parent = None):
        super(QAbstractTableModel, self).__init__(parent)
        self.results = []
    

Como pode ser visto aqui, o meu modelo deve sobrescrever alguns métodos para que ele possa utilizado. Caso o modelo necessite apenas ser lido (read-only), os métodos flags(), data(), headerData() e rowCount() devem ser providos. Este quatro métodos são suficientes para um QAbstractListModel, mas como estou implementando uma tabela, columnCount() também deve ser provido.

Uma implementação básica seria:

def rowCount(self, parent = QModelIndex()):
    return len(self.results)

def columnCount(self, parent = QModelIndex()):
    return 1 # por enquanto não quero mais de uma coluna

def flags(self, index):
    # quero todos os itens habilitados e editáveis
    return Qt.ItemIsEnabled | Qt.ItemIsEditable

# Recupera um dado de um índice (QModelIndex)
def data(self, index, role):
    # índice não é válido, então retorno um dado vazio
    if not index.isValid():
        return QVariant()

    # se a linha está fora dos bounds da minha lista, resultado vazio
    if index.row() >= len(self.results):
        return QVariant()

    # se o role é o DisplayRole (ou seja, o valor que deve ser renderizado)
    # retorno o valor que esta na linha lista
    if role == Qt.DisplayRole:
        return QVariant(self.results[index.row()])
    else:
        # podemos retornar valores específicos para outros roles,
        # por exemplo ToolTipRole. Pela simplicidade, ignoramos isso.
        return QVariant()

# Recupera os cabeçalhos da tabela
def headerData(self, section, orientation, role):
    if role != Qt.DisplayRole:
        return QVariant()

    if orientation == Qt.Horizontal:
        # Results vai aparecer no topo da linha
        return QVariant("Results")
    else:
        # em cada linha, mostrar o numero dela no início
        return QVariant("%s it." % (section + 1,))

Pronto, agora já podemos exibir o modelo em uma QTableView :-).

No próximo post irei mostrar como fazer para um model ser editável e redimensionável.

Outros materiais:
MVC
Model/View Programming
Model Subclassing
QTableView
QAbstractTableModel
Qt Documentation

analytics