Einsatz von QValidator

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Antworten
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Hallo Leute,

mein Code funktioniert auch soweit. Folgende Situation. Ich habe mit dem QT Designer eine *.ui-Datei erstellt auf denen sind zwei Buttons (pushButtonLogIn und pushButtonClose) und dazu zwei Textfelder (lineEditNickname und lineEditPassword). Das Ganze wird also wie ein kleines LogIn-Fenster aufgezogen. Allgemein wollte ich nun folgendes erreichen: Wenn der Anwender im Benutzer-Feld seinen Benutzernamen eingibt, aber das Passwort-Feld ist noch leer, so soll der LogIn-Button deaktiviert bleiben. Gibt der Anwender nun sein Passwort in das entsprechende vorgesehene Feld ein, soll überprüft werden, dass der Benutzername auch eingeben wurde, erst dann soll der LogIn-Button aktiviert werden.

Folgende Randsituation wollte ich auch vermeiden: Anwender gibt zuerst das Passwort in das vorgesehene Feld ein, und will später dann den Benutzernamen eingeben. Also die umgekehrte Reihenfolge. Dann soll geprüft werden, ob die Benutzer-Feld gefüllt ist, wenn nicht, dann soll der LogIn-Button weiterhin deaktiviert bleiben. Logisch, denn ohne Benutzername keinen Login :-)

Und raus gekommen ist diese Funktion.

Code: Alles auswählen

    def check_inputs(self):
        #-------------------------------------------------------------------------------
        sender = self.sender()
        validator = sender.validator()
        state = validator.validate(sender.text(), 0)[0]
        #-------------------------------------------------------------------------------
        if state == QValidator.Acceptable:
            #-------------------------------------------------------------------------------
            if self.lineEditNickname.text() == "":
                self.ui_pp_login.pushButtonLogIn.setEnabled(False)
            else:
                self.ui_pp_login.pushButtonLogIn.setEnabled(True)
            #-------------------------------------------------------------------------------
            if self.lineEditPassword.text() == "":
                self.ui_pp_login.pushButtonLogIn.setEnabled(False)
            else:
                #-------------------------------------------------------------------------------
                if self.ui_pp_login.lineEditNickname.text() == "":
                    self.ui_pp_login.pushButtonLogIn.setEnabled(False)
                else:
                    self.ui_pp_login.pushButtonLogIn.setEnabled(True)
                #-------------------------------------------------------------------------------
         #-------------------------------------------------------------------------------
            color = '#c4df9b' # green
            self.ui_pp_login.lineEditPassword.setEnabled(True)
        #-------------------------------------------------------------------------------
        elif state == QValidator.Intermediate:

            color = '#fff79a' # yellow
            self.ui_pp_login.pushButtonLogIn.setEnabled(False)

        else:
            color = '#f6989d' # red
        sender.setStyleSheet('QLineEdit { background-color: %s }' % color)
Ich habe bestimmte Abschnitte mit dem Enter auseinander gezogen und mit den Strichen versucht für mich etwas Übersicht reinzubringen. Wie gesagt, funktioniert auch alles, aber sieht sehr verknotet aus. Für dieses LogIn-Fenster ist es noch vielleicht erträglich, aber was ist, wenn man mehre Textfelder hat? Dann sieht das ganze richtig schwindel erregend aus. Nur leider habe ich keine Idee, wie ich während der Eingabe durch den QValidator die Textfelder überprüfen kann, damit ja auch die Randsituation mit berücksichtigt wird.
BlackJack

@Sophus: Das sieht alles ein wenig wirr aus. Unter welchen Umständen wird diese Methode denn aufgerufen? Und was für ein Validator wird da benutzt? Ich würde ja einfach auf beide Eingabefelder einen Validator oder eine Maske setzen der/die mindestens auf nicht-leer prüft und das Signal für Textänderungen von beiden Eingabefeldern mit einer Methode verbinden die den Zustand des Login-Buttons steuert. Zum Beispiel so (ungetestet):

Code: Alles auswählen

    def on_text_change(self, _text):
        self.ui_pp_login.pushButtonLogIn.setEnabled(
            self.lineEditNickname.acceptableInput
            and self.lineEditPassword.acceptableInput
        )
Bei den Farben ist mir jetzt nicht ganz klar was unter welchen umständen wie eingefärbt werden soll weil wir ja gar nicht wissen was der Validator prüft und auf was er prüft, also was Okay, eventuell Okay, und nicht Okay ist.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Sehr umständlich es es auch noch geschrieben. Der Block

Code: Alles auswählen

if self.lineEditNickname.text() == "":
    self.ui_pp_login.pushButtonLogIn.setEnabled(False)
else:
    self.ui_pp_login.pushButtonLogIn.setEnabled(True)
lässt sich viel einfacher ausdrücken:

Code: Alles auswählen

self.ui_pp_login.pushButtonLogIn.setEnabled(self.lineEditNickname.text() != "")
oder

Code: Alles auswählen

self.ui_pp_login.pushButtonLogIn.setEnabled(bool(self.lineEditNickname.text()))
Die ganzen Abgrenzungen durch Linien in deinem Code solltest du dir gleich wieder abgewöhnen. Es gibt schon einen Grund, warum niemand das so macht ;-) Benutze einfach Leerzeilen wo nötig, und keine wo nicht; zum Beispiel Zeilen 28 und 31. Die ganzen "ui_pp"-Prefixe tragen auch nicht gerade zur Lesbarkeit bei.
Das Leben ist wie ein Tennisball.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@BlackJack: Um deine Frage zu beantworten: Die Funktion check_inputs wird durch eine textChanged-Methode eines lineEdit-Textfeldes aufgerufen:

Code: Alles auswählen

self.lineEditNickname.textChanged.connect(self.check_inputs)
Hierbei wird also während der Eingabe geprüft, wie die Text gefüllt sind und entsprechend wollte ich dann den Button steuern. Ich werde mir mal dein Beispiel anschauen. Aber was mich eher Kopfzerbrechen bereitet, ist, wenn ich mehrere Textfeld habe, die gefüllt sein müssen, damit ein Button aktiviert wird, damit der Anwender auch wirklich dazu gezwungen wird, alle Textfelder auszufüllen. Du sagst ja selber, dass meine Funktion sehr wirr aussieht :-)
Zuletzt geändert von Sophus am Mittwoch 18. März 2015, 22:58, insgesamt 2-mal geändert.
BlackJack

@Sophus: Wie ich diese Methode (ohne die Farben) schreiben würde habe ich ja gezeigt. Der Login-Button wird genau dann aktiviert wenn beide Eingaben akzeptabel sind und deaktiviert wenn nicht. Das ist *eine* relativ simple Anweisung.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@BlackJAck:

Dein Beispiel scheint nicht zu klappen:
File "D:\Dan\Python\project_xarphus\files\modules_ui\ui_pp_login.py", line 78, in check_inputs
self.lineEditNickname.acceptableInput
AttributeError: 'QLineEdit' object has no attribute 'acceptableInput'
Ich habs dann wie folgt geändert:

Code: Alles auswählen

            self.ui_pp_login.pushButtonLogIn.setEnabled(
                self.lineEditNickname.hasAcceptableInput()
                and self.lineEditPassword.hasAcceptableInput())
Scheint zu funktionieren, aber nur mal zur Sicherheit. So ist es richtig oder?
Zuletzt geändert von Sophus am Mittwoch 18. März 2015, 23:08, insgesamt 1-mal geändert.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@EyDu:

Dein Beispiel werde ich mir mal genauer anschauen, wie ich da zum Beispiel mehrere Textfelder drunter kriege. Aber eine Frage:

Wieso soll ich mir diese Striche abgewöhnen? Ich meine, ich für meinen Teil finde es übersichtlich, und sehe gleich sofort die "Grenzen" wo etwas zu Ende ist, bzw, was thematisch nicht zusammen gehört :-)
BlackJack

@Sophus: Meine Güte deswegen stand da ja auch ungetestet. Muss man halt mal in der Dokumentation nachschauen wie das wirklich heisst (wieder ungetestet):

Code: Alles auswählen

    def on_text_change(self, _text):
        self.ui_pp_login.pushButtonLogIn.setEnabled(
            self.lineEditNickname.hasAcceptableInput()
            and self.lineEditPassword.hasAcceptableInput()
        )
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@BlackJack: Entschuldige, wenn ich dir an den Karren gefahren bin. Ich wollte nur so höflich sein. Wenn andere dieses Problem haben, und hier mitlesen, dann würden sie dann gleich darauf hingewiesen werden. War keine böse Absicht :-)
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Nun, für das Widget QtextEdit ist die Methode hasAcceptableInput nicht vorhanden. Ich habe auch in der Dokumentation geschaut. Irgendwie fand ich keine brauchbare Alternative für diesen Fall.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Sophus hat geschrieben:Wieso soll ich mir diese Striche abgewöhnen? Ich meine, ich für meinen Teil finde es übersichtlich, und sehe gleich sofort die "Grenzen" wo etwas zu Ende ist, bzw, was thematisch nicht zusammen gehört :-)
Das ist doch das genaue Gegenteil von übersichtlich, dass stört nur den Lesefluss. Zum Aufteilen verwendet man üblicherweise Leerzeichen. Und wenn eine Funktion zu unübersichtlich wird, dann ist sie entweder schlecht Strukturiert, sie macht mehr als nötigt oder sollte in mehrere Funktionen aufgeteilt werden. Und wie ich schon schrieb: Ich kenne niemanden, der das so macht. Und ich gehe einfach mal davon aus, dass ich schon deutlich mehr Code gelesen und geschrieben habe als du. Und falls ich doch mal so jemanden treffen sollte, dann wird der vom Arbeitsplatz im schönen schattigen Keller auf die viel zu helle Dachterrasse verbannt ;-)
Das Leben ist wie ein Tennisball.
Sirius3
User
Beiträge: 17747
Registriert: Sonntag 21. Oktober 2012, 17:20

@EyDu: das ist häufiger als man denkt. Ich hab schon Assembler-Code gesehen, der mit solchen Strichen übersät war.
Benutzeravatar
MagBen
User
Beiträge: 799
Registriert: Freitag 6. Juni 2014, 05:56
Wohnort: Bremen
Kontaktdaten:

Du hast jetzt Model, View und Controller alles in einer Klasse, deshalb sieht's auch schon recht unübersichtlich aus, obwohl die Funktionalität minimal ist: zwei Textfelder prüfen und den OK Button entsprechend aktivieren. Wie meinst Du wird das ganze aussehen, wenn es komplexer wird, z.B. beim Adressformular einer Bestellung:
  • zig Textfelder
  • teilweise optional: wenn Packstation, dann ist die Straße optional, wenn Mobilnummer ausgefüllt, dann ist Festnetznummer optional
  • unterschiedlicher Syntaxcheck der Textfelder: gültiges Postleitzahlformat, gültiges Email-Format
Bei sowas hilft ein Objektmodell. Die Textfelder sind alle ziemlich dumm, sie schreiben ihren Wert einfach nur in ein bestimmtes Attribut der Adressklasse. Die Attribute werfen Exceptions wenn die Eingabe ungültig ist und das Textfeld wird dann rot. Immer wenn ein Attribut geändert wurde, wird der Gesamtzustand des Adressobjekts analysiert und wenn sich der Zustand von unvollständig auf vollständig geändert hat (oder umgekehrt), dann wird ein Signal ausgelöst.

Wenn Du mit PyQt entwickelst, dann brauchst Du den QValidator auch gar nicht. Der QValidator ist ein C++ Hilfsmittel, in Python gibt es aber viel einfachere Möglichkeiten die Eingabe zu überprüfen als in C++.
a fool with a tool is still a fool, www.magben.de, YouTube
BlackJack

@Sophus: Bei einem `QTextEdit` kann man ja auch keinen `QValidator` setzen. Einen solchen Texteditor verwendet man üblicherweise auch nicht um dort Text in einem bestimmten vorgegebenen Format einzugeben sondern für Fliesstest (sogar mit Formatierungen). Wenn man die Eingabe dort in einem bestimmten Muster/Format erzwingen will muss man das selber tun. Zum Beispiel eine eigene Klasse ableiten bei der man einen Validator setzen kann und die dann bei der Eingabe gegen diesen Validator prüft. Und dann kann man der Klasse ja auch eine `hasAcceptableInput()`-Methode verpassen. Aber wie gesagt, das ist ja eher ungewöhnlich das man so etwas mit einem Freitext-Feld machen möchte.

Ich finde die Trennstriche übrigens auch total unübersichtlich, habe auch nicht herausgefunden was die denn nun trennen oder zusammenfassen weil sie weder konsistent paarweise auftreten noch einzelne Trenner sind. Einige sehen so aus als wenn sie einfach nur alles in einem Block einschliessen — aber da sieht man ja durch den Block das der Code darin offensichtlich zusammengehört. Da die auch nicht konsistent eingerückt sind, sehen die für mich komplett willkürlich eingestreut aus.

Genau wie Sirius3 kenne ich solche Trennstrich-Kommentare von Assembler-Quelltexten und alten BASIC-Dialekten. Dort setze ich sie auch selber ein, aber aus dem einzigen Grund weil man dort nicht am Quelltext (Einrückung) die Code-Struktur erkennen kann, und deshalb andere visuelle Möglichkeiten braucht um Anfang und Ende von Routinen, Schleifen, und Programmzweigen an den Leser zu übermitteln.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@BlackJack: Ich wollte bei QTextedit keinen QValidator setzen. Ich habe mit dem QT Designer eine Art Support-Fenster erstellt. Auch wieder zwei Textfelder, einmal die Betreffzeile und einmal für den Fließtext. Und ich habe gehofft, dass ich (natürlich ohne QValidator) so ähnlich vorgehen kann - das erst die Betreffzeile-Feld und der Fließtext-Feld ausgefüllt sein müssen, damit der Sende-Button aktiviert wird.

@MagBen: Du meinst, es gäbe in Python eine bessere und einfache Lösung? Ich verwende QT-4.8. Kannst du mir mal ein nicht lauffähiges Pseudo-Code liefern, damit ich dich verstehe was du meinst?
BlackJack

@Sophus: Bei einem `QTextEdit` kann man einfach testen ob das enthaltene `QTextDocument` leer ist oder nicht.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@BlackJack:

Meinst du etwa so? (ungetestet):

Code: Alles auswählen

def on_text_change(self, _text):
        self.ui_pp_login.pushButtonSend.setEnabled(
            self.lineEditSubject.text() != ""
            and self.textEditFeedback.toPlainText() != ""
        )
Ich habe mich hierbei an deinem vorherigen Beispiel gehalten.
Zuletzt geändert von Sophus am Donnerstag 19. März 2015, 16:38, insgesamt 1-mal geändert.
BlackJack

@Sophus: Beim `QLineEdit` würde ich wohl weiterhin beim Validator bleiben, vielleicht einer der Prüft ob mindestens ein nicht-Whitespace-Zeichen eingeben wurde.

Bei `QTextEdit` würde ich wie gesagt testen ob das enthaltene `QTextDocument` Zeichen enthält. Klar könnte man sich da auch bei jeder Änderung den gesamten Textinhalt geben lassen. Ich hätte halt die Hoffnung das die Anzahl der Zeichen effizienter ermittelt werden kann.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@BlackJack:

Ich habe erst einmal eine "Zwischenlösung":

Code: Alles auswählen

        self.ui_pp_feedback.lineEditSubject.setStyleSheet('background-color: #fff79a;')
        self.ui_pp_feedback.lineEditSubject.textChanged.connect(self.on_text_change)
        self.ui_pp_feedback.lineEditSubject.setValidator(QRegExpValidator(QRegExp("[r'\w'aA-zZ0-9 ]+"), self.lineEditSubject))
[...]
    def on_text_change(self):
            self.ui_pp_feedback.pushButtonSend.setEnabled(
                self.lineEditSubject.text() != ""
                and self.textEditFeedback.toPlainText() != "")

            if not self.ui_pp_feedback.lineEditSubject.text() != "":
                self.ui_pp_feedback.lineEditSubject.setStyleSheet('background-color: #fff79a;') # yellow
            else:
                self.ui_pp_feedback.lineEditSubject.setStyleSheet('background-color: #c4df9b;') # green

            if not self.ui_pp_feedback.textEditFeedback.toPlainText() != "":
                self.ui_pp_feedback.textEditFeedback.setStyleSheet('background-color: #fff79a;') # yellow
            else:
                self.ui_pp_feedback.textEditFeedback.setStyleSheet('background-color: #c4df9b;') # green
Nun, mittels QRegExpValidator legeich fest, dass zum Beispiel Leerzeichen, Zahlen und Buchstaben, sogar Umlaute erlaubt sind. Das alles nur in der Betreffzeile. Ich habe gehofft, dass ich mit dem textEditFeedback-Widget genauso verfahren kann. Dafür kennt dieses Widget aber die Methode setValidator leider nicht. In der on_text_change-Funktion geht es erst einmal um die Überprüfung der Text-Inhalte, um den Button zu steuern. Im nächsten If-Block wollte ich mit den Farben spielen. Klappt auch alles. Aber ich werde das Gefühl nicht los, dass ich es hätte besser machen können. Irgendwelche Idee?

Problem bei den beiden Texfeldern, ist, da man mit den Leerzeichen hier vorgaukeln kann, dass hier angeblich was gefüllt wird. Wir gehen davon aus, man betätigt in beiden Textfelder unendlich viel die Leertaste - keine Zeichen, Buchstaben und Zahlen. Zum Beispiel lässt lineEditSubject durch den QRegExpValidator zu, dass auch Leerzeichen erlaubt sind. Wenn ich dann durch den Klick auf pushButtonSend überprüfen will, ob die TextFelder auch wirklich nicht leer sind, reicht sicherlich nicht zu überprüfen ob die jeweiligen Felder ="" sind. Wie gesagt, durch die Leertasten (whitespace) kann man den Prüf-Mechanismus getrost überspringen und es wird am Ende doch nur leerer Inhalt versendet.
Antworten