QTablewidget drop without creating new rows - qt

I have a QTableWidget and 1 column has only checkboxes, so for those items I have these flags:
/* create prototype for checkbox item */
checkItem = new QTableWidgetItem();
Qt::ItemFlags flags = checkItem->flags();
flags &= ~Qt::ItemIsEditable;
flags &= ~Qt::ItemIsDropEnabled;
flags &= ~Qt::ItemIsDragEnabled;
flags |= Qt::ItemIsUserCheckable;
checkItem->setFlags(flags);
/**/
Ok that works... almost. I can't drop anything in those items, that's good. But now there can be dropped in between 2 cells, so there is a new row created.
How can I prevent that?
In the other columns where the cells are drop-enabled, I can only drop in the cells and not in between and that is good. Why is this behavior changed when the item is not drop-enabled?

A fast hack using an event filter (could need some tweaks):
What you do this is ignore any drop on the checkbox column. So it should be enough to disable row creation.
bool yourWidget::eventFilter(QObject *a_object, QEvent *a_event)
{
bool result = false;
if ((a_object == table->viewport()) && (a_event->type() == QEvent::Drop))
{
QDropEvent *p_drop_event = static_cast<QDropEvent *>(a_event);
QPoint pos = p_drop_event->pos();
QModelIndex new_index = table->indexAt(pos);
if (new_index.column() == YOUR COLUMN HERE)
{
// Ignore drop event
p_drop_event->setDropAction(Qt::IgnoreAction);
p_drop_event->accept();
return true;
}
else
{
// Allow drop
return false;
}
}
return QObject::eventFilter(a_object, a_event);
}
Info about eventFilters:
Event Filters

Related

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);
}
}

Fixed/freezed row in tableview

I have a TableView that displays the information contained in a QSortFilterProxyModel (whose model is a QAbstractTableModel)
Is there any way to fix/freeze in the TableView's top all rows with a specific property set to true, while maintaining filter and sorting for the other rows?
To put a specific row always on the tableview's top, I added the following logic to the QSortFilterProxyModel's lessThan method:
// Get left and right row
int leftRow = left.row();
int rightRow = right.row();
// Get specific data from left row
QModelIndex leftIndex = sourceModel()->index(leftRow, SPECIFIC_DATA_INDEX);
QString leftProperty = sourceModel()->data(leftIndex).toString();
// Get specific data from right row
QModelIndex rightIndex = sourceModel()->index(rightRow, SPECIFIC_DATA_INDEX);
QString rightProperty = sourceModel()->data(rightIndex).toString();
if(leftProperty .compare("Invalid") == 0) // put left on top if it has a specific property
if(sortOrder() == Qt::AscendingOrder) {
return true;
} else {
return false;
}
} else if(rightProperty .compare("Invalid") == 0) { // put right on top if it has a specific property
if(sortOrder() == Qt::AscendingOrder) {
return false;
} else {
return true;
}
}

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();

HowTo find Subitem in QAbstractItemModel and QTreeView class?

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 *

Copying part of QTableView

So I have a question very closely related to another question I've seen on here but when I tried posing my question there I got no responses, I'm hoping by asking this as a fresh question someone can help me out. Basically I want simply copy a portion of my table that I've created so that I can paste it to an excel file. Here's what I have:
QAbstractItemModel *abmodel = ui.tableview->model();
QItemSelectionModel *model = ui.tableview->selectionModel();
QModelIndexList list = model->selectionIndexes();
qSort(list);
QModelIndex index = list.first();
for(int i = 0; i < list.size(); i++)
{
QModelIndex index = list.at(i);
QString text = abmodel->data(index).toString();
copy_table.append(text);
if(index.row() != previous.row())
{
copy_table.append('\n');
}
else
{
copy_table.append('\t');
}
previous = index;
}
QClipboard *clipboard = QApplication::clipboard();
clipboard->setText(copy_table);
This will copy a column fine, but when I attempt to copy a row or say a 2x2 subtable the row index gets messed up, incorrectly assigning the row index for the values. Any thoughts?
Well, already figured it out, sorry anyone that wasted their time and looked.
void TestCopyTable::on_pushButton_copy_clicked()
{
QAbstractItemModel *abmodel = ui.tableView->model();
QItemSelectionModel * model = ui.tableView->selectionModel();
QModelIndexList list = model->selectedIndexes();
qSort(list);
if(list.size() < 1)
return;
QString copy_table;
QModelIndex last = list.last();
QModelIndex previous = list.first();
list.removeFirst();
for(int i = 0; i < list.size(); i++)
{
QVariant data = abmodel->data(previous);
QString text = data.toString();
QModelIndex index = list.at(i);
copy_table.append(text);
if(index.row() != previous.row())
{
copy_table.append('\n');
}
else
{
copy_table.append('\t');
}
previous = index;
}
copy_table.append(abmodel->data(list.last()).toString());
copy_table.append('\n');
QClipboard *clipboard = QApplication::clipboard();
clipboard->setText(copy_table);
}
I wrote some code based on Phil's to copy the selection when the user types Control-C.
I subclassed QTableWidget and overrode keyPressEvent():
void MyTableWidget::keyPressEvent(QKeyEvent* event) {
// If Ctrl-C typed
// Or use event->matches(QKeySequence::Copy)
if (event->key() == Qt::Key_C && (event->modifiers() & Qt::ControlModifier))
{
QModelIndexList cells = selectedIndexes();
qSort(cells); // Necessary, otherwise they are in column order
QString text;
int currentRow = 0; // To determine when to insert newlines
foreach (const QModelIndex& cell, cells) {
if (text.length() == 0) {
// First item
} else if (cell.row() != currentRow) {
// New row
text += '\n';
} else {
// Next cell
text += '\t';
}
currentRow = cell.row();
text += cell.data().toString();
}
QApplication::clipboard()->setText(text);
}
}
Output example (tab-separated):
foo bar baz qux
bar baz qux foo
baz qux foo bar
qux foo bar baz
Regarding the cdline:
qSort(cells); // Necessary, otherwise they are in column order
currently(20190118) it brings a warning:
Warnung: 'qSort >' is deprecated: Use std::sort
so my solution to replace the line with:
std::sort(cells.begin(),cells.end());
Compile, Run OK -> so far so good. But Question: Benefit of this cdline?
I found there is no one. Made several Test with copy from gui and parsing it to excel. Everything was fine even with the scenario 2x2 or othe XxY.

Resources