can't remove custom QWidget from QVBoxLayout - c++

This is a reasonably well documented issue but after some reading and trying, I am still struggling to remove a custom widget from a QVBoxLayout.
in the MyClass.h file, I am declaring a vector to hold QPointer objects:
vector< QPointer<MyWidget> > myWidgets;
In the MyClass.cpp file, I am creating the widget and adding it to the layout with:
// create object
void MyClass::createMyWidget() {
QPointer<MyWidget> myWidget(new MyWidget());
myWidgets.push_back(myWidget);
}
//display in layout
void MyClass::displayWidgets() {
for(int i = 0; i < myWidgets.size(); i++) {
myWidgets[i]->setId(i);
ui->myVBoxLayout->addWidget(myWidgets[i].data());
}
}
I have tried the following to remove the widgets from the layout:
void MyClass::removeMyWidget(int id) { // id of the custom widget. id is also position in the layout
for(int i = 0; i < myWidgets.size(); i++) {
if(items[i]->getId() == id) { //correctly targets the widget
// method 1
ui->myVBoxLayout->removeItem(ui->myVBoxLayout->takeAt(i));
//method 2
//ui->myVBoxLayout->removeWidget(items[i]);
}
}
}
Neither of the above works and I have not been able to work out why.

In Qt, a layout is just a manager for sizing and positioning widgets (and other items, like nested layouts or spacers).
When you remove an item from a layout, it is still a child-widget of the parent widget that is layed out by the layout, only that its geometry is no longer managed. Still, the widget is present and displayed, and the user can interact with it.
You also have to remove it or hide it. Try
void MyClass::removeMyWidget(int id){ // id of the custom widget. id is also position in the layout
for(int i=0;i<myWidgets.size();i++){
if(items[i]->getId() == id){ //correctly targets the widget
ui->myVBoxLayout->removeWidget(myWidgets[i]);
delete myWidgets[i]; // <= Delete widget
}
}
}

Related

Fetch values from QWidgets added to QTreeWidgets via QTreeWidgetItems

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.

How to get current row of QTableWidget if I clicked on its child?

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

QT - Can I dynamically point to GUI's labels using a variable passed into a method?

I'm creating an inventory and want to reference my inventory Forms QLabels with the name of the item passed into my method.
The QLabels and the names of the items are the same so I wanted to reduce if statements by referencing a QLabel depending on the name of the item passed in.
void Zork::on_btnTake_clicked()
{
Item *item = new Item(ui->takeItem->currentText().toStdString());
Colin->addItemToInventory(item);
inventory_screen->addItem(item->getDescription()); //Enables the Item in the Inventory
currentRoom->deleteItem(item);
ui->takeItem->removeItem(ui->takeItem->currentIndex()); //Only remove the item in the foreground of combobox at the time take button was clicked
}
Calls this Method addItemToInventory(item):
void Inventory_Ui:: addItem(string itemName){
myUi->itemName->setText(QString::fromStdString(itemName));
}
I am unable to do so as itemName is not a member of Ui, although itemName does contain members of Ui.
Example:
In my Inventory_Ui Form I have 6 labels, one named broken_bottle. I want to pass broken_bottle into the method's parameter itemName and them use that as a reference so at run-time it would look like myUi->broken_bottle->setText...
I think I follow what you are asking to do. I would access it with findChild or findChildren.
http://doc.qt.io/qt-5/qobject.html#findChild
http://doc.qt.io/qt-5/qobject.html#findChildren
QList <QLabel *> labels = ui->myForm()->findChildren<QLabel*>();
QLabel* targetLabel = 0;
foreach(QLabel * label, labels)
{
if(label->text() == ui->takeItem->currentText())
{
targetLabel = label;
break;
}
}
if(targetLabel != 0)
{
// ... do something with targetLabel
}
or...
QList <QLabel *> labels = ui->myForm()->findChildren<QLabel*>();
foreach(QLabel * label, labels)
label->setObjectName(label->text());
// later
QLabel * targetLabel = ui->myForm()->findChild<QLabel*>("text of label");
if(targetLabel)
// use it
Or if you are connecting all these labels to the same slot, you can figure some of it out using the sender static method.
http://doc.qt.io/qt-5/qobject.html#sender
void MainWindow::on_takeItem_currentTextChanged()
{
QLabel* targetLabel = qobject_cast<QLabel*>QObject::sender();
if(targetLabel)
// do something with it
}
Hope that helps.

Qt 5.3: Accessing/returning/calling checkboxes that are created dynamically(?)

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

Removing QWidgets from a QGridLayout

I have a QGridLayout where I add my custom QWidgets.
When I try to delete all of them they are supposedly removed from the layout (as the function layout.count() returns 0) but they are still shown in the interface and I can interact with them.
Here you have the way I add widgets:
void MyClass::addCustomWidget(CustomWidget *_widget, int r, int c)
{
layout->addWidget(_widget, r, c);
_widget->show();
}
And here the way I delete them:
void MyClass::clearLayout()
{
qDebug() << "Layout count before clearing it: " << layout->count();
int count = layout->count();
int colums = layout->columnCount();
int rows = layout->rowCount();
int i=0;
for(int j=0; j<rows; j++)
{
for(int k=0; k<colums && i<count; k++)
{
i++;
qDebug() << "Removing item at: " << j << "," << k;
QLayoutItem* item = layout->itemAtPosition(j, k);
if (!item) continue;
if (item->widget()) {
layout->removeWidget(item->widget());
} else {
layout->removeItem(item);
}
qDebug() << "Removed!";
}
}
qDebug() << "Layout count after clearing it: " << layout->count();
}
Any kind of help or tip to delete items/widgets correctly from a QGridLayout?
P.D. : I have seen on the internet that a lot of people deletes the widget directly (delete _widget) after removing them from the layout. In my case it is not possible as I need to mantain that widgets in memory.
You can also use deleteLater() to avoid issue with maintaining child
count during iterations:
for (int i = 0; i < gridLayout.count(); i++)
{
gridLayout.itemAt(i)->widget()->deleteLater();
}
Just to be clear. You didn't "delete" the widgets. You only removed them from layout. Removing from layout means only that widget will be no more managed (resized/positioned) by this layout BUT it doesn't mean that widget will be "deleted" (in C++ way). Also widget won't be magically hidden. Your widget after removing from layout still leaves in widget it was created / managed in. So owner of this layout still has this widget as child (visible child).
You have to
hide widget or if you're sure it will not be used anymore
delete widget with "delete" keyword
Also you don't need to call removeWidget(item->widget()); removeItem(item) will be enough for all layout items (even those with widget inside)
Try
QLayoutItem *child;
while ((child = layout->takeAt(0)) != 0);
It is supposed to be safe. If for any reasons it doesn't work, you can use a collection of widgets or layoutitems, which is updated every time you add a widget. Then to delete you loop on the collection and remove each element from the layout.
Header:
class Grid : QGridLayout
{
public:
Grid(QWidget* parent);
void addElement(QWidget* element, int x, int y);
void delElement(int x, int y);
void resize(int width, int height);
void clear();
protected:
QSize size;
};
void Grid::clear(){
for(int i = 0;i<size.width();i++){
for(int j = 0;j<size.height();j++){
QLayoutItem* item = takeAt(i*size.width() + j);
if(item != NULL) delete item;
}
}
}
None of these answers worked for me. In my situation, I have several object each with their own QChartView. The idea is that the user selects which object they want to view and a common area central in the main window is updated with the QChartView of the user-selected object. This should have been straightforward, simply remove the widget from the chart area, add a new one. What ended up working for me was this:
while( QLayoutItem* item = ui->mPlotArea->layout()->takeAt(0) )
{
item->widget()->setVisible(false);
ui->mPlotArea->layout()->removeItem(item);
}
ui->mPlotArea->layout()->addWidget( pv );
pv->setVisible(true);
Where mPlotArea is a QFrame and pv is a derived class of QChartView. I can't explain why the other answers did not worked, but I spent a couple of hours trying different widgets and different ways of removing without deleting, organizing, etc...