I am trying to get the id of the record in the model from a QCombobox with findData(index), but when select a item, it retunrs -1. It has been working in another project, but this is the second one that doesn't work. Here's my code:
modAnfi = new QSqlTableModel(this);
modAnfi->setQuery("SELECT id, (nombres || ' ' || apellidos) as Nombre, nombres, apellidos FROM tbPersonas WHERE activo=1");
comboAnfitrion->setModel(modAnfi);
comboAnfitrion->setModelColumn(1);
comboAnfitrion->setEditable(true);
comboAnfitrion->completer()->setCompletionMode(QCompleter::PopupCompletion);
connect(comboAnfitrion, SIGNAL(currentIndexChanged(int)), this, SLOT(currentIndexChangeAnfitrion(int)));
and:
void controlReg::currentIndexChangeAnfitrion(int index)
{
qDebug() << comboAnfitrion->findData(index); // -1
qDebug()<< comboAnfitrion->itemData(1); // QVariant(Invalid)
}
Thanks for your time, any help will be appreciated.
You have to use the model you assign to the comboBox, use the index to look for it:
modAnfi->data(modAnfi->index( index, 0));
Check the QComboBox documentation; from the findData description, quoting:
Returns the index of the item containing the given data
Where you are passing index as the "given data". However, index is already an index in the combobox. But you're obviously not looking for an index (since you already have one).
I suspect you actually want to call the itemData method instead? That would retrieve the data associated with an element for a given index.
Related
I have a QStandardItemModel with a single column (represents a list). Each item in the list has a unique integer ID stored as the QStandardItem's data (via QStandardItem::setData which I guess is into Qt::UserRole+1 by default).
Given one of these IDs, I'd like to find and remove the corresponding row from the model. Right now I'm doing this:
void NetworkManager::removeSessionFromModel (QStandardItemModel *model, int sessionId) {
foreach (const QStandardItem *item, model->findItems("*", Qt::MatchWildcard)) {
if (item->data() == sessionId) {
model->removeRow(item->index().row());
break;
}
}
}
It works fine, but every single line of that function makes me cringe. Is there a cleaner way to do any of this?
How about traversing the QStandardItemModel directly? Something like this:
void NetworkManager::removeSessionFromModel (QStandardItemModel *model, int sessionId)
{
for (int i = 0; i < model->rowCount(); ++i)
{
if (model->item(i)->data() == sessionId)
{
model->removeRow(i);
break;
}
}
}
Not sure how QStandardItemModel behaves with random access, maybe your method is more efficient.
Edit:
Actually, there is a function to do what you want: QAbstractItemModel::match
It returns a QModelIndexList with all entries that have matching data in a given role.
void NetworkManager::removeSessionFromModel (QStandardItemModel *model, int sessionId)
{
QModelIndexList list = model->match(model->index(0, 0), Qt::UserRole + 1, sessionId);
if (!list.empty())
model->removeRow(list .first().row());
}
Setting data to a specific role can be done as follows:
model->setData(model->index(row, col), QVariant(13), Qt::UserRole + 1);
You need to get the row index from your item id.
A more effective way could be to use a QMap with the row index as value and the item id as a key.
In this case, you also need to maintain the map values every time you add/remove a row.
If you don't have 3 millions items in your list, just keep it simple and use your code.
By optimize this code, you probably also add complexity and reduce maintainability, and you get is 0,05 ms instead of 0,06 ms.
In GUI code, I often have code like this : it's simple, everyone get it immediatly and it does the job. It' also fast enough.
You're using findItems wrong, it can already return the item you want just by passing the value you're searching for. If you call it like you're doing right now you're looping through your items at least two times, since findItems must iterate through all the items to find those that match your pattern, in your case all items match, then you iterate the returned items again to find the sessionId.
void NetworkManager::removeSessionFromModel (QStandardItemModel *model, int sessionId) {
auto items = model->findItems(QString::number(sessionId));
if (!items.empty()) {
auto row = items.first()->index().row();
model->removeRow(row);
}
}
Alternatively you can use the match method since findItems uses that internally, so you avoid allocating the StandardItem just to get its index. Also match returns right after the number of items matching the pattern, in this case the value of sessionId, are found so it doesn't always iterate all the items; that's more efficient. Obviously if the value is not found after iterating all the items it returns an empty list.
auto start = model->index(0, 0);
auto indexes = model->match(start, Qt::UserRole + 1, QString::number(sessionId), 1, Qt::MatchExactly);
if (!indexes.empty()) {
auto row = indexes.first().row();
model->removeRow(row);
}
I'm working on a scheduling program, and inside the dataGridView, we have a few ComboBox Columns that are populated by 3 entries upon creation, but I wanted to be able to add more as the user creates them, but I have no idea how you would access the combobox data. Any help is appreciated!
// this is initialized in a separate part.
/* System::Windows::Forms::DataGridView^ dataGridView;*/
System::Windows::Forms::DataGridViewComboBoxColumn^ newCol =
(gcnew System::Windows::Forms::DataGridViewComboBoxColumn());
dataGridView->Columns->AddRange(gcnew cli::array< System::Windows::Forms::DataGridViewComboBoxColumn^ >(1) {newCol});
// add the choices to the boxes.
newCol->Items->AddRange("User inputted stuff", "More stuff", "Add New...");
Solution
If you have access to the data from the user entry and you know the column index for the DataGridViewComboBoxColumn, you should be able to just do the following wherever needed:
DataGridViewComboBoxColumn^ comboboxColumn = dataGridView->Columns[the_combobox_column_index];
if (comboboxColumn != nullptr)
{
comboboxColumn->Items->Add("the new user entry");
}
Comments Response
how could you change the selected index of that combobox (the one that
the edit was triggered on)? [...] we want it so that when the new item
is added the selected index is set to that new item).
Couple of ways come to mind.
Add a single line within the if-statement of the above code. This will set the default displayed value for each DataGridViewComboBoxCell in the DataGridViewComboBoxColumn.
if (comboboxColumn != nullptr)
{
comboboxColumn->Items->Add("the new user entry");
comboboxColumn->DefaultCellStyle->NullValue = "the new user entry";
}
Pros: Clean, efficient. Previous user-selected values are left intact. The cell's FormattedValue will display the new user value by default if no other selection has been made.
Cons: Doesn't actually set a cell's selected value, so Value will return null on cells not explicitly user-selected.
Actually set the value of certain cells (based on your criteria) to the user-added value.
if (comboboxColumn != nullptr)
{
comboboxColumn->Items->Add("the new user entry");
for (int i = 0; i < dataGridView->Rows->Count; i++)
{
DataGridViewComboBoxCell^ cell = dataGridView->Rows[i]->Cells[the_combobox_column_index];
if ( cell != nullptr /* and your conditions are met */ )
{
cell->Value = "the new user entry";
}
}
}
Pros: The Value of targeted cells is actually set to the new user value.
Cons: Logic deciding which cells should be affected is more complicated.
In advance, please forgive me if I do not give adequate background information for my question. Long time reader, first time asker.
I am making a program where one has a database of cars accessed through a tab delimited .txt file (we did something like this recently in my programming class, so I wanted to expand upon it).
Instead of using the terminal window, my format is displaying the Car objects (containing make, model, year, price, etc.) in ArrayList. I'm using JFrame, a JList, and a ListModel since I'm using an array of Car objects.
In my program, I wanted to create a delete method where the user could delete items from the database. Initially they would select the item from the JList and then would click on the delete button. This invokes the delete() method, which is the tab shown below...
void delete()
{
int i = list.getSelectedIndex();
String string = (String)listModel.getElementAt(i);
for(Car c : cars)
{
String year = String.valueOf(c.getYear());
String conditionReport = String.valueOf(c.getConditionReport());
String price = String.valueOf(c.getPrice());
if(c.getMake().indexOf(string) != -1 && c.getModel().indexOf(string) != -1 && year.indexOf(string) != -1 && conditionReport.indexOf(string) != -1 && price.indexOf(string) != -1 && c.getWarranty().indexOf(string) != -1 && c.getComments().indexOf(string) != -1)
{
int choice = JOptionPane.showConfirmDialog(null, "Are you sure you would like to remove the " + cars.get(i).getYear() + " " + cars.get(i).getMake() + " " + cars.get(i).getModel() + "?", "Choose One", JOptionPane.YES_NO_OPTION);
if(choice == JOptionPane.NO_OPTION || choice == JOptionPane.CLOSED_OPTION)
{
return;
} else
{
cars.remove(c);
listModel.removeElementAt(i);
}
}
}
writeFile();
}
I have pinpointed my issue to be inside the if statement. (I printed things before and after to try to find where the program is lying. 'list' is my JList and 'listmodel' is my default list model. Car is an object I created that contains the elements (as seen by the get methods). The elements shown in the listModel are merely Strings that show getMake(), getModel(), and so forth... (Each 'get' item is separated by about 10 spaces.)
What am I doing wrong in the if statement? I figured that the getMake() and getModel() (and so forth) would be substrings of the index selected.
Thank you so much for your assistance! Any input regarding ways I could make further questions more specific and clear would be greatly appreciated!
It seems like you are doing this to find the selected Car in some kind of data structure. You would be better off doing something like programming a custom list model that had access to cars itself. Then you could retrieve the selection more immediately. If cars is an ArrayList that list merely parallels I also don't see why you can't do something to the effect of cars.remove(list.getSelectedIndex());. Or since JList can display any object, override Car's toString to display what the list currently displays and have the list display Cars. Then you can cars.remove((Car)list.getSelectedValue());.
But aside from that, based on your description it sounds like you mean to do the evaluation the other way. It's the list item that should contain all of the Car attributes, rather than all of the Car attributes containing the list item. So something like
if( string.contains(c.getMake()) && string.contains(year) // and so on
(Or with indexOf but since contains merely returns indexOf > -1, using contains makes your code somewhat shorter.)
My row has 5 columns and I need the data from the last column. I've written the below function. This function should return the element from the last column of the selected row, but unfortunately, after debug I've noticed that my function only reads the first column. Can anyone help me to solve this?
QString MainWindow::getIDNumberFromSelectedRow(const QModelIndexList indexes)
{
QStringList selected_text;
foreach(QModelIndex current,indexes)
{
QVariant data = model->data(current);
QString text = data.toString();
selected_text.append(text);
qDebug() << text;
}
QString idNumber = selected_text.last();
return idNumber;
}
Possibly, indexes, and thus, current(s) refers to the first column of the model.
What if you refer directly to a specific item, e.g:
model->data(model->index(current.row(), 4))
I don't know if this work, anyway I hope it'll help
I have a QTableWidget with SelectionMode set to SingleSelection, and SelectionBehavior set to SelectColumns. This means that only a single column can be selected.
But I later need to find out which column is selected, and the only functions I can use are selectedIndexes() or selectedItems(), both of which return entire lists, which is wasteful.
Is there a way to do this more efficiently?
Your approach with the selectedItems() was correct.
As QT cant know that you've set your widget to singlerow/column selection it offers those functions to return a QList<>.
in your case you can work on those by using .first().
Evne though I suggest to use the signals currentColumnChanged() to react in your application
( http://harmattan-dev.nokia.com/docs/library/html/qt4/qitemselectionmodel.html#currentColumnChanged )
you could always iterate over all columns of the selected row via selectionModel()->isColumnSelected()
( http://qt-project.org/doc/qt-4.8/qitemselectionmodel.html#isColumnSelected )
connect(tableWidget, SIGNAL(currentCellChanged(int,int,int,int), this, SLOT(onCellChanged(int,int,int,int)));
void Class::onCellChanged(int curRow, int curCol, int preRow, int preCol)
{
current_Col = curCol;
// curRow, preRow and preCol are unused
}
connect(tableWidget->selectionModel()
, SIGNAL(currentColumnChanged(QModelIndex,QModelIndex))
, SLOT(onColumnChanged(QModelIndex)));
...
void Class::onColumnChanged(const QModelIndex &index)
{
int col = index.column();
}
It seems that the function selectedRanges() does what I need. It returns a list of the selected ranges, but since it's a single column, this list would have only one item (so it's efficient, no big list need to be created).
int column = ui->tableWidget->selectedRanges().front().leftColumn();
currentColumn() returns an int of the current selected column.