QDataWidgetMapper how to clear mapped widgets when model is empty
-
@StarterKit
If the model emits a signal likedataChanged()
for whatever your "NULL" case is, then if you sayQDataWidgetMapper
does not act on the signal maybe you can subclass and handle it? If it does not emit a signal then it does not sound like aQDataWidgetMapper
issue. I suggest you test this.@JonB, but data haven't been changed. Why would a model emit anything?
I mean for the case of topic starter it might be a solution. But my case is slightly different. I have a table of data and user may move a cursor by one way or another (I mean data cursor, that tracks active row/record in the database table). As result different data should be shown in views and widgets. I.e. no change of data, only change of view should happen. So model has no reason to emit anything - it is precisely why QDataWidgetMapper was created - to make widgets aware of cursor's position change and supply a new piece of data to them. -
@JonB , I would be happy if you will show a small example of how to handle my case with help of delegate.
Because either I miss something crucial here or it isn't possible. My opinion is backed with the fact that I have a custom widget with integer property that handles value from a database. And everything works fine until it comes to a row with NULL value - the problem is that widget is never notified by QDataWidgetMapper in this case and it simply have no idea that it should display something new.
So I belive it isn't possible to handle this case via delegate. I would be happy to be wrong and learn something new from you.I remember out discussion happened by link you posted. But there we talked about opposite direction - when widget has NULL value and should put this value into database table. This indeed may be overridden (and I implemented it) because I know the moment when data are being saved to the database.
But here I'm talking about displaying the data from database (and actually more complex actions that might be hanled by custom widget) - and widget isn't notified about data change at all. And this is much bigger problem - you have no idea when row was changed in database. Yes, you may make some solution that will track user activity in some place and notify widgets - but it makesQDataWidgetMapper
a useless thing because it is big part of its destiny.@StarterKit said in QDataWidgetMapper how to clear mapped widgets when model is empty:
until it comes to a row with NULL value - the problem is that widget is never notified by QDataWidgetMapper in this case and it simply have no idea that it should display something new.
Then why don't you implement this?
-
@StarterKit said in QDataWidgetMapper how to clear mapped widgets when model is empty:
until it comes to a row with NULL value - the problem is that widget is never notified by QDataWidgetMapper in this case and it simply have no idea that it should display something new.
Then why don't you implement this?
@JonB, I feel I lost the point.
Lets me explain it again and then you may feel the gap if I have it somewhere.
For simplicity, let's have only 1 table with data.
This table is displayed in a couple of different table views.
There is a panel that has several widgets linked to currently selected row in a table and displays something to the user based on data present in current row.
I.e. user or application may "randomly" select a row in the table. And after this selection something is displayed to the user.I have widgets from the panel linked to the table via
QDataWidgetMapper
- so if row is changed then widgets are updated by means ofQDataWidgetMapper
and perform their logic (And this happens well until we hit a NULL integer value).
I might be wrong but AFAIKQSqlTableModel
orQDataWidgetMapper
doesn't have a signal that would be fired after row change.
So currently I have quite a simple link - widgets-mapper-table that works on its own. What you propose me is to monitor status of views linked to the table and then update widgets? is it right? Then it will create a lot of interconnections between unrelated modules that I really would like to avoid.... -
@JonB, I feel I lost the point.
Lets me explain it again and then you may feel the gap if I have it somewhere.
For simplicity, let's have only 1 table with data.
This table is displayed in a couple of different table views.
There is a panel that has several widgets linked to currently selected row in a table and displays something to the user based on data present in current row.
I.e. user or application may "randomly" select a row in the table. And after this selection something is displayed to the user.I have widgets from the panel linked to the table via
QDataWidgetMapper
- so if row is changed then widgets are updated by means ofQDataWidgetMapper
and perform their logic (And this happens well until we hit a NULL integer value).
I might be wrong but AFAIKQSqlTableModel
orQDataWidgetMapper
doesn't have a signal that would be fired after row change.
So currently I have quite a simple link - widgets-mapper-table that works on its own. What you propose me is to monitor status of views linked to the table and then update widgets? is it right? Then it will create a lot of interconnections between unrelated modules that I really would like to avoid....@StarterKit QDataWidgetMapper emits the currentIndexChanged signal. I don't believe a model would have a reason to track "current index". I think what Jon is suggesting is to subclass QDataWidgetMapper to get it to properly update when it comes across nulls?
-
@JonB, I feel I lost the point.
Lets me explain it again and then you may feel the gap if I have it somewhere.
For simplicity, let's have only 1 table with data.
This table is displayed in a couple of different table views.
There is a panel that has several widgets linked to currently selected row in a table and displays something to the user based on data present in current row.
I.e. user or application may "randomly" select a row in the table. And after this selection something is displayed to the user.I have widgets from the panel linked to the table via
QDataWidgetMapper
- so if row is changed then widgets are updated by means ofQDataWidgetMapper
and perform their logic (And this happens well until we hit a NULL integer value).
I might be wrong but AFAIKQSqlTableModel
orQDataWidgetMapper
doesn't have a signal that would be fired after row change.
So currently I have quite a simple link - widgets-mapper-table that works on its own. What you propose me is to monitor status of views linked to the table and then update widgets? is it right? Then it will create a lot of interconnections between unrelated modules that I really would like to avoid....@StarterKit You've peaked my curiosity... because I realized your specific spinbox problem was going to be my problem as well very soon. I've come up with a work around that seems to work at the moment. I subclassed QSpinBox and created a custome property, value2, that accepts a string instead of an integer. If the mapper attempts to call the setter function with an empty string then the function will intercept this and convert it to zero.
from PySide6.QtCore import QByteArray, Property from PySide6.QtWidgets import QSpinBox class MySpin(QSpinBox): def readValue2(self): return self.value() def setValue2(self, val): if not val: self.setValue(0) else: self.setValue(int(val)) value2 = Property(str, readValue2, setValue2)
Use the overloaded addMapping function to map to this custom property like this:
mapper.addMapping(my_custom_spin, 3, QByteArray("value2"))
The root of the issue seems to be is that the datamapper by default maps to the 'user' property, which is 'value' for a spinbox. The value property is strictly an integer type. Because mapper sees an integer property and it doesn't know how to convert null to integer it doesn't bother and skips updating the value property. But we've observed mapper will convert nulls to an empty string for text boxes... So trick mappper to sending a string by mapping to a string type property instead.
Edit: realizing Jon suggested the approach should be to create a custom delegate Id really like to see how a pro would handle this! I still haven't fully wrapped my head around how/when to use delegates.
-
@StarterKit
I looked at your bug report. It is rather different from the issue we were discussing, viz. "unbind" aQDataWidgetMapper
in the sense of return it to its original state.You are now asking for all Qt data-mappable widgets to support the display/entry of backend database NULL values. Possibly regrettably that has never been the case, and I would guess is unlikely to be introduced as a result of your bug report. You are expected to write this yourself via an item delegate and suitable code in
setEditorData()
&setModelData()
.See also https://forum.qt.io/topic/131088/pyside6-qdatawidgetmapper-how-to-store-null-from-mapped-widget-into-db and https://www.qtcentre.org/threads/35832-Custom-QLineEdit-to-store-NULL-with-QDataWidgetMapper.
@JonB said in QDataWidgetMapper how to clear mapped widgets when model is empty:
...You are expected to write this yourself via an item delegate and suitable code in
setEditorData()
&setModelData()
.Could it be this simple?:
class MyDelegate(QStyledItemDelegate): def setEditorData(self, editor, index): if isinstance(editor, QSpinBox) and not index.data(): editor.setValue(0) return super().setEditorData(editor, index)
I suppose I should read through the links you posted.
-
@StarterKit QDataWidgetMapper emits the currentIndexChanged signal. I don't believe a model would have a reason to track "current index". I think what Jon is suggesting is to subclass QDataWidgetMapper to get it to properly update when it comes across nulls?
@BamboozledBaboon said in QDataWidgetMapper how to clear mapped widgets when model is empty:
I think what Jon is suggesting is to subclass QDataWidgetMapper to get it to properly update when it comes across nulls?
If the mapper attempts to call the setter function with an empty string then the function will intercept this and convert it to zero.
Because mapper sees an integer property and it doesn't know how to convert null to integer it doesn't bother and skips updating the value property.
Yes to all of these!
Could it be this simple?:
Does it work? If so, yes!
The only thing I would say: You are actually setting the editor to
0
when data is "NULL". This means the user cannot see the difference between a genuine0
versus a "NULL" shown as a0
. And particularly when the user is in edit mode and submitting the record code (e.g. viasetModelData()
) won't know when it sees a0
whether to submit that or "NULL" to the database.In the case of a
QSpinBox
I believe you could empty out the text of the number to show as "blank", and accept "blank" input as meaning submit "NULL" to the database. It would mean that you must not use any standard integer validator on it.I think you may have to use
findChild<QLineEdit *>(spinBox)
to access the text. Another possibility might be to employ an unused value, e.g.-1
if appropriate, and combine withspecialVlaueText()
. There is alsovalueFromText()
andtextFromValue()
.Sometimes this does not work for the widget type. I recall having a
QDateEdit
for dates. But these could be left "unfilled" on the form, stored as "NULL" in the database.QDateEdit
does not allow you to "blank it out" in any way. Then you need the delegate to add something to allow for "NULL". For example, a checkbox for "NULL"/"empty"/"no date". And maybe disable/enable the date edit according as that box is checked/unchecked. -
@BamboozledBaboon said in QDataWidgetMapper how to clear mapped widgets when model is empty:
I think what Jon is suggesting is to subclass QDataWidgetMapper to get it to properly update when it comes across nulls?
If the mapper attempts to call the setter function with an empty string then the function will intercept this and convert it to zero.
Because mapper sees an integer property and it doesn't know how to convert null to integer it doesn't bother and skips updating the value property.
Yes to all of these!
Could it be this simple?:
Does it work? If so, yes!
The only thing I would say: You are actually setting the editor to
0
when data is "NULL". This means the user cannot see the difference between a genuine0
versus a "NULL" shown as a0
. And particularly when the user is in edit mode and submitting the record code (e.g. viasetModelData()
) won't know when it sees a0
whether to submit that or "NULL" to the database.In the case of a
QSpinBox
I believe you could empty out the text of the number to show as "blank", and accept "blank" input as meaning submit "NULL" to the database. It would mean that you must not use any standard integer validator on it.I think you may have to use
findChild<QLineEdit *>(spinBox)
to access the text. Another possibility might be to employ an unused value, e.g.-1
if appropriate, and combine withspecialVlaueText()
. There is alsovalueFromText()
andtextFromValue()
.Sometimes this does not work for the widget type. I recall having a
QDateEdit
for dates. But these could be left "unfilled" on the form, stored as "NULL" in the database.QDateEdit
does not allow you to "blank it out" in any way. Then you need the delegate to add something to allow for "NULL". For example, a checkbox for "NULL"/"empty"/"no date". And maybe disable/enable the date edit according as that box is checked/unchecked.Hi guys,
@JonB, @BamboozledBaboon thank you for discussion. Things your mentioned are clear for me and I did it previously one way or another so these things should work well.Just to add some comments from my side.
@JonB, you are right - we should distinguish "Zero" and "NULL" values at a widget level. But it isn't a big issue - if your design requires DB to have both, "Zero" and "NULL" values, then your widget should be capable to distinguish and handle it. It is up to you how to implement it - for example, I did a separate button (that is part of custom widget) that sets the widget to NULL value implicitly.
@BamboozledBaboon, you proposal with a string property is clear and it should work for sure (just I would prefer to use
fieldIndex()
method to get an index of a field foraddMapping()
as names are a bit more consistent then order of fields).But... I dislike an idea of having second string property very much. While it works it makes code really dirty with unnecessary duplication.
My strong opinion - it is a bug, because it isQDataWidgetMapper
who does the job and it should do it for any value that may be present in database. And as NULL is a valid value - it should handle it one way or another, not ignore it.Anyway, thanks both of you for this discussion and ideas how to overcome it because looking at number of unresolved bugs in Qt Widgets I feel it might take a looong time to have it corrected.
-
Hi guys,
@JonB, @BamboozledBaboon thank you for discussion. Things your mentioned are clear for me and I did it previously one way or another so these things should work well.Just to add some comments from my side.
@JonB, you are right - we should distinguish "Zero" and "NULL" values at a widget level. But it isn't a big issue - if your design requires DB to have both, "Zero" and "NULL" values, then your widget should be capable to distinguish and handle it. It is up to you how to implement it - for example, I did a separate button (that is part of custom widget) that sets the widget to NULL value implicitly.
@BamboozledBaboon, you proposal with a string property is clear and it should work for sure (just I would prefer to use
fieldIndex()
method to get an index of a field foraddMapping()
as names are a bit more consistent then order of fields).But... I dislike an idea of having second string property very much. While it works it makes code really dirty with unnecessary duplication.
My strong opinion - it is a bug, because it isQDataWidgetMapper
who does the job and it should do it for any value that may be present in database. And as NULL is a valid value - it should handle it one way or another, not ignore it.Anyway, thanks both of you for this discussion and ideas how to overcome it because looking at number of unresolved bugs in Qt Widgets I feel it might take a looong time to have it corrected.
@StarterKit said in QDataWidgetMapper how to clear mapped widgets when model is empty:
if your design requires DB to have both, "Zero" and "NULL" values, then your widget should be capable to distinguish and handle it.
Just to wrap up. This is the nub of the problem. There seem to be just 3 possibilities:
- The widget itself should allow for a "NULL" value, somehow.
QDataWidgetMapper
supplied should "modify" all widgets it uses to add a facility for showing/entering "NULL", somehow.- You should modify
QDataWidgetMapper
(e.g. in an item delegate) to deal with "NULL" yourself.
Now,
QDataWidgetMapper
simply is not supplied with #2. In the Qt approach of "KISS" QDWM just deals with values which can be mapped to the widget, as supplied. Since the widget does not handle "NULL" the straightforward implementation of QDWM does not either. I do not see that as a failing of QDWM.Of course the "best" would be if all the base widgets allowed for "NULL"/empty. But they don't. Like I said, you may be able to do it for a
QSpinBox
by leaving it "empty", but take the case ofQDateEdit
and it's impossible. And if it did allow for this you could not simply have a widget value type ofint
orQDate
, you would either have to change that to e.g. aQVariant
or have someisEmpty()
method. So it would be a not inconsiderable change to all widgets. -
@StarterKit said in QDataWidgetMapper how to clear mapped widgets when model is empty:
if your design requires DB to have both, "Zero" and "NULL" values, then your widget should be capable to distinguish and handle it.
Just to wrap up. This is the nub of the problem. There seem to be just 3 possibilities:
- The widget itself should allow for a "NULL" value, somehow.
QDataWidgetMapper
supplied should "modify" all widgets it uses to add a facility for showing/entering "NULL", somehow.- You should modify
QDataWidgetMapper
(e.g. in an item delegate) to deal with "NULL" yourself.
Now,
QDataWidgetMapper
simply is not supplied with #2. In the Qt approach of "KISS" QDWM just deals with values which can be mapped to the widget, as supplied. Since the widget does not handle "NULL" the straightforward implementation of QDWM does not either. I do not see that as a failing of QDWM.Of course the "best" would be if all the base widgets allowed for "NULL"/empty. But they don't. Like I said, you may be able to do it for a
QSpinBox
by leaving it "empty", but take the case ofQDateEdit
and it's impossible. And if it did allow for this you could not simply have a widget value type ofint
orQDate
, you would either have to change that to e.g. aQVariant
or have someisEmpty()
method. So it would be a not inconsiderable change to all widgets.@JonB I respect your summary and I think it is a correct one.
But let me disagree with you aboutQDataWidgetMapper
behavior. I have nothing against "KISS" but the behavior should be also consistent and clear.
As I see it is:
A) inconsistent - as for some datatypes we have a mapping for NULL and for others have not. There might be different views but down to underlying bits - empty string and NULL are not the same values, so here we definitely have an adaptation and translation. But we don't have it for integer.
B) unclear - the documentation says nothing about someting won't be updated. There are only this text "Every time the current index changes, each widget is updated with data from the model via the property specified when its mapping was made." So it is always a surprise that calls for some kind of ad-hoc workarounds (instead of a proper design).Summarizing - I think the best what should be done:
- all datatypes should be treated equally (either with some translation or ingnoring NULL for all of them).
- documentation should have a clear statement about how it actually works.
I think I should update my bug reports with this kind of proposal.