QComboBox keeps storing duplicate strings entered by the user, even if I call its member function QComboBox::setDuplicatesEnabled(false).
How can I store single copies of the strings even when the user inserts duplicates?
From Qt documentation:
duplicatesEnabled : bool
This property holds whether the user can enter duplicate items into the combobox.
Note that it is always possible to programmatically insert duplicate items into the combobox.
By default, this property is false (duplicates are not allowed).
Access functions:
bool duplicatesEnabled () const
void setDuplicatesEnabled ( bool enable )
As the documentation says:
This property holds whether the user can enter duplicate items into
the combobox. Note that it is always possible to programmatically
insert duplicate items into the combobox.
So this option doesn't affect string you set programmatically. You need to remove duplicates from your list manually.
For example, if you're storing your list in QStringList, duplicates can be easily removed using list = list.toSet().toList().
you need to check, if the userinput is valid (not duplicated or not) and catch the void editTextChanged ( const QString & text ) signal.
you could also derive your own class from QComboBox and overload the void keyPressEvent(QKeyEvent* event) // may be not the correct name
Related
My PyQt5 application contains a bunch of input fields of different types: QLineEdit, QSpinBox, QComboBox, QTableView etc.
I want to alert the user if they input data or change the content of one or more of the fields and try to close the window without saving.
Do I have to connect all the different variations of the textChanged signal to some kind of bookkeeping function, or is there an easier way?
EDIT: More details on sequence of events:
The UI gets build from a Qt Designer .ui file, so some fields have default values (like QSpinBox, QDateEdit)
The models of different QTableViews get initialized with certain default data structures, like a 2d array of None, or a dict whose keys all return None
A bunch of documents get loaded from a document store, and the fields are set to the values of these documents. It might happen that no corresponding key exists in the document, so the field won't be set. But it's also possible that a value in the documents just happens to be the default value.
The user changes the content of some of the fields, and on saving the document in the store will be updated accordingly.
I want to be able to tell if any field has been modified after step 3 which is to say a user made change. I'd like to avoid having to compare all fields against the document store.
For simple widgets that store a single property, the solution is to user the user property of the meta object of each widget.
Item views require a custom checking to compare the model.
You need to create a list of the widgets you need to monitor for changes, get the value during initialization and then verify it when closing.
To simplify things, you can set a dynamic property for all widgets that need checking, so that you can iter through all widgets, check if the property is set, and add those widgets to the list. To add a custom property to a widget in Designer, select the widget and click on the "+" symbol in the property editor; in the following example I used a boolean property ("validate") with a basic truthfulness check: remember that properties that are not set return None, so in this case you must also set the property to True.
class Test(QtWidgets.QDialog):
def __init__(self, parent=None):
super().__init__(parent)
uic.loadUi('mapper.ui', self)
self.loadData()
def loadData(self):
# some stuff setting the initial values
# ...
# save the current values
self.fieldData = []
for widget in self.findChildren(QtWidgets.QWidget):
if not widget.property('validate'):
continue
if isinstance(widget, QtWidgets.QAbstractItemView):
model = widget.model()
if not model:
continue
data = []
for row in range(model.rowCount()):
rowData = []
data.append(rowData)
for column in range(model.columnCount()):
rowData.append(model.index(row, column).data())
self.fieldData.append((widget, data))
else:
property = widget.metaObject().userProperty()
self.fieldData.append((widget, widget.property(property.name())))
def ignoreChanges(self):
for widget, default in self.fieldData:
if isinstance(widget, QtWidgets.QAbstractItemView):
model = widget.model()
for row, rowData in enumerate(default):
for column, itemData in enumerate(rowData):
if model.index(row, column).data() != default:
break
else:
property = widget.metaObject().userProperty()
if widget.property(property.name()) != default:
break
else:
return True
res = QtWidgets.QMessageBox.question(self, 'Ignore changes',
'Fields have been modified, do you want to ignore?',
QtWidgets.QMessageBox.Ok|QtWidgets.QMessageBox.Cancel)
if res != QtWidgets.QMessageBox.Ok:
return False
return True
def reject(self):
if self.ignoreChanges():
super().reject()
def closeEvent(self, event):
if not self.ignoreChanges():
event.ignore()
I have modified a validate method of form control. On this control I'm typing the product name.
In validate method I'm checking if this product name exists in the table. If it does not exists the error is thrown.
My issue is that after the error is thrown I want to clear control. Here is my code:
public boolean validate()
{
InventTable inventTable;
boolean ret = super();
select inventTable
where inventTable.nameAlias == this.text();
if (!inventTable.recid)
{
error("error");
this.text("");
}
return ret;
}
this.text(""); does not work. So how can I clear the control? The control is a field from my datasource.
In validate methods you do not need to clear the field. The system does that for you when validate returns false.
So instead of this.text('')) just return false.
But I doubt that the idea of users entering the full name is really useful at all.
If you use NameAlias as an alternate item number an even easier option exist.
Change the AliasFor property on the InventTable.NameAlias field to point to ItemId.
When entering in an ItemId and you enter a NameAlias instead, it is translated to the corresponding item id by the AX run-time. This happens everywhere an item id is entered and validated.
In QAbstractItemModel, some functions, such as beginInsertRows, beginRemoveRows, can be used to insert and remove rows. But how to implement replacing a row item by another one?
If I understand you right, you need to notify subcribed views about data changing (one row replaced by new, for views it means that data has been changed) for the specified model index:
// let's the row is index that we want to invalidate
QVector<int> roles;
roles << Qt::DisplayRole;
emit dataChanged(index(row, 0), index(row, columnCount()-1), roles);
If you want to change a lot of data, you should do something like this:
beginResetModel();
// change data
endResetModel();
If you change just one row, emitting dataChanged() should do the trick.
Is it possible to define a property to limit the number of elements which will appear in a mx:List ? I've read about setting the property rowCount, but I don't see any effect.
Can a filter be applied to accomplish this? My intention was to avoid removing the items from the list/array collection, but simply "hide" them. Can this be done?
You can "hide" items from display in a List-based class, without modifying your underlying source data, by using a Collection class, such as an ArrayCollection, and filtering the data.
Read these docs on Collection filtering.
To quote:
You use a filter function to limit the data view in the collection to
a subset of the source data object. The function must take a single
Object parameter, which corresponds to a collection item, and must
return a Boolean value specifying whether to include the item in the
view. As with sorting, when you specify or change the filter function,
you must call the refresh() method on the collection to show the
filtered results. To limit a collection view of an array of strings to
contain only strings starting with M, for example, use the following
filter function:
public function stateFilterFunc(item:Object):Boolean
{
return item >= "M" && item < "N";
}
A different option is to use a new arraycollection and get your limited items from your big arraycollection :
//get first 10 elements
myArrayCollection = new ArrayCollection( myBigArrayCollection.toArray().slice(0,9) );
if you want to work with pagers, you could hold a counter where you keep track of what page the user is on, and get the next elements from you big array collection. example:
//this is just a (very) simple example
//page = integer (counter) for knowing which page the user is on
page = 0;
page_low = page*10;
page_high = page_low + 9;
myArrayCollection = new ArrayCollection( myBigArrayCollection.toArray().slice(page_low,page_high) );
(still using a filter is a more elegant solution)
I have QTableView which has QComboBox in one of the columns. The combobox is displaying data from a vector which get updates when I click a button.
When I start the application the combobox displays all the items in vector. Now I press the button (which adds more items to the vector) but the combobox doesn't reflect new data in vector. It still shows old data. I am also emitting dataChanged() once the vector is updated but I don't see any change. data() function does get call in the model which does return all the elements of the vector, but setEditorData doesn't get call in delegate.
Am I missing something.
Thanks,
Dev
Then you need to do something like this function:
void updateComboBox(QComboBox *comboToUpdate, const QStringList & list )
{
QString curentText = comboToUpdate->currntText();
comboToUpdate->clear();
comboToUpdate->insertItems(list);
comboToUpdate->setCurrentIndex(comboToUpdate->findText(currentText));
}
Lines
QString currentText = comboToUpdate->currentText();
...
comboToUpdate->setCurrentIndex(comboToUpdate->findText(currentText));
are optional and used to don't change currentItem after selection.