Sub-classing QlineEdit
-
A while ago - in another thread - @JKSH suggested I could subclass QLineEdit for a specific purpose.
I decided to try, but I can't quite get it to work. The code is posted below.
In this case I don't use the model and mapper from Qt. I run a SQL query and use the value property to get my data into the object. That works.But when I type into the widget, the value does not change. The text property does change, but not value. This I don't understand.
How does my typing change the object? To me, it looks like my class definition is ignored, and QLineEdit.setText() is used.
from PyQt5 import QtWidgets,QtGui, QtCore import locale from PyQt5.QtCore import pyqtProperty #******************************************************************************* # # A subclass of QLineEdit which can be used to display decimal numbers only. # The numbers are displayed according to locale and always with a # fixed number of decimals # A validator is in place to ensure that only valid flots are entered by User # #******************************************************************************* class DblEdit (QtWidgets.QLineEdit): def __init__(self, parent): super().__init__(parent) self._decChar = QtCore.QLocale().decimalPoint() self._decimals = 2 self._minimum = 0 self._maximum = 9999 self._dblVal = QtGui.QDoubleValidator(self) self._dblVal.setNotation(QtGui.QDoubleValidator.StandardNotation) self._dblVal.setRange(self._minimum, self._maximum, self._decimals) self.setValidator(self._dblVal) self._value = 0.0 #------------------------------------------------------------------------------- # Private version of setText #------------------------------------------------------------------------------- def _setText(self, text): super().setText(text) #------------------------------------------------------------------------------- # Private method to pad text with 0 after decimals if necessary #------------------------------------------------------------------------------- def _fillDecimals(self, lacks): if lacks <= 0: return else: self._setText(self.text() + '0') self._fillDecimals(lacks-1) #------------------------------------------------------------------------------- # setText not to be used #------------------------------------------------------------------------------- def setText(self, text): raise Exception('Method setText not available. Set value property') #------------------------------------------------------------------------------- # getter for _value #------------------------------------------------------------------------------- @pyqtProperty(float,user=True) def value(self): print ('You are in value') return self._value #------------------------------------------------------------------------------- # Setter for value should be used instead of setText . # Supply a float as argument #------------------------------------------------------------------------------- @value.setter def value(self, d): if str(d).strip(' ') == '' or d == None: self._value= None self._setText = '' else: self._value = round(float(d), self._decimals) self._setText(locale.str(self._value).strip()) #find position of decimal characer pos = self.text().find(self._decChar,0,len(self.text())) if pos == -1: self._setText(self.text()+self._decChar) lacksDecimals = self._decimals else: lacksDecimals = self._decimals - ( len(self.text())-(pos+1)) self._fillDecimals(lacksDecimals) print ('You are in setValue', self.text(), self.value) #------------------------------------------------------------------------------- # Method to specify how many decimals are wanted #------------------------------------------------------------------------------- def setDecimals(self, i): self._decimals = int(i) #------------------------------------------------------------------------------- # Method to set range of values for validator #------------------------------------------------------------------------------- def setRange(self, min, max): self._minimum = min self._maximum = max #------------------------------------------------------------------------------- # some getters #------------------------------------------------------------------------------- def decimals(self): return self._decimals def minimum(self): return self._minimum def maximum(self): return self._maximum
-
@bkvldr
I'm not sure, so please take my remark with a pinch of salt, but: internal Qt code will feel free to callQLineEdit::setText()
. I am then unsure whether it will resolve to the baseQLineEdit::setText()
, or whether it will call your override which will thenraise
[I would put aprint
in there, just in case it's getting called form somewhere with exceptions trapped], but either way that does not sound your desired behaviour? I don't think you can afford to try to preventsetText()
being called on your subclassed widget.if str(d).strip(' ') == '' or d == None:
You're supposed to write that withd is None
. Probably not relevant to your question. Why you choose to firststr()
it and then test forNone
I don't know, to me it would be cleaner to reverse those tests, but up to you :) -
I finally got some time to play with this. In keyPressEvent, I set self._value. This keeps self.text() and self._value in sync as I type. I am still not quite comfortable, because I don't know how my typing enters into my class.
If you look at my original post, you'll see that it cannot be through the DblEdit.setText() .from PyQt5.QtCore import pyqtSlot from PyQt5.QtGui import QDoubleValidator from PyQt5.QtWidgets import QApplication, QWidget, QLineEdit, QVBoxLayout import locale class DblEdit (QLineEdit): def __init__(self, Id): QLineEdit.__init__(self) self.Id = Id self.Decimals = 2 self.Minimum = 0 self.Maximum = 9999 self._value = 0.0 self.DblVal = QDoubleValidator(self) self.DblVal.setNotation(QDoubleValidator.StandardNotation) self.DblVal.setRange(self.Minimum, self.Maximum, self.Decimals) self.setValidator(self.DblVal) @pyqtSlot(object) def keyPressEvent(self, event): print('Captured Key Press Event in',self.Id,') ',event.text()) QLineEdit.keyPressEvent(self, event) if self.text() == '': self._value = 0.0 else: self._value = locale.atof(self.text()) class MainWindow(QWidget): def __init__(self): QWidget.__init__(self) self.setWindowTitle('Muck With It') Top=300; Left=700; Width=250; Hight=75 self.setGeometry(Left, Top, Width, Hight) self.DblOne = DblEdit(1) self.DblTwo = DblEdit(2) VBox = QVBoxLayout() VBox.addWidget(self.DblOne) VBox.addWidget(self.DblTwo) VBox.addStretch(1) self.setLayout(VBox) if __name__ == "__main__": MainEventHandler = QApplication([]) locale.setlocale(locale.LC_ALL, '') application = MainWindow() application.show() MainEventHandler.exec()