niedziela, 30 czerwca 2013

Qt4: edytowanie danych w tabeli

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:
  1. Rozszerzenie klasy NowyModel przedstawionej poprzednio przez dodanie do niej metod setData() i flags().
  2. 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  

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 ;)