QT + Python in QGIS Warten auf Beenden einer Funktion

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Antworten
F1r3fly
User
Beiträge: 6
Registriert: Donnerstag 21. März 2019, 21:44

Hi,

ich bin ziemlicher Anfänger mit Python und habe von QT leider noch keine Ahnung. Dennoch schreibe ich gerade für das Programm QGis ein eigenes kleines Python-Plugin. QGis nutzt Python 3 und QT. Dabei stoße ich immer wieder auf Probleme, die ich entweder selbst lösen kann oder gelegentlich Hilfe auf stackexchange bekomme. Leider klappt das nicht immer und aktuell stehe ich vor einem Problem wo ich auch mit einem Post auf Stackexchange nicht weiter komme. Eventuell gibt es hier jemanden, der mir helfen kann. Würde gerne mit meinem Plugin weiter kommen.

Mein aktuelles Problem ist folgendes.
Ich rufe über einen Button eine Funktion auf. Dabei wird eine QT-Gui geöffnet, der User kann Werte eingeben und die Maske schließen. Nach dem Schließen wird in QGis ein Maptool gesetzt, dass die Koordinaten eines Mausklicks auf der Karte ausgibt. Nach dem Klick wird das Maptool zurückgesetzt und ich möchte mit den Koordinaten weiter arbeiten. Mein Problem dabei ist, dass das Maptool als Funktion aufgerufen wird und Hauptfunktion einfach weiter läuft, obwohl der Nutzer noch keine Eingabe gemacht hat. Es gibt seitens des Maptools ein QT void Signal, dass ausgelöst wird. Ich verstehe allerdings nicht, wie ich dieses Signal nutze.
Vielleicht kann mir jemand von euch erklären, wie ich damit umgehe.

Die Dokumentation zu dem Maptool
https://www.qgis.org/api/classQgsMapTool.html

Void-Signal, dass vermutlich meinen Zweck erfüllt
https://www.qgis.org/api/classQgsMapToo ... af14459599

Anbei noch Ausschnitte meines Codes.

Code: Alles auswählen

    def run2(self): 
        # öffnet die entsprechende GUI
        self.dlg2.show()
        result = self.dlg2.exec_()
        # Wenn GUI bestätigt, weiter machen
        if result: 
           # QGSMapToolEmitPoint ist das Maptool
            self.pointTool = QgsMapToolEmitPoint(self.canvas)
            self.pointTool.canvasClicked.connect(self.display_point)
            self.canvas.setMapTool(self.pointTool)
            
            # Auf das Beenden dieses Aufrufes würde ich gerne Warten.
            self.display_point(self.pointTool)
            
            # Hier kommt dann der restliche Code der Funktion
            # DoSomething mit self.point
          
      # Funktion zum Erhalt der Koordinaten  
     def display_point(self, pointTool):
        try:
            self.point = QgsGeometry.fromPointXY(QgsPointXY(pointTool.x(), pointTool.y()))
            print(self.point)
            # Reset des MapTools
            self.canvas.unsetMapTool( self.pointTool )
            # Jetzt erst weiter in run2
        except AttributeError:
            pass    
            
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Zwei Anmerkungen:

- niemals AttributeError abfangen. Du verschleierst dadurch Programmierfehler.
- deine Vorstellung darueber, wie GUIs funktionieren, ist falsch. "Jetzt weiter in" geht nicht. GUI arbeiten Ereignis-orientiert. Ereignisse sind Benutzerinteraktionen, oder zB Timer, oder Daten die ankommen via Netzwerk oder Massenspeicher. Dein Signal self.pointTool.canvasClicked ist mit display_point verbunden. Und danach war's das. Die Kontrolle kehrt zurueck an den Mainloop, also da Programm. WENN (kann ja ne Weile dauern) der Benutzer irgendwann mal auf die Karte geklickt hat, dann loest dieses Ereignis schlussendlich den Aufruf von display_point auf. Und da muss der Code dann halt weiter machen.
F1r3fly
User
Beiträge: 6
Registriert: Donnerstag 21. März 2019, 21:44

Danke für die Antwort.
-Den AttributeError mache ich raus
- Das "Jetzt weiter in" war mehr als Kommentar gedacht, was ich gerne hätte. Das die GUI ereignisorientiert arbeitet ist mir bewusst.
Gibt es denn keine Möglichkeit in der Main-Loop zu warten bis display_point durchgelaufen ist?
Mein Hintergrund ist, dass ich in der GUI Abfrage, ob die Punkte über display_point oder über einen GPS-basiertem Abruf stattfinden soll. Nachdem der Punkt ermittelt wurde, werden alle Attribute aus der GUI zusammen mit dem Punkt an eine weitere Funktion übergeben, die alles dann in eine Datenbank schreibt. Wenn ich dich da richtig verstehe, müsste ich die Attribute aus der GUI den Funktionen display_point und der GPS-Funktion mitgeben und aus diesen beiden Funktionen jeweils die Funktion zum Schreiben der Daten aufrufen. Sehe ich das richtig?
__deets__
User
Beiträge: 14493
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ereignisorientiert ist eben != linearer Code Verlauf. So ganz bewusst ist das also noch nicht.

Dein genaues Problem habe ich jetzt nicht begriffen. Was da wohier wie wo etwas tun soll. Aber du musst display_point befähigen, weiter zu machen. Ggf zb durch per functools.partial heran dekorierte Werte. Oder durch eine Closure, also display_point zu einer lokalen Funktion in run2 (mieser Name übrigens) zu machen.
F1r3fly
User
Beiträge: 6
Registriert: Donnerstag 21. März 2019, 21:44

Danke nochmal für deine Antwort.
ich habe mein Problem derweil gelöst
- Das Abfangen der AttributeError scheint in dem Fall notwendig zu sein, da ansonsten bis zum Klicken auf die Karte QgsGeometry.fromPointXY(QgsPointXY(pointTool.x(), pointTool.y())) keine X und Y Werte hat und direkt in einen Fehler läuft.
- run2 ist für mich nur eine Testfunktion zum spielen. Der Name ist natürlich bescheuert.
- ich habe mich nochmal etwas umfangreicher mit dem Tool beschäftigt. Innerhalb der Class für das Maptool kann man in der Funktion canvasReleaseEvent(self, event): über .emit() die Ergebnisse nach außen zurückgeben und dann über .connect() in meiner "run2" funktion weiter machen. Damit braucht man dann auch nicht mehr den AttributeError abfangen.
Benutzeravatar
__blackjack__
User
Beiträge: 13003
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@F1r3fly: Den `AttributeError` zu behandeln, und dann auch noch so grossräumig, ist trotzdem falsch. Denn ein `AttributeError` deutet a) so gut wie immer auf einen Programmierfehler hin den man *lösen* sollte, und b) verschluckt man mit dieser Ausnahmebehandlung alle diese Programmierfehler in dem ``try``-Block und allem was von dort direkt oder indirekt aufgerufen wird!

Welches Attribut wurde denn da auf welchem Objekt nicht gefunden?
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
F1r3fly
User
Beiträge: 6
Registriert: Donnerstag 21. März 2019, 21:44

.x() und .y() wurde nicht gefunden. aber ich denke ich gehe den weg über die Class und dem emit().
Benutzeravatar
__blackjack__
User
Beiträge: 13003
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@F1r3fly: Das kann ich mir nicht vorstellen das es tatsächlich die *Methoden* nicht gibt. Der Code passt sowieso nicht so wirklich zu der verlinkten Dokumentation. Dort hat das `QgsMapToolEmitPoint.canvasClicked`-Signal *zwei* Argumente – einen `QgsPointXY`- und einen `Qt.MouseButton`-Wert. Dein Slot erwartet aber nur *ein* Argument. Den erstem Wert nennst Du `pointTool`, was ein komischer, irreführender Name für einen `QgsPointXY`-Wert ist. Und das ist eine Klasse die in C++ implementiert ist und garantiert immer `x()`- und `y()`-Methoden hat. Ich sehe nicht wo da ein `AttributeError` für eines dieser beiden Attribute herkommen soll‽

`pointTool` ist auch deswegen ein sehr schlechter Name für den Punkt weil es auf dem Objekt selbst ein `pointTool` gibt das an ein `QgsMapToolEmitPoint`-Objekt gebunden ist, also etwas gänzlich anderes.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
F1r3fly
User
Beiträge: 6
Registriert: Donnerstag 21. März 2019, 21:44

@blackjack
Ich hatte den code von stackexchange übernommen und mir erstmal keine weiteren Gedanken darüber gemacht. Ich habe mich aber auch im Nachhinein ein wenig gewundert, insbesondere nachdem ich noch ein anderes MapTool eingebunden habe und dort die Dokumentation passte. Ich werde es vorläufig so lassen. Sobald ich die anderen Probleme gelöst habe, schaue ich mir das nochmal an. Ich denke das ganze wie beschreiben über das canvasReleaseEvent in der Class zu machen ist der richtige Weg.
Antworten