HowTo find Subitem in QAbstractItemModel and QTreeView class? - qt

Question: how to find sub item, in a QTreeView loaded QAbstractItemModel model with model->match() method?
Problem: model->match() can't find sub items, wtf?!
Here is the example:
As you can see from the picture, I'm trying to expand Layouts sub item with this code:
void Dialog::restoreState(void)
{
// get list
QSettings settings("settings.ini", QSettings::IniFormat);
settings.beginGroup("MainWindow");
QStringList List = settings.value("ExpandedItems").toStringList();
settings.endGroup();
foreach (QString item, List)
{
if (item.contains('|'))
item = item.split('|').last();
// search `item` text in model
QModelIndexList Items = model->match(model->index(0, 0), Qt::DisplayRole, QVariant::fromValue(item));
if (!Items.isEmpty())
{
// Information: with this code, expands ONLY first level in QTreeView
view->setExpanded(Items.first(), true);
}
}
}
Where settings.ini file contains:
[MainWindow]
ExpandedItems=Using Containers, Connection Editing Mode, Form Editing Mode, Form Editing Mode|Layouts
PS: root items successfully expands on start!

Here is the solution:
QModelIndexList Items = model->match(
model->index(0, 0),
Qt::DisplayRole,
QVariant::fromValue(item),
2, // look *
Qt::MatchRecursive); // look *
* Without that argument match() function searches only 1 level

My working example on QTreeView.
QModelIndexList Indexes = this->ui->treeView->selectionModel()->selectedIndexes();
if(Indexes.count() > 0)
{
QStandardItemModel *am = (QStandardItemModel*)this->ui->treeView->model();
QStack<QModelIndex> mis;
QModelIndex mi = Indexes.at(0);
while(mi.isValid())
{
mis.push(mi);
mi = mi.parent();
}
QStandardItem *si;
bool FirstTime = true;
while (!mis.isEmpty())
{
mi = mis.pop();
if(FirstTime)
{
FirstTime = false;
si = am->item(mi.row());
}
else
{
si = si->child(mi.row());
}
}
// "si" - is selected item
}

Wanted to add to the answer that #mosg gave
The forth parameter is actually the hits parameters.
It decides ho many matches one wants to return.
For all matches specify -1 as can be seen
here:
QModelIndexList Items = model->match(
model->index(0, 0),
Qt::DisplayRole,
QVariant::fromValue(item),
-1, // any number of hits
Qt::MatchRecursive); // look *

Related

Qt: QFormLayout::addRow(QWidget* label, QWidget* field) not adding a new row

I am trying to add a new row to my QFormLayout after I have loaded two QLineEdits from a file, which works, but when I run my code it doesnt add anything, ot atleast anything that I can see. And I am also not able to add any wigets using QLayout::addWidget(QWidget* widget) anymore, which I used to be able to.
Thanks
The code, where it doesnt work:
void Kegelbuch::load(QString path, QString tab) {
//Load json file
Datastream* loadStream = new Datastream;
QJsonObject data = loadStream->loadData(path);
//Declaring all important Variables
QJsonArray keglerFullName;
QJsonArray keglerShortName;
QFormLayout* formLayout = (QFormLayout*)ui.keglerTab->layout();
int defaultRows = 2, width = 155;
//Retriev arrays from file
if (data.contains("KeglerFullName") && data["KeglerFullName"].isArray()) {
keglerFullName = data["KeglerFullName"].toArray();
}
if (data.contains("KeglerShortName") && data["KeglerShortName"].isArray()) {
keglerShortName = data["KeglerShortName"].toArray();
}
//Correctly add QLineEdits to the FormLayout
for (auto names : boost::combine(keglerFullName, keglerShortName)) {
QLineEdit fullNameEdit;
QLineEdit shortNameEdit;
QJsonValue fullNameValue, shortNameValue;
boost::tie(fullNameValue, shortNameValue) = names;
if (fullNameValue.isString()) {
fullNameEdit.setText(fullNameValue.toString());
fullNameEdit.setObjectName("fullName");
fullNameEdit.setMinimumWidth(width);
}
if (shortNameValue.isString()) {
shortNameEdit.setText(shortNameValue.toString());
shortNameEdit.setMaximumWidth(width);
shortNameEdit.setObjectName("shortName");
}
/*
if (keglerFullName.at(1).isString()) {
fullNameEdit->setText(keglerFullName.at(1).toString());
fullNameEdit->setObjectName("fullName");
fullNameEdit->setMinimumWidth(width);
}
if (keglerShortName.at(1).isString()) {
shortNameEdit->setText(keglerShortName.at(1).toString());
shortNameEdit->setMaximumWidth(width);
shortNameEdit->setObjectName("shortName");
}
*/
formLayout->addRow(&fullNameEdit, &shortNameEdit);
}
}

How to limit the number of QCheckboxes checked at the same time?

I'm in the process of creating a QT application which is using multiple (14) qcheckboxes. I need to have a limit (preferably set as a variable that i can change) to the number of checkboxes that can be checked at the same time, is there any way to achieve this cleanly ? Thanks for your time.
There is no simple way of doing this, you have to write your code to do it.
I suppose you have the checkboxes in some parent widget class. So I would create a slot which looks like this.
void SomeParentWidget::onCheckBoxToggled(bool value)
{
// when we unchecked the checkbox,
// we do not need to count the number of checked ones
if (!value)
return;
int total = 0;
int limit = 15; // your "magic" number of maximum checked checkboxes
for (auto chb : allCheckBoxes()) // allCheckBoxes() is some method which returns all the checkboxes in consideration
{
if (chb->isChecked())
{
++total;
if (total > limit)
{
// too many checkboxes checked! uncheck the sender checkbox
// Note: you may want to add some nullptr checks or asserts to the following line for better robustness of your code.
qobject_cast<QCheckBox*>(sender())->setChecked(false);
return;
}
}
}
}
And when creating each of your checkboxes inside some parent widget, connect this slot to their signal:
auto chb = new QCheckBox();
connect(chb, &QCheckBox::toggled, this, &SomeParentWidget::onCheckBoxToggled);
Implementation of allCheckBoxes() is up to you, I do not know how you can retrieve the collection of all your check boxes. Depends on your design.
I found another, even simpler solution. Use this slot.
void SomeParentWidget::onCheckBoxToggled(bool value)
{
static int totalChecked = 0; // static! the value is remembered for next invocation
totalChecked += value ? 1 : -1;
Q_ASSERT(totalChecked >= 0);
int maxChecked = 15; // any number you like
if (value && totalChecked > maxChecked)
{
qobject_cast<QCheckBox*>(sender())->setChecked(false);
}
}
... and connect it to checkboxes' toggled() signal. Note that in order to work correctly, all check boxes must be unchecked at the time when you make the signal-slot connection because this function starts counting from zero (0 is the initial value of the static variable).
You can store all your checkboxes in a map (either in an std::map, an std::unordered_map or an QMap). Your keys will be your checkboxes, and your values will be their states, so something like this:
std::unordered_map<QCheckBox*, bool> m_checkBoxStates;
Here's what your connected to your toggled signal of all your checkboxes look like (keep in mind that all the signals will be connected to the same slot):
void MainWindow::onToggled(bool checked) {
QCheckBox* checkBox = sender(); //the checkbox that has been toggled
m_checkBoxStates[checkBox] = checked;
if (!checked) {
return;
}
const int count = std::count_if(m_checkBoxStates.begin(), m_checkBoxStates.end(),
[](const auto pair) {
return pair.second == true;
});
if (count > maxCount) {
checkBox->setChecked(false);
}
}

Keep row selected in QTableView

I am working on a program to view and edit records in a file. It features a QTableView that displays all records, a QLineEdit to search for records, and some labels that display the details of the selected record:
There is a QAbstractTableModel class that holds the data and a QSortFilterProxyModel class that helps filtering the rows in the QTableView.
Searching and filtering works fine. Typing text in the search box immediately filters the list of records. But there are two things I cannot get to work:
if the list is not empty, I want one item to be selected/current always
the selected/current item must be brought into view when typing in the search box
For example, when I type "tesla", the list will be empty since no item matches. But as soon as I backspace to "te", the "Forester" matches and I want it to be selected. Second example: when (after starting the program) I type "f" the list is narrowed down to 8 items and "Pacifica" is selected. When I erase the "f", the Pacifica is still selected but no longer in the visible part of the list.
I have posted the full source code of this on Pastie, and here are some (hopefully) relevant snippets.
void MainWindow::on_lineEditSearch_textChanged(const QString & text)
{
itemProxy->setFilterFixedString(text);
updateStatusBar();
}
void MainWindow::currentRowChangedSlot(QModelIndex const & current, QModelIndex const & /*previous*/)
{
Car * car = 0;
if (current.isValid())
{
QModelIndex sibling = current.sibling(current.row(), COLUMN_THIS);
QVariant variant = itemProxy->data(sibling);
car = static_cast<Car *> (variant.value<void *> ());
}
updateCarMake(car);
updateCarModel(car);
}
MainWindow::MainWindow(QWidget * parent, CarItemModel * itemModel, CarSortFilterProxyModel * itemProxy) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
this->itemModel = itemModel;
this->itemProxy = itemProxy;
ui->setupUi(this);
setupStatusBar();
ui->tableView->setModel(itemProxy);
ui->tableView->setColumnHidden(COLUMN_THIS, true);
QItemSelectionModel * selectionModel = ui->tableView->selectionModel();
connect(selectionModel, SIGNAL(currentRowChanged(QModelIndex const &, QModelIndex const &)),
this, SLOT(currentRowChangedSlot(QModelIndex const &, QModelIndex const &)));
connect(selectionModel, SIGNAL(selectionChanged(QItemSelection const &, QItemSelection const &)),
this, SLOT(selectionChangedSlot(QItemSelection const &, QItemSelection const &)));
ui->tableView->selectRow(0);
ui->lineEditSearch->setFocus();
updateStatusBar();
}
<widget class="QTableView" name="tableView">
<property name="verticalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOn</enum>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SingleSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
</widget>
So, my question is: how can I make sure an item is always selected and in the visible part of the list (unless the user is scrolling, of course)?
Here's what I did to
always have an item selected, and
have the selected item visible when searching
The solution is even a bit fancier since when going from no visible items to some visible items the most recently selected item will be selected instead of just the first item.
First, I added a QModelIndex lastModelIndex; private member to the MainWindow class, and set it in the SelectionChanged slot. Note that the model index is stored and not the proxy index.
void MainWindow::selectionChangedSlot(QItemSelection const & selected, QItemSelection const & /*deselected*/)
{
if (selected.count() > 0)
{
QModelIndex index = selected.indexes().first();
QModelIndex modelIndex = itemProxy->mapToSource(index);
lastModelIndex = modelIndex;
}
}
Next, I added two methods: ensureSelected() ...
void MainWindow::ensureSelected(QItemSelectionModel * selectionModel, int const proxyCount)
{
if (selectionModel->hasSelection())
{
// an item is currently selected - don't have to do anything
}
else if (proxyCount == 1)
{
// no item is currently selected, but there is exactly one item in the list - select it
QModelIndex proxyIndex = itemProxy->index(0, 0);
selectionModel->setCurrentIndex(proxyIndex, QItemSelectionModel::Select | QItemSelectionModel::Rows);
}
else if (proxyCount > 1)
{
// no item is currently selected, but there are several items in the list
QModelIndex proxyIndex; // !isValid
if (lastModelIndex.isValid())
{
// there's a most recently selected item - compute its index in the list
proxyIndex = itemProxy->mapFromSource(lastModelIndex);
}
if (proxyIndex.isValid())
{
// the most recently selected item is in the list - select it
proxyIndex = proxyIndex.sibling(proxyIndex.row(), COLUMN_THIS);
}
else
{
// there's no most recently selected item or it is no longer in the list - select the first item
proxyIndex = itemProxy->index(0, 0);
}
selectionModel->setCurrentIndex(proxyIndex, QItemSelectionModel::Select | QItemSelectionModel::Rows);
}
else
{
// There are no items in the list - cannot select anything.
}
}
... and ensureVisible():
void MainWindow::ensureVisible(QItemSelectionModel * selectionModel)
{
if (selectionModel->hasSelection())
{
const QModelIndex index = ui->tableView->currentIndex();
ui->tableView->scrollTo(index);
ui->tableView->selectRow(index.row());
ui->tableView->scrollTo(index);
}
}
Weird as it may seem, I have to call scrollTo() twice, or the tableView won't scroll.
These new methods are called from on_lineEditSearch_textChanged() like so:
void MainWindow::on_lineEditSearch_textChanged(const QString & text)
{
itemProxy->setFilterFixedString(text);
QItemSelectionModel * selectionModel = ui->tableView->selectionModel();
int modelCount = itemModel->rowCount(); // number of items in the model
int proxyCount = itemProxy->rowCount(); // number of items in tableview
ensureSelected(selectionModel, proxyCount);
ensureVisible(selectionModel);
updateStatusBar();
}
Please find the updated and complete source code on Pastie.
Handle selection change signal and connect it to your custom slot:
QSortFilterProxyModel *model = new QSortFilterProxyModel(this);
ui->tableView->setModel(model);
connect(ui->tableView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(selectionChanged(QItemSelection,QItemSelection)));
Every time, you table change the current selection the app should select the first item in the model:
void MainWindow::selectionChanged(QItemSelection selected, QItemSelection deselected) {
// Use this
const int rows = ui->tableView->selectionModel()->selectedRows().size();
// or
const int rowCount = selected.size();
// Lets select the first item if there are some items availables
if (rowCount < 1) {
const int availableItems = ui->tableView->model()->rowCount();
if (availableItems > 0) {
const QModelIndex index = ui->tableView->model()->index(0,0);
ui->tableView->selectionModel()->select(index, QItemSelectionModel::ClearAndSelect);
}
}
}
By this way, you should have at least one item selected.

qtablewidget selectedItems gives empty list

I have a qtablewidget with one column with a widget and others with data. The only column with widget is shown and all other columns are hidden.
foreach (BillHeader *billHeader, billHeaderList)
{
m_pBillTable->insertRow(i);
itemWidget = new LookupItem;
itemWidget->setImage(1);
...
m_pBillTable->setCellWidget(i, 0, itemWidget);
tableItem = new QTableWidgetItem(billHeader->billNumber);
tableItem->setTextAlignment(Qt::AlignCenter);
m_pBillTable->setItem(i, 1, tableItem);
...
m_pBillTable->hideColumn(1);
...
I have a signal slot connected as below:
connect(m_pOkButton, SIGNAL(clicked()), this, SLOT(handleOkClick()));
when ok button click i try to get the selected item and get data from the widget set to it
void OrderLookup::handleOkClick()
{
qDebug()<<Q_FUNC_INFO<<"Invoked";
QList<QTableWidgetItem*> itemList = m_pBillTable->selectedItems();
qDebug()<<Q_FUNC_INFO<<itemList.count();
if (!itemList.isEmpty())
{
int row = itemList.at(0)->row();
qDebug()<<Q_FUNC_INFO<<row;
LookupItem *item = (LookupItem*)m_pBillTable->cellWidget(row, 0);
if (NULL != item)
{
QString billNumber = item->getBillNumber();
emit orderLookupComplete(billNumber);
accept();
}
}
qDebug()<<Q_FUNC_INFO<<"Exits";
}
But i am getting the list count as zero.
The row is getting selected and gets highlighted.
I’ve set some properties to table widget as below:
m_pBillTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
m_pBillTable->setSelectionBehavior(QAbstractItemView::SelectRows);
m_pBillTable->setSelectionMode(QAbstractItemView::SingleSelection);
m_pBillTable->setFocusPolicy(Qt::NoFocus);
Please can someone help me to know why list count is empty..
The issue got solved ..
QItemSelectionModel *itemModel = m_pBillTable->selectionModel();
QModelIndexList indexList = itemModel->selectedRows();
qDebug()<<Q_FUNC_INFO<<"IndexList Count"<<indexList.count();
if (!indexList.isEmpty())
{
int row = indexList.at(0).row();

QAbstactTableModel insert at top

I have addFile function in my TableModel class which inserts a new record at the end.
void TableModel::addFile(const QString &path)
{
beginInsertRows(QModelIndex(), list.size(),list.size());
TableItem item;
item.filename = path;
QFile file(path);
item.size = file.size();
item.status = StatusNew;
list << item;
endInsertRows();
}
This function works fine but instead of appending record at the end I would like to insert it at the top. Any pointers on how to update my existing function ?
I have already tried some combinations but there is no Luck.
There are two things that you need to do. First is to adjust the call to beginInsertRows. Because it is here that we are telling the model that we are adding rows, where they will go, and how many we are adding. Here is the method description:
void QAbstractItemModel::beginInsertRows ( const QModelIndex & parent,
int first, int last )
So in your case since you want to add a row at the first index, and only one row, we pass 0 as the index of the first item, and 0 which is the index of the last item we are adding (because of course we are only adding one item).
beginInsertRows(modelIndex(), 0, 0);
Next we have to provide the data for the item. I assume that 'list' is a QList (if not it is probably similar). So we want to call the 'insert' method.
list.insert(0, item);
And that should be it.
For display you can try delegates as explained in the link (I haven't tried the example though). It will help the community if you can add your observations.
Thanks to everyone for replying. I have found the solution by my own:
In case if anyone is interested
void TableModel::addFile(const QString &path)
{
beginInsertRows(QModelIndex(), list.size(), list.size());
TableItem item;
item.filename = path;
QFile file(path);
item.size = file.size();
item.status = StatusNew;
list << item; // Why Assign first? Maybe not required
for (int i = list.size() - 1; i > 0; i--)
{
list[i] = list[i-1];
}
list[0] = item; // set newly added item at the top
endInsertRows();
}

Resources