PDA

View Full Version : Adding validation to a QListView's edit mode



MattT
25th July 2010, 12:21
I want to create a QListView that validates the data entered in edit mode before saving it to the model.

The user workflow I envision for it is:
The user double clicks an item in the QListView to go into edit mode
The user enters in invalid data into the edit widget for the item, then signals that he is done editing (by, i.e., hitting enter, or changing focus away from the edit widget, or whatever.)
The application checks the data, sees that it's invalid, and displays an error dialog to the user. The edit widget is kept around and focus is returned to it when the dialog is dismissed, so that the user can continue editing his data.
When the user has entered valid data, and signals that he is done editing, the application validates it, then saves the data to the model and hides the edit widget as normal.

After mucking around in the source for QAbstractItemView and QStyledItemDelegate, I have found that this is a mess to implement. The delegate has a non-virtual eventFilter function that it uses to determine when the user has finished editing. It then sends two signals to the ItemView: The first causes the ItemView to tell the delegate to save its data to the model; the second signal causes the ItemView to destroy the edit widget.

So I've figured I either need to somehow hook the delegates eventFilter() which detects when editing is done, and cause it to validate the data before sending the commit data & destroy widget signals; or I can hook into QAbstractItemView's setItemDelegate(), which is responsible for wiring up the slots for the commit & destroy signals, and hopefully wire those signals into my validation slots, which will then pass on the signals if the data is valid. I'd rather do the latter. But the catch is neither function is virtual!

So how can I get this to work? Overriding the non-virtual function won't work, will it? And the only other thing I can think of is to copy&paste all 4,000+ lines of code for QAbstractItemDelegate, add the "virtual" keyword before setItemDelegate(), rename the class, and compile it into a new module. Besides being a PITA, this will also mean I lose out on updates to QAbstractItemDelegate -- so if Nokia updates it significantly in the next version of Qt, I'm out of luck.

Is there a better way to do this?

(I've tried validating the data in the model and, if invalid, calling ListView::edit() on the item again, but that doesn't seem to work. I can get the model to not save the data, of course, but can't find a way to let the user keep editing that data.)

Lykurg
25th July 2010, 12:38
Can't you use a normal delegate and add a custom editor to it. Then after the user has finished his input, QStyledItemDelegate::setModelData() is called. There check for validity (you should also be able to display a dialog) and set the data.

MattT
29th July 2010, 19:56
The main problem with that is that setModelData() is called based (indirectly) on the commitData() signal being emitted by the delegate, which is always emitted right before closeEditor(). So I can stop the data from being written, but not the editor from being closed.

I did discover that eventFilter() was actually virtual, so I have tried overriding that. I also hooked the editor's editingFinished signal in my createEditor() function (inspired by the Programming Qt 4 book.) It kinda works, but has some bugs.

First, when the window is being closed with the editor open, the "input error" dialog fires like 3 times, presumably because editingFinished fires a bunch of times.

Second, I can't get the focus to work correctly. If I hit 'enter' in the editor, the error fires and focus stays on the editor correctly, but if I try to close the editor based on it losing focus, the editor doesn't keep focus. I have editor->setFocus() called (I even tried using a queued dispatch of that signal to editor.) I have also tried swollowing the FocusOut event in the editor's eventFilter(), to no effect. The editor retains the text input cursor, but the actual focus goes to whatever was clicked. How can I change that?

GreenScape
29th July 2010, 20:26
why does you can't set your own editor which commits data(and closes) only when it's valid? reimplement delegates createEditor(...); method and create desired editors.

MattT
30th July 2010, 08:21
That's what I'm doing right now -- I have my own custom delegate which uses a QLineEdit widget as its editor, then hooks into that widget's evenFilter to tell when the user has pressed enter/esc or the widget has lost focus. It works great for keeping the widget open and not saving the data when it is invalid, but the problem is that last one: losing focus. When my widget gets a FocusOut event, I try to prevent it from losing focus, but it doesn't work.

It also has a bug where it gets a bunch of FocusOut events when the window is closing, which causes my validation code to fire 3 times, and 3 "invalid input" error boxes to popup one after the other, right as the winow should be closing.

GreenScape
30th July 2010, 16:26
That's what I'm doing right now -- I have my own custom delegate which uses a QLineEdit widget as its editor, then hooks into that widget's evenFilter to tell when the user has pressed enter/esc or the widget has lost focus. It works great for keeping the widget open and not saving the data when it is invalid, but the problem is that last one: losing focus. When my widget gets a FocusOut event, I try to prevent it from losing focus, but it doesn't work.

It also has a bug where it gets a bunch of FocusOut events when the window is closing, which causes my validation code to fire 3 times, and 3 "invalid input" error boxes to popup one after the other, right as the winow should be closing.

i sad. your own editor. i mean subclass from QLineEdit and reimplement event filter, then send editingFinished(); signal only when your data is valid.
and for future, read more Qt's documentation. and examples, like this (http://doc.trolltech.com/4.3/itemviews-stardelegate.html).

MattT
31st July 2010, 00:26
Even if I create a widget, I still have the problem with losing focus. Creating a new widget doesn't solve the problem, it only makes my problem with delegates a problem with widgets.

The problem isn't with not sending the edtingFinished() signal -- I have a version of my code which doesn't even use that signal, and uses an event filter on the widget to detect when the user is done editing (presses 'enter' or 'esc', or the widget loses focus) -- it's with getting my widget to retain focus, and at having the FocusOut events fire when the window is closing, causing my "invalid data" error box to pop up a bunch.

Also, I think your link should be to here (http://doc.trolltech.com/4.6/itemviews-stardelegate.html) ;) You should always check that you're using the latest version of the Qt docs. As you can see, that example has been updated to use the QStyledItemDelegate now.

GreenScape
31st July 2010, 18:49
I have a version of my code which doesn't

QLineEdit automatically sends this signal, read (http://doc.qt.nokia.com/4.6/qlineedit.html#editingFinished).

MattT
31st July 2010, 19:56
Yes, but it isn't hooked up to anything by default. The signals that cause the editor to close are commitData and closeEditor, emitted by the delegate. Check the code for QListView/QAbstractItemView, you'll see. I have one version of my code where I hook that signal into one of my delegate's slots in order to validate the data and then, if valid, emit commitData and closeEditor to the ListView and, if not valid, open up an error dialog. The other version of my code doesn't listen for editingFinished, and just uses the QLineEdit's event filter to try to determine when the user has pressed enter, esc or the editor has lost focus. If it does it emits commitData and closeEditor.

GreenScape
31st July 2010, 21:29
if not valid, open up an error dialog

and do not close editor inside cell?

you say you have 2 versions. both are not working?

MattT
1st August 2010, 01:53
Yup, now we're on the same page.

The problem isn't with the editor closing -- I can prevent that fine. The problem is with the editor losing focus. How do I keep focus on the editor, even when the user clicks on another widget?