I'm writing an application that has a QTreeWidget that is populated by parsing an XML file containing the tree levels. If I select a top level checkbox, I need all of the sub-level checkboxes to be checked also.
I already have the XML parser working and populating the QTreeWidget with QTreeWidgetItems that have checkboxes but they can only be individually checked.
To do this, keep the code you have to generate the tree with your XML. Then connect to the itemChanged() signal and update the check states in a slot. It should look something like:
connect(treeWidget, SIGNAL(itemChanged(QTreeWidgetItem*, int)),
this, SLOT(updateChecks(QTreeWidgetItem*, int)));
void ClassName::updateChecks(QTreewidgetItem* item, int column)
{
// Checkstate is stored on column 0
if(column != 0)
return;
recursiveChecks(item);
}
void ClassName::recursiveChecks(QTreeWidgetItem* parent)
{
Qt::CheckState checkState = parent->checkState(0);
for(int i = 0; i < parent->childCount(); ++i)
{
parent->child(i)->setCheckState(0, checkState);
recursiveChecks(parent->child(i));
}
}
A few notes to consider:
You may be tempted to use the itemClicked signal instead of the itemChanged signal. This usually works, but will not work when the user uses the arrow keys and the space bar to change checkstates.
You will need to think about what will happen when you uncheck one of the sub-items that have been checked by clicking on its parent. Usually this means you need to make all ancestors either uncheck or partially-checked. This may not be true for your case.
itemUpdated will also get fired for other changes to the item (like the text changing), so be aware that this is not a super efficient way of doing this.
I just worked on this a little and got nice results based on Rick's answer. Maybe it can help other out there.
It updates the state of parents and children with a tristate status for parents only (checked, unchecked, partially-checked).
void ClassName::updateChecks(QTreeWidgetItem *item, int column)
{
bool diff = false;
if(column != 0 && column!=-1)
return;
if(item->childCount()!=0 && item->checkState(0)!=Qt::PartiallyChecked && column!=-1){
Qt::CheckState checkState = item->checkState(0);
for (int i = 0; i < item->childCount(); ++i) {
item->child(i)->setCheckState(0, checkState);
}
} else if (item->childCount()==0 || column==-1) {
if(item->parent()==0)
return;
for (int j = 0; j < item->parent()->childCount(); ++j) {
if(j != item->parent()->indexOfChild(item) && item->checkState(0)!=item->parent()->child(j)->checkState(0)){
diff = true;
}
}
if(diff)
item->parent()->setCheckState(0,Qt::PartiallyChecked);
else
item->parent()->setCheckState(0,item->checkState(0));
if(item->parent()!=0)
updateChecks(item->parent(),-1);
}
}
Doesn't need recursiveChecks() anymore. Connect between the treeWidget and updateChecks still active.
This appears still quite high in search engines, and is outdated.
Just set the flag Qt::ItemIsAutoTristate on your top-level item.
Related
I've searched many places and found lots of interesting information, but none of that seems to work for what I want. I've tried to follow the solution shown at https://stackoverflow.com/a/9986293/11035837 to no avail.
Basics of my structure: I have a QTreeWidget. I add top level QTreeWidgetItems dynamically (upon the push of a button in a header button box). Each top level QTreeWidgetItem then gets other widgets added to it using:
QTreeWidget* treeWidget = new QTreeWidget;
QTreeWidgetItem* new_record = new QTreeWidgetItem;
QPushButton* add_child = new QPushButton;
QLineEdit* user_input = new QLineEdit;
treeWidget->setItemWidget(new_record,1,add_child);
treeWidget->setItemWidget(new_record,2,user_input);
The add_child button works perfectly. I have a display that inserts all my QLabels, QLineEdits, and QPushButtons in a tree tiered fashion. My buttons work for adding and removing the visual display of items even triggering the visibility of various other elements.
However, I cannot get the user input data out of the QLineEdits to process for anything (such as writing to an output file).
I have my output function iterate through the QTreeWidget:
QTreeWidgetItemIterator iter(treeWidget);
while (*iter)
{
stream.writeStartElement("record");
if ((*iter) != nullptr)
{
for (int i = 0; i < 12; i++)
{
if((*iter)->text(i) != nullptr) stream.writeAttribute("record_name", (*iter)->text(i));
}
}
for (int i = 0; i < 12; i++)
{
if ((*iter) != nullptr && (*iter)->child(i) != nullptr)
{
for (int j = 0; j < 12; j++)
{
if ((*iter)->child(i)->text(j) != nullptr) stream.writeAttribute("record_name", (*iter)->child(i)->text(j));
}
}
}
++iter;
}
This prints as many records with record_name displayed as were created, but it doesn't display any of the other data, because the pointer defined by (*iter)->child(i) is nullptr regardless of i
I then tried using data();
stream.writeAttribute("record_name ", (*iter)->data(2, Qt::UserRole).toString());
This doesn't err out because of nullptr, but it prints out record_name="" rather than record_name="<user_input>"
I'm able to get the user input for QLineEdit widgets that are not in the QTreeWidget, just not the ones in the tree. I assume if I can figure out how to get the data out of the QLineEdits within the tree that I should be able to adapt that to getting the QLineEdits out of a custom QWidget also within the tree.
I found a solution using information from https://www.qtcentre.org/threads/23228-typecast-of-QWidget
stream.writeAttribute("record_name", qobject_cast<QLineEdit*>(treeWidget->itemWidget((*iter), 2))->text() );
The issue was that the QLineEdit widgets were being recalled as QWidgets and not as QLineEdit widgets and as such the text() method/function was not available to it until I cast it back as the desired type.
I have a custom QTableView and QAbstractTableModel. My QTableView hides some of the columns from the QAbstractTableModel as they aren't needed.
When I hit Tab, I would like to select the next available (editable) column. My current implementation is to grab the next index from the QAbstractTableModel, but this index includes columns that are hidden. (So when hitting Tab it may be a couple presses before you see the "next" column selected.)
How can I tell Tab to jump to the next visible column?
The language is C++. Below is the code within my QTableView:
void keyPressEvent(QKeyEvent* event)
{
if((event->modifiers() == Qt::KeyboardModifier::NoModifier) && (event->key() == Qt::Key::Key_Tab))
{
this->moveToNextCell();
}
else
{
this->QTableView::keyPressEvent(event);
}
}
void moveToNextCell()
{
const QModelIndex index = this->currentIndex();
int nextColumn = index.column() + 1;
if(index.column() <= lastEditableCol)
{
this->setCurrentIndex(model->index(index.row(), nextColumn));
}
}
It's not elegant, but I've solved the problem by using isColumnHidden() from QTableView. I just iterate through the columns until I find one that isn't hidden.
for(int i = nextColumn; i <= numOfColumns && nextColumn <= numOfColumns; i++)
{
if(this->isColumnHidden(nextColumn) == true)
{
nextColumn += 1;
}
else
{
i = numOfCol + 1;
}
}
I have created a QTableWidget in which I've used setCellWidget(QWidget*). I've set QLineEdit in the cell widget. I've also created a delete button and clicking that button sends a signal to the function deleteRow. I've also used a function currentRow() to get the current row, but it returns -1 because of the QLineEdit. The code snippet is below.
void createTable() {
m_table = new QTableWidget(QDialog); //member variable
for (int i = 0; i < 3; i++)
{
QLineEdit *lineEdit = new QLineEdit(m_table);
m_table->setCellWidget(i, 0, lineEdit);
}
QPushButton *deleteBut = new QPushButton(QDiaolg);
connect(deleteBut, SIGNAL(clicked()), QDialog, SLOT(editRow()));
}
editRow() {
int row = m_table->currentRow(); // This gives -1
m_table->remove(row);
}
In above scenario I click in the QLineEdit and then click on the button delete. Please help me out with a solution.
Just tried it here, it seems that currentRow of the table returns -1 when clicking the button right after program start, and when first selecting a cell, then selecting the QLineEdit and then clicking the button, the correct row is returned.
I would do the following as a workaround: Save the row number in the QLineEdit, e.g. by using QObject::setProperty:
QLineEdit *lineEdit = new QLineEdit(m_table);
lineEdit->setProperty("row", i);
m_table->setCellWidget(i, 0, lineEdit);
Then, in the editRow handler, retrieve the property by asking the QTableWidget for its focused child:
int row = m_table->currentRow();
if (row == -1) {
if (QWidget* focused = m_table->focusWidget()) {
row = focused->property("row").toInt();
}
}
The accepted solution, as is, would not work if rows might get deleted while the program runs. Thus the approach would require to update all the properties. Can be done, if this is a rare operation.
I got away with an iteration approach:
for(unsigned int i = 0; i < table->rowCount(); ++i)
{
if(table->cellWidget(i, relevantColumn) == QObject::sender())
{
return i;
}
}
return -1;
Quick, dirty, but worked, and in my case more suitable, as rows got deleted often or changed their positions, only buttons in the widget were connected to the slot and the slot was never called directly. If these conditions are not met, further checks might get necessary (if(QObject::sender()) { /* */ }, ...).
Karsten's answer will work correctly only if QLineEdit's property is recalculated each time a row is deleted, which might be a lot of work. And Aconcagua's answer works only if the method is invoked via signal/slot mechanism. In my solution, I just calculate the position of the QlineEdit which has focus (assuming all table items were set with setCellWidget):
int getCurrentRow() {
for (int i=0; i<myTable->rowCount(); i++)
for (int j=0; j<myTable->columnCount(); j++) {
if (myTable->cellWidget(i,j) == myTable->focusWidget()) {
return i;
}
}
return -1;
}
So I have reached my ceiling of knowledge when it comes to Qt and C++ in general I guess. I am creating check boxes in a QScrollArea based off the input from a QComboBox. Depending on the value selected in the QComboBox, a specific number of check boxes are created. Once I created those check boxes, I am having a problem understanding how to interact (in my case, simply check to see if they are checked or not) with them outside of the function they are being created and called in. I know how to work with them if the buttons were static, but since the check boxes are dynamic (is that the right word?) and can change, I don't know what to do. Below is a little snippet of code on how the check boxes are created. If I now want to simply check if any of the boxes are checked, how do I do that. Can I "return" or "call" the created check boxes in another function somehow? I know I'll simply need to loop through the array and check, I just simply don't know how to get the array of check boxes into another function or how to return them in the function below.
Thanks for the help!
void MyProgram::create_checkboxes(QString opnum)
{
QWidget* MDAcheckboxes = new QWidget(ui->MDA);
QVBoxLayout* MDAlayout = new QVBoxLayout(MDAcheckboxes);
QCheckBox *MDAmycheckBox[9];
QList<QString> boxes;
if (opnum == "640")
{
boxes << "16-1" << "16-2";
for (int i = 0; i < 2; i++)
{
MDAmycheckBox[i] = new QCheckBox(MDAcheckboxes);
MDAmycheckBox[i]->setText(boxes[i]);
MDAlayout->addWidget(MDAmycheckBox[i]);
}
ui->MDA->setWidget(MDAcheckboxes);
}
else if (opnum == "645")
{
boxes << "13-01"<<"13-2"<<"13-3"<<"13-4"<<"13-5";
for (int i = 0; i < 5; i++)
{
MDAmycheckBox[i] = new QCheckBox(MDAcheckboxes);
MDAmycheckBox[i]->setText(boxes[i]);
MDAlayout->addWidget(MDAmycheckBox[i]);
}
ui->MDA->setWidget(MDAcheckboxes);
}
else if (opnum == "650")
{
boxes << "13-6"<<"13-7"<<"13-8"<<"13-9"<<"13-10"<<"13-11"<<"13-12"<<"13-13"<<"13-14";
for (int i = 0; i < 9; i++)
{
MDAmycheckBox[i] = new QCheckBox(MDAcheckboxes);
MDAmycheckBox[i]->setText(boxes[i]);
MDAlayout->addWidget(MDAmycheckBox[i]);
}
ui->MDA->setWidget(MDAcheckboxes);
}
}
All your checkBoxes should have a parent. In this case you will be able to find it with findChildren. It also can be done without groupBox if you sure that app has no any other checkboxes and findChildren will not return you checkboxes which you don't need.
Try this:
QList<QCheckBox *> allButtons = ui->groupBox->findChildren<QCheckBox *>();
qDebug() <<allButtons.size();
for(int i = 0; i < allButtons.size(); ++i)
{
if(allButtons.at(i)->isChecked())
qDebug() << "Use" << allButtons.at(i)->text()<< i;//or what you need
}
In general case:
QList<QCheckBox*> allButtons = parentOfCheckBoxes->findChildren<QCheckBox *>();
Moreover findChildren allows you to find children with special objectName which can be useful in some cases. Note that you can set the same objectName to the different objects.
http://qt-project.org/doc/qt-5/qobject.html#findChildren
I have a simple class which inherits QTableView and I want the following behavior: when the user selects a few cells, I want the first cell selected to be set as the current index.
So for example if I select from (0, 0) towards (2, 2), when I start typing the text would show up in (0, 0), not (2, 2) which seems to be the default.
I have tried overriding the setSelection function with the following:
void SampleTable::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command)
{
if((command & QItemSelectionModel::Current) != 0)
{
QModelIndex curr = indexAt(rect.topLeft());
selectionModel()->select(curr, QItemSelectionModel::Current);
command ^= QItemSelectionModel::Current;
}
QTableView::setSelection(rect, command);
}
but to no avail. It seems to have something to do with the mouse events, but I can't quite locate the problem in the source code and I'm hoping there's an easier way anyway.
What are you trying to achieve? If you'd like the user to edit/select a single cell only, use setSelectionBehaviour to force this. Otherwise you could try chinfoo's idea but make sure to communicate the behavior in a way the user is able to understand it (i.e. he's able to see that his edit will change the first cell/row).
I figured out the problem and how to fix it, but it's not pretty. The problem is in the mouse moved event for a QAbstractItemView. After much debugging and searching through the source code I found this in qabstractitemview.cpp:
void QAbstractItemView::mouseMoveEvent(QMouseEvent *event)
...
if (index.isValid()
&& (index != d->selectionModel->currentIndex())
&& d->isIndexEnabled(index))
d->selectionModel->setCurrentIndex(index, QItemSelectionModel::NoUpdate);
}
I fixed it by giving my class a QModelIndex member which stores the last location of the top left QModelIndex (set in my overriden version of setSelection like above) and then I overrode the mouseMoveEvent with this:
void SampleTable::mouseMoveEvent(QMouseEvent *event)
{
QTableView::mouseMoveEvent(event);
if (state() == ExpandingState || state() == CollapsingState || state() == DraggingState || state() == EditingState)
return;
if ((event->buttons() & Qt::LeftButton)) {
if (m_topLeft.isValid())
{
selectionModel()->setCurrentIndex(m_topLeft, QItemSelectionModel::NoUpdate);
}
}
}
Not a pretty solution, but it works.
The QtableWidget class has a signal itemSelectionChanged(), connect it to your custom slot. In that slot, use selectedIndexes() to get all indexes, then use setCurrentIndex() to set the cell which you want to be the current index.