Pokazywanie postów oznaczonych etykietą qt. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą qt. Pokaż wszystkie posty

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

niedziela, 3 lutego 2013

Implementacja nowych sygnałów Qt w Pythonie

Dzisiaj będzie krótko, w ramach uzupełnienia do poprzedniego artykułu. Opisałem w nim krótko koncepcję obsługi zdarzeń w Qt4, natomiast teraz chciałbym to uzupełnić o definiowanie nowych sygnałów dla widgetów tworzonych w Pythonie.
Zasada jest dość prosta, bo opiera się na stosowaniu metody pyqtSignal(args, name=nazwa_sygnalu), której zadaniem jest utworzenie nowego sygnału.

Przykład (tworzenie nowego sygnału)

from PyQt4.QtCore import pyqtSignal
from PyQt4.QtGui import QWidget, QPushButton

class NowyWidget(QWidget):
    
    def __init__(self, parent):
        QWidget.__init__(self, parent)
        btn = QPushButton('Kliknij')
        btn.clicked.connect(self.btnClick)

    widgetSygnal = pyqtSignal(str, name='widgetSygnal')

    def btnClick(self, ch):
        self.widgetSygnal.emit('Witaj')

def nowySlot(wiadomosc):
    print wiadomosc

def main():
    wgt = NowyWidget()
    wgt.widgetSygnal.connect(nowySlot)

Ze zdarzeniem QPushButton.clicked został połączony slot w postaci metody btnKlik zdefiniowany w klasie NowyWidget. Gdy użytkownik kliknie w przycisk, zostanie ona uruchomiona, a następnie wyemitowany nowy sygnał widgetSygnal, który został utworzony przy pomocy wspomnianej funkcji pyqtSignal. Zgodnie z jej wywołaniem slot, który podłączymy do sygnału powinien przyjmować jeden argument typu str, tak jak metoda nowySlot, która wyświetla otrzymaną wiadomość na ekranie.

wtorek, 15 stycznia 2013

Budowanie wykazów nazw

Ostatnimi czasy dużo się działo w kwestii QAZP2. Ostatnie poprawki błędów, niekiedy dość poważnych, nowe funkcjonalności - na przykład wykonywanie skryptów modyfikujących zawartość bazy danych - co może mieć znaczenie, gdy osoba odpowiadająca za techniczny aspekt bazy połączonej z QAZP2 nie ma do niej bezpośredniego dostępu. W takim wypadku wystarczy spreparować skrypt SQL i wysłać do użytkownika, który za pomocą interfejsu graficznego aplikuje przygotowane zmiany. Rzecz jasna taki mechanizm jest pewnym zagrożeniem dla stanu bazy danych, jednak jest to koszt, który można ponieść, pamiętając, by nie stosować skryptów z niepewnego źródła. Między innymi z tego powodu trudno było mi znaleźć czas i spełnić obietnicę, że nowe artykuły będą wprowadzane conajmniej raz na tydzień.

Ostatnimi czasy na blogu była poruszana kwestia wykazów pojęć archeologicznych, których używa się do klasyfikacji kulturowo-funkcjonalno-historycznej. W QAZP2 wykorzystywane są do tego wykazy przygotowane w Narodowym Instytucie Dziedzictwa i uzupełnione o nazwy, których obecności wymaga praktyka badań archeologicznych. Ich konstrukcję próbowałem opisać w poprzednim artykule, natomiast dzisiaj chciałbym przedstawić inne podejście, które chociaż nie zostało wdrożone, to wydaje się ciekawą alternatywą dla wyżej wspomnianego sposobu budowania wykazów.


sobota, 13 października 2012

Początek

Przed chwilą zatwierdziłem ostatnie zmiany w QAZP2, czyli nowej wtyczki do QuantumGIS, której zadaniem jest wspomaganie użytkowników w zarządzaniu informacjami zbieranymi w trakcie nieinwazyjnych badań archeologicznych. I pomyślałem, że być może warto opisać doświadczenia z realizacji tego projektu, przede wszystkim po to, by nie umknęły mej pamięci, ale też ze względu na to, że problematyka tworzenia rozszerzeń do QuantumGIS jest w polskojęzycznym internecie rzadko omawiana.
W związku z tym tworząc ten dziennik chciałbym poruszyć następujące kwestie:
  1. Wykorzystywanie API QuantumGIS.
  2. Tworzenie aplikacji z zastosowaniem biblioteki Qt.
  3. Zarządzanie danymi archeologicznymi i ich przetwarzanie. 
Na tą chwilę przychodzą mi do głowy tylko te trzy punkty, ale w każdej chwili lista może ulec rozszerzeniu. Będę starał się pisać na tyle regularnie, na ile pozwolą mi na to obowiązki zawodowe i czasami przemijające twórcze natchnienie ;). W ramach wstępu w kolejnym poście opiszę krótko projek, w ramach którego narodziła się idea utworzenia rozszerzenia QAZP(2)