Verschiedene QWidgets in class integrieren

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Nobuddy
User
Beiträge: 1015
Registriert: Montag 30. Januar 2012, 16:38

Hallo zusammen,

Ich suche nach einer Möglichkeit, verschiedene QWidgets (QLineEdit, PasswordEdit) in einer class zu integrieren.

Bisher verwende ich für jedes QWidget eine eigene Klasse (Beispiel).
Die Events (focusInEvent, focusOutEvent, mousePressEvent, eventFilter), sind bei den verschiedenen QWidgets identisch.

Code: Alles auswählen

class TextInput(QLineEdit):
	def __init__(self, parent):
		super(TextInput, self).__init__()
		self.parent = parent
		self.installEventFilter(self)

	def focusInEvent(self, event):
		colorField(widget=self.parent.focusWidget(), parent=self.parent)
		super(TextInput, self).focusInEvent(event)

	def focusOutEvent(self, event):
		try:
			i = self.parent.inputWidgets.index(self.parent.focusWidget())
		except ValueError:
			return None
		self.parent.lastCol = i-1 if i > 0 else 0
		widget = self.parent.inputWidgets[self.parent.lastCol]
		colorField(widget=widget, parent=self.parent)
		super(TextInput, self).focusOutEvent(event)

	def mousePressEvent(self, QMouseEvent):
		if QMouseEvent.type() == QEvent.MouseButtonPress:
			button = ""
			i = self.parent.inputWidgets.index(self.parent.focusWidget())
			if QMouseEvent.button() == Qt.LeftButton:
				button = "Left"
				self.parent.keyRelease = 'mouse'
				self.parent.inputControl()
			elif QMouseEvent.button() == Qt.RightButton:
				button = "Right"
			elif QMouseEvent.button() == Qt.MiddleButton:
				button = "Middle"
		super(TextInput, self).mousePressEvent(QMouseEvent)

	def eventFilter(self, widget, event):
		if event.type() == QEvent.KeyRelease and widget is self:
			key = event.key()
			i = self.parent.inputWidgets.index(self.parent.focusWidget())
			if widget.text().strip() == '':
				try:
					self.parent.Login.setEnabled(False)
				except AttributeError:
					pass
				try:
					self.parent.Registration.setEnabled(False)
				except AttributeError:
					pass
				try:
					self.parent.Send.setEnabled(False)
				except AttributeError:
					pass
				for w in self.parent.inputWidgets[
						(i if len(self.parent.inputWidgets)-1 == i
						else i+1):]:
					w.clear()
					colorField(widget=w, parent=self.parent)
			if key in (Qt.Key_Return, Qt.Key_Enter):
				self.parent.keyRelease = 'return'
				colorField(widget=widget, parent=self.parent)
				self.parent.inputControl()
			elif key == Qt.Key_Down:
				self.parent.keyRelease = 'down'
				colorField(widget=widget, parent=self.parent)
				self.parent.inputControl()
			elif key == Qt.Key_Up:
				self.parent.keyRelease = 'up'
				self.parent.inputControl()
				colorField(widget=widget, parent=self.parent)
			elif key == Qt.Key_Tab:
				self.parent.keyRelease = 'down'
				try:
					check = {
						0 : self.parent.checkMail,
						1 : self.parent.checkLogin,
						2 : self.parent.userPassword,
						3 : self.parent.userPasswordRepeat
						}[self.parent.lastCol]
				except AttributeError:
					check = False
				if not check:
					widget = self.parent.inputWidgets[self.parent.lastCol]
					widget.setFocus()
				else:
					colorField(widget=widget, parent=self.parent)
		return QWidget.eventFilter(self, widget, event)
Gib es eine Möglichkeit, dies zusammenzufassen, so dass Events funktionieren?

Grüße Nobuddy
Benutzeravatar
sparrow
User
Beiträge: 4501
Registriert: Freitag 17. April 2009, 10:28

@Nobuddy: Ich weiß nicht, ob es sein Spezialfall ist, weil pyQt ud pySide ja nur dünne Wrapper zu C++-Bibliotheken sind, aber du kannst hier ganz normale Vererbung anwenden. Also eine Elter-Klasse, die Gemeinsamheiten implementiert und von der geerbt wird.
Benutzeravatar
__blackjack__
User
Beiträge: 13919
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Nobuddy: Anmerkungen zum Quelltext: Ich hoffe das wird nicht tatsächlich noch Python 2 verwendet, denn `super()` braucht keine Argumente in Python 3.

Das `parent`-Attribut darf so nicht heissen, denn `QObject` hat eine Methode die so heisst, die damit überschrieben wird. Man braucht dieses Attribut aber auch gar nicht, denn genau diese Methode von `QObject` liefert das Elternobjekt. zu dem `QObject`.

Was hat denn in `focusOutEvent` den `ValueError` ausgelöst? Die gleiche Zeile aus dem ``try`` kommt noch zwei andere male im Quelltext vor, ohne dass da ein `ValueError` durch ignorieren behandelt wird. Das kann ja eigentlich nur der `index()`-Aufruf sein. Und wie kann es dann sein, dass das Widget mit dem Fokus nicht in der Liste vorhanden ist?

Bei `mousePressEvent()` heisst ein Argument `QMouseEvent` also gleich wieder Datentyp. Deswegen gibt es ja die Namenskonventionen, damit Variablen nicht gleich benannt werden wie Datentypen und man dann im besten Fall den Leser verwirrt, und im schlechten Fall es unmöglich macht den Datentyp zu verwenden, weil der durch die Variable verdeckt wird.

`button` wird nirgends verwendet. Es macht auch nicht so wirklich Sinn Qt-Konstanten durch Zeichenketten zu ersetzen. `i` wird ebenfalls nicht verwendet in der Methode. Wenn man das unbenutzte rauswirft, sieht man, dass man die beiden übrig bleibenden, verschachtelten ``if`` zu einem zusammenfassen kann.

Das setzen von `keyRelease` auf dem Elternobjekt ist bis auf eine Ausnahme immer von einem Aufruf von `inputControl()` auf dem Elternobjekt gefolgt. Das riecht danach als wenn der `keyRelease()`-Wert eigentlich ein Argument beim Aufruf sein sollte.

In `eventFilter()` wird `i` definiert bevor überhaupt klar ist, ob der Wert gebraucht wird und auch recht weit von der Stelle wo der Wert gegebenenfalls verwendet wird. `key` wird auch zu früh definiert.

`AttributeError` behandelt man nicht im Code. Das ist ein Programmierfehler wenn man auf nicht existierende Attribute zugreifen will. Entweder ein Objekt hat die Attribute, oder man greift da nicht drauf zu. Ich habe die vage Erinnerung, dass das schon mehrfach Thema war. Mindestens zwischen Dir und Sirius3.

So ein ``self.parent().inputWidgets.index(self.parent().focusWidget())`` ist auch ziemlich irrsinnig wenn man ja weiss, dass der Eventfilter gerade ein Tasten-Ereignis behandelt und dass das Widget das Widget selbst ist. Damit ist ja klar das `self` das Widget mit dem Fokus sein muss, denn sonst hätte es gerade keinen Tasten-Ereignis bekommen können.

Ein Wörterbuch mit ganzen Zahlen die von 0 an aufsteigen ist kein Wörterbuch sondern eine Liste.

`check` wird nicht wirklich gebraucht. Zu ``try``/``except`` gibt es auch ``else``. Aber das ganze Konstrukt ist falsch, denn auch hier wird ein `AttributeError` zur Laufzeit behandelt der so gar nicht vorkommen darf.

Zwischenstand (ungetestet):

Code: Alles auswählen

class TextInput(QLineEdit):
    def __init__(self, parent):
        super().__init__(parent)
        self.installEventFilter(self)

    def focusInEvent(self, event):
        colorField(widget=self.parent().focusWidget(), parent=self.parent())
        super().focusInEvent(event)

    def focusOutEvent(self, event):
        lastCol = max(
            0,
            self.parent().inputWidgets.index(self.parent().focusWidget()) - 1,
        )
        self.parent().lastCol = lastCol
        colorField(
            widget=self.parent().inputWidgets[lastCol], parent=self.parent()
        )
        super().focusOutEvent(event)

    def mousePressEvent(self, event):
        if (
            event.type() == QEvent.MouseButtonPress
            and event.button() == Qt.LeftButton
        ):
            self.parent().inputControl("mouse")
        super().mousePressEvent(event)

    def eventFilter(self, widget, event):
        if event.type() == QEvent.KeyRelease and widget is self:
            if widget.text().strip() == "":
                self.parent().Login.setEnabled(False)
                self.parent().Registration.setEnabled(False)
                self.parent().Send.setEnabled(False)
                #
                # Alle Widgets nach dem Widget mit dem Fokus, aber auf jeden
                # Fall das Letzte, auch wenn das den Fokus hat, leeren.
                #
                inputWidgets = self.parent().inputWidgets
                for input_widget in inputWidgets[
                    min(
                        inputWidgets.index(self.parent().focusWidget()) + 1,
                        len(inputWidgets) - 1,
                    ) :
                ]:
                    input_widget.clear()
                    colorField(widget=input_widget, parent=self.parent())

            key = event.key()
            if key in (Qt.Key_Return, Qt.Key_Enter):
                colorField(widget=widget, parent=self.parent())
                self.parent().inputControl("return")
            elif key == Qt.Key_Down:
                colorField(widget=widget, parent=self.parent())
                self.parent().inputControl("down")
            elif key == Qt.Key_Up:
                self.parent().inputControl("up")
                colorField(widget=widget, parent=self.parent())
            elif key == Qt.Key_Tab:
                #
                # TODO Muss das hier sein?  Kann das weg oder fehlt ein
                #   `inputControl()`-Aufruf auf dem Eltern-Objekt?
                #
                self.parent().keyRelease = "down"
                #
                # FIXME Das hier muss anders gelöst werden.  Man behandelt
                #   keinen `AttributeError` zur Laufzeit auf diese Weise.
                #
                try:
                    [
                        self.parent().checkMail,
                        self.parent().checkLogin,
                        self.parent().userPassword,
                        self.parent().userPasswordRepeat,
                    ][self.parent().lastCol]
                except AttributeError:
                    self.parent().inputWidgets[
                        self.parent().lastCol
                    ].setFocus()
                else:
                    colorField(widget=widget, parent=self.parent())

        return QWidget.eventFilter(self, widget, event)
Aber das ist eh alles falsch. Ein Widget sollte nichts über Widgets über sich wissen müssen und auch nichts damit machen. Also alle `self.parent()` haben da nichts zu suchen. Hier muss ja jedes Widget etwas über den Aufbau der Eingabemaske wissen in der es steckt. Bis hin zur Reihenfolge der Elemente dort. Und alle diese Kind-Widgets müssen über das gleiche Wissen verfügen. Das steckt in der falschen Ebene. Wenn ein Kind Informationen hat die es loswerden möchte, dann löst es Signale dafür aus. Die kann man dann mit welchem Objekt auch immer, das darauf reagieren soll, verbinden.
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
Nobuddy
User
Beiträge: 1015
Registriert: Montag 30. Januar 2012, 16:38

__blackjack__, danke für Deinen Input!

Habe Python 3 und habe dies bei "super" gleich korrigiert.
parent habe ich durch obj ersetzt, damit QObject keinen Koflikt bekommt.

Das mit dem ValueError bei "focusOutEvent" , kann ich ehrlich gesagt auch nicht mehr nachvollziehen.
Ist aber behoben, kein ValueError mehr.
Habe self.parent.focusWidget() in self.focusWidget() geändert, was gleich funktioniert.

Das mit QMouseEvent in mousePressEvent() ist geändert.
"button" ist wirklich überflüssig.
Danke für Deinen Code-Vorschlag!

Das Attribut "keyRelease", dient zur Steuerung wie weiter verfahren wird.
Tab und Taste down gehen abwärts zum nächsten Widget.
Bei der up Taste und dem Klick mit der Mause, hatte ich Probleme mit Fehlermeldungen.
Dies ist aber behoben.

Ich sehe, dass ich da noch an einiges ran muss, damit das ganze Hand umd Fuß hat!

Danke mal für den Input!
Melde mich wieder bei neuen Fragen dazu!

Grüße Nobuddy
Benutzeravatar
__blackjack__
User
Beiträge: 13919
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Nobuddy: Was das vor und zurückgehen angeht, scheint mir du scheinst da Funktionalität von Qt nachbauen zu wollen. Man kann ja bereits mit Tab und Shift+Tab den Eingabefokus bewegen. Wenn man das programmatisch machen möchte, beispielsweise wenn die Eingabetaste gedrückt wurde, oder bei den Cursortasten, dann kann man `focusNextChild()`, `focusPreviousChild()`, oder `focusNextPrevChild()` dafür verwenden.
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
Nobuddy
User
Beiträge: 1015
Registriert: Montag 30. Januar 2012, 16:38

@__blackjack__, das mit den Richtungstasten und der Maus, soll den Ablauf sicher stellen.
Eingabefelder haben erst die Berechtigung zur Eingabe, wenn das vorherige Feld ausgefüllt, dem Format entspricht und registriert ist.

Konnte Dank Deines Mitwirkens, so umsetzen:

Code: Alles auswählen

class TextInput(QLineEdit):
	def __init__(self, obj):
		super().__init__(obj)
		self.obj = obj
		self.installEventFilter(self)

	def focusInEvent(self, event):
		colorField(widget=self.focusWidget(), parent=self.obj)
		super().focusInEvent(event)

	def focusOutEvent(self, event):
		self.obj.lastCol = lastCol = max(
			0,
			self.obj.inputWidgets.index(self.focusWidget()) - 1,
		)
		colorField(
			widget=self.obj.inputWidgets[lastCol], parent=self.obj
		)
		super().focusOutEvent(event)

	def mousePressEvent(self, event):
		if event.type() == QEvent.MouseButtonPress:
			if event.button() == Qt.LeftButton:
				self.obj.inputControl('mouse')
		super().mousePressEvent(event)

	def eventFilter(self, widget, event):
		if event.type() == QEvent.KeyRelease and widget is self:
			key = event.key()
			i = self.obj.inputWidgets.index(self.focusWidget())
			if widget.text().strip() == '':
				if self.obj.objectName() in ['Login', 'Registration']:
					self.obj.Login.setEnabled(False)
					if not self.obj.secure:
						self.obj.Registration.setEnabled(False)
				elif self.obj.objectName() == 'Password':
					self.obj.Send.setEnabled(False)
				for w in self.obj.inputWidgets[
						(i if len(self.obj.inputWidgets)-1 == i
						else i+1):]:
					w.clear()
					colorField(widget=w, parent=self.obj)
			if key in (Qt.Key_Return, Qt.Key_Enter, Qt.Key_Down,
					Qt.Key_Up, Qt.Key_Tab):
				self.obj.inputControl(
					{
						Qt.Key_Return : 'return',
						Qt.Key_Enter : 'return',
						Qt.Key_Down : 'down',
						Qt.Key_Up : 'up',
						Qt.Key_Tab : 'down'
					}[key]
				)
		return QWidget.eventFilter(self, widget, event)
Da ist bestimmt noch nicht alles ok, aber besser als zuvor.
Benutzeravatar
__blackjack__
User
Beiträge: 13919
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Nobuddy: `parent` in `obj` umzubenennen macht es nicht weniger überflüssig, denn es gibt ja `parent()`.

Das ist immer noch auf dem falschen Objekt und man sollte weder `parent()` noch `obj` so benutzen. Ein einzelnes Eingabefeld sollte nicht wissen müssen was es sonst noch so in der Eingabemaske gibt, keinen ständigen Zugriff auf das Elternobjekt benötigen, und schon gar nichts über den Ablauf der Eingaben wissen was es nicht selbst betrifft.

Und insgesamt klingt das komisch. Also wenn da versucht wird eine GUI auf eine serielle Eingabe, wie eine Konsolenanwendung einzuengen, die nacheinander Dinge abfragt. Also gar nicht so wie der Benutzer eine GUI-Anwendung erwartet. Bei GUI-Anwendungen hat man nun mal die Wahl in welcher Reihenfolge man präsentierte Eingabefelder ausfüllt. Es macht wenig Sinn den Benutzer hier so komisch zu gängeln.
“Java is a DSL to transform big Xml documents into long exception stack traces.”
— Scott Bellware
Nobuddy
User
Beiträge: 1015
Registriert: Montag 30. Januar 2012, 16:38

@__blackjack__, danke für Dein Input ... da habe ich noch einiges vor mir.
Das mit parent(), kannte ich nicht.
Muss dazu sagen, dass ich das nur für mich mache um im Kopf fit zu bleiben und was dazu zulernen.
Werde mir das durchlesen, zum Glück gibts ja google translater.

Mein jetziger Stand:

Code: Alles auswählen

class TextInput(QLineEdit):
	def __init__(self, obj):
		super().__init__(obj)
		self.obj = obj
		self.installEventFilter(self)

	def focusInEvent(self, event):
		colorField(widget=self.focusWidget(), obj=self.obj)
		super().focusInEvent(event)

	def focusOutEvent(self, event):
		self.obj.lastCol = lastCol = max(
			0,
			self.obj.inputWidgets.index(self.focusWidget()) - 1,
		)
		colorField(
			widget=self.obj.inputWidgets[lastCol], obj=self.obj
		)
		super().focusOutEvent(event)

	def mousePressEvent(self, event):
		if event.type() == QEvent.MouseButtonPress:
			if event.button() == Qt.LeftButton:
				inputControl('mouse', self.obj)
		super().mousePressEvent(event)

	def eventFilter(self, widget, event):
		if event.type() == QEvent.KeyRelease and widget is self:
			key = event.key()
			if key in (Qt.Key_Return, Qt.Key_Enter, Qt.Key_Down,
					Qt.Key_Up, Qt.Key_Tab):
				keyRelease = {
					Qt.Key_Return : 'return',
					Qt.Key_Enter : 'return',
					Qt.Key_Down : 'down',
					Qt.Key_Up : 'up',
					Qt.Key_Tab : 'tab'
				}[key]
				inputControl(keyRelease, self.obj)
		return QWidget.eventFilter(self, widget, event)
melde mich wieder, wenn es was neues gibt!

Danke und Grüße
Nobuddy
Nobuddy
User
Beiträge: 1015
Registriert: Montag 30. Januar 2012, 16:38

Habe das mit parent() verstanden und dies bei mir aktualisiert.
Mein Code, konnte ich auch noch etwas reduzieren.

Code: Alles auswählen

class TextInput(QLineEdit):
	def __init__(self, parent):
		super().__init__(parent)
		self.installEventFilter(self)

	def focusInEvent(self, event):
		colorField(widget=self.focusWidget(), parent=self.parent())
		super().focusInEvent(event)

	def focusOutEvent(self, event):
		self.parent().lastCol = lastCol = max(
			0,
			self.parent().inputWidgets.index(self.focusWidget()) - 1,
		)
		colorField(
			widget=self.parent().inputWidgets[lastCol],
			parent=self.parent()
		)
		super().focusOutEvent(event)

	def mousePressEvent(self, event):
		if event.type() == QEvent.MouseButtonPress:
			if event.button() == Qt.LeftButton:
				inputControl('mouse', self.parent())
		super().mousePressEvent(event)

	def eventFilter(self, widget, event):
		if event.type() == QEvent.KeyRelease and widget is self:
			key = event.key()
			if key in (Qt.Key_Return, Qt.Key_Enter, Qt.Key_Down,
					Qt.Key_Up, Qt.Key_Tab):
				inputControl(key, self.parent())
		return QWidget.eventFilter(self, widget, event)
Danke für die Unterstützung!

Grüße Nobuddy
Antworten