W poprzednim artykule opisałem zasady implementowania modelu tabeli, który określa sposób wyświetlania danych w komponencie QTableView. Skoro potrafimy już przedstawić użytkownikom informacje, to warto pokusić się o umożliwienie im modyfikowania wartości. Implementacja modelu będzie składała się z następujących etapów:
- Rozszerzenie klasy NowyModel przedstawionej poprzednio przez dodanie do niej metod setData() i flags().
- Rozszerzenie QStyledItemDelegate, której zadaniem jest wyświetlanie odpowiedniego edytora wartości na żądanie użytkownika.
1. Model tabeli
W pierwszym kroku trzeba rozszerzyć klasę NowyModel, która z kolei dziedziczy po klasie QAbstractTableModel i nadpisać dwie metody:
- setData(indeks, wartość, rola) - metoda jest wywoływana, gdy użytkownik zatwierdza nową wartość w wybranej komórce tabeli,
- flags(indeks) - metoda określa dla wskazanej komórki tabeli, czy można ją edytować.
Model, którego implementację opisywałem poprzednio powodował wyświetlenie danych źródłowych w trzech komórkach. W pierwszej początek zakresu, w drugiej koniec zakresu, a w trzeciej cały zakres sformatowany jako string. W nowej implementacji użytkownik będzie miał możliwość modyfikowania wartości w pierwszej i drugiej kolumnie.
class EdytowalnyModel(NowyModel):
def __init__(self, wartosci):
NowyModel.__init__(self, wartosci)
def setData(self, indeks, wartosc, rola=Qt.EditRole):
if rola != Qt.EditRole:
return
w, k = indeks.row(), ideks.column() # współrzędne komórki
if k > 1: # tylko pierwszą i drugą kolumnę można edytować
return
self._dane[w][k] = wartosc.toInt[0] # konwersja QVariant -> int
self.dataChanged.emit(indeks, indeks)
def flags(self, indeks):
if indeks.column() <= 1:
return Qt.ItemIsEditable
return Qt.ItemIsSelectable
def __init__(self, wartosci):
NowyModel.__init__(self, wartosci)
def setData(self, indeks, wartosc, rola=Qt.EditRole):
if rola != Qt.EditRole:
return
w, k = indeks.row(), ideks.column() # współrzędne komórki
if k > 1: # tylko pierwszą i drugą kolumnę można edytować
return
self._dane[w][k] = wartosc.toInt[0] # konwersja QVariant -> int
self.dataChanged.emit(indeks, indeks)
def flags(self, indeks):
if indeks.column() <= 1:
return Qt.ItemIsEditable
return Qt.ItemIsSelectable
Dane źródłowe to lista (pole self._dane), z której każdy element jest wyświetlany jako osobny wiersz w komponencie QTableView. Elementami listy są pary liczb: pierwsza oznacza początek zakresu i jest wyświetlana w pierwszej kolumnie, druga - koniec zakresu - jest wyświetlana w drugiej kolumnie, a w trzecie wartość utworzona z połączenia pierwszej liczby i drugiej. Dlatego wartości w trzeciej kolumnie nie można edytować. Ona będzie się zmieniała automatycznie, gdy zmieni się wartość w pierwszej albo drugiej kolumnie. Trzeba pamiętać, że w tej implementacji nie jest sprawdzana poprawność wprowadzanych wartości, to znaczy użytkownik bez problemu zdefiniuje zakres, w którym pierwsza wartość z pary jest większa od drugiej.
2. Edytor wartości
Naszym użytkownikom chcemy ograniczyć wybór początku i końca zakresu. Dlatego zamiast prostego pola tekstowego będzie wyświetlana lista rozwijana QComboBox, z której będzie wybierał wartość. Aby uzyskać taki efekt trzeba rozszerzyć klasę QStyledItemDeleget, której komponent QTableView używa do określania, jaki edytor ma być wyświetlany dla wybranej komórki tabeli.
class ComboDelegate(QStyledItemDelegate):
def __init__(self, parent=None):
QStyledItemDelegate.__init__(self, parent=parent)
self._dopuszczalne = ['1','2','3','4','5','6','7','8','9']
def createEditor(self, parent, styl, indeks):
self.initStyleOption(styl, indeks)
cb = QComboBox(parent)
cb.addItems(self._dopuszczalne)
return cb
def setEditorData(self, edytor, indeks):
wartosc = indeks.data()
edytor.setCurrentIndex(wartosc-1)
def setModelData(self, edytor, model, indeks):
model.setData(indeks, edytor.currentIndex()+1)
Implementacja sprowadza się do nadpisania trzech metod:
- createEditor() - tworzy i zwraca nowy komponent edytora - w tym przypadku QComboBox, z którego można wybrać wartość z zakresu [1; 10]
- setEditorData() - po utworzeniu edytora i przed wyświetleniem go użytkownikowi trzeba na liście zaznaczyć aktualną wartość. W tym przypadku to jest bardzo proste, gdyż indeks na liście rozwijanej będzie zawsze o 1 mniejszy od wyświetlanej liczby (pobieranej przy pomocy metody indeks.data())
- setModelData() - metoda wywoływana na zakończenie edycji, jeżeli użytkownik zatwierdzi wybór nowej wartości. Jednym z parametrów jest obiekt klasy EdytowalnyModel, do którego wstawiamy nową wartość przy pomocy zaimplementowanej metody setData().
3. Uruchomienie
def nowaEdytowalnaTabela():
tv = QTableView()
dane = [[3, 6], [1, 8], [4, 9]]
model = EdytowalnyModel(dane)
tv.setModel(model)
edytor = ComboDelegate()
tv.setItemDelegateForColumn(0, edytor)
tv.setItemDelegateForColumn(1, edytor)
Połączenie wszystkiego w całość jest bardzo proste. Najpierw inicjujemy nową implementację modelu z danymi początkowymi, następnie tworzymy instancję klasy, która wyświetli edytor na żądanie użytkownika i przydzielamy ją do pierwszej i drugiej kolumny metodą setItemDelegateForColumn().
Przedstawione implementacje modelu są bardzo proste i podatne na błędy. Nic nie stoi na przeszkodzie, by wprowadzić błędne dane początkowe (na przykład z wartościami z poza zakresu). Ale rozwiązanie tego problemu pozostawiam P.T czytelnikom jako zadanie domowe ;)
edytor = ComboDelegate()
tv.setItemDelegateForColumn(0, edytor)
tv.setItemDelegateForColumn(1, edytor)
Połączenie wszystkiego w całość jest bardzo proste. Najpierw inicjujemy nową implementację modelu z danymi początkowymi, następnie tworzymy instancję klasy, która wyświetli edytor na żądanie użytkownika i przydzielamy ją do pierwszej i drugiej kolumny metodą setItemDelegateForColumn().
Przedstawione implementacje modelu są bardzo proste i podatne na błędy. Nic nie stoi na przeszkodzie, by wprowadzić błędne dane początkowe (na przykład z wartościami z poza zakresu). Ale rozwiązanie tego problemu pozostawiam P.T czytelnikom jako zadanie domowe ;)