C++ Qt How to add widgets on scroll area? - c++

I'm trying to show custom widgets in a scroll area, but it only shows the last one.
Scroll area contents has to change dinamicly with combo box index, and they change and show the last element too.
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
fileMenu = ui->menuBar->addMenu(tr("&Archivo"));
openFileAction = new QAction(tr("Abir archivo"), this);
connect(openFileAction,
SIGNAL(triggered()),
this,
SLOT(openFile()));
fileMenu->addAction(openFileAction);
scroll = ui-> scrollArea;
scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
scroll->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
scroll->setWidgetResizable(true);
ui->stackedWidget->setCurrentIndex(0);
}
vector<product> MainWindow::filter(QString cad)
{
vector <product> tempList;
QMessageBox message;
for(size_t i(0);i<products.size();i++){
if(products.at(i).getId().contains(cad)){
product *p = new product;
p->setId(products.at(i).getId());
p->setName(products.at(i).getName());
p->setPrice(products.at(i).getPrice());
tempList.push_back(*p);
}
}
return tempList;
}
void MainWindow::loadproducts(int category){
QMessageBox message;
vector <product> tempList;
switch(category){
case Alimentos:{
tempList=filter("AB");
break;
}
case Libros:{
tempList=filter("L");
break;
}
case Electronicos:{
tempList=filter("E");
break;
}
case Hogar:{
tempList=filter("HC");
break;
}
case Deporte:{
tempList=filter("D");
break;
}
case Todos:{
tempList=products;
break;
}
default:{
break;
}
}
//THIS FOR IS SUPPOSED TO ADD WIDGETS TO SCROLL AREA
for(size_t i=0;i<tempList.size();i++){
ProductWidget *p = new ProductWidget(widget, tempList.at(i).getId(), tempList.at(i).getName(), tempList.at(i).getPrice());
scroll->setWidget(p);
}
tempList.clear();
}
Scroll area has to show 10 widgets, but it only shows the last one.

You can only set a widget in the QScrollArea using the setWidget() method, if another one is added it will replace the previous one. If you want to show several widgets then you must place them all within a widget, and that last widget set it in the QScrollArea. For example in your case:
// ...
QWidget *container = new QWidget;
scroll->setWidget(container);
QVBoxLayout *lay = new QVBoxLayout(container);
for(size_t i=0;i <tempList.size(); i++){
ProductWidget *p = new ProductWidget(...);
lay->addWidget(p);
}
// ...

Related

QPixmap load many images in Qt

I am trying to load images from a folder and view the previous and next images by clicking a button on the GUI (similar to Windows Image Viewer). The names of the images in this folder are xxxx_00.jpg to xxxx_99.jpg, so I use index++ and index-- to change the filename when clicking the buttons.
My codes work well for displaying the first image, but when I click a button to view the previous or the next images, it always shows
QPixmap::scaled: Pixmap is a null pixmap
and returns an empty image (the first image disappeared but new image didn't display).
Here's my code:
In mainwindow.cpp
void MainWindow::on_pushButton_4_clicked() //previous image
{
if (index < 1)
{
index = 99;
QLabel label;
label.setText("Go back");
label.show();
}
else
{
index--;
}
RefreshFilename();
loadimage();
}
void MainWindow::on_pushButton_5_clicked() //next image
{
if (index > 98)
{
index = 0;
QLabel label;
label.setText("ALL SET");
label.show();
}
else
{
index = index + 1;
}
RefreshFilename();
loadimage();
}
void MainWindow::loadimage()
{
// image.load(filename);
// im = image.scaled(500,500,Qt::KeepAspectRatio);
imageObject = new QImage();
imageObject->load(filename);
image = QPixmap::fromImage(*imageObject);
im = image.scaled(400,400,Qt::KeepAspectRatio);
scene = new QGraphicsScene(this);
scene->addPixmap(image);
scene->setSceneRect(image.rect());
ui->mainimage->setScene(scene);
}
I have spent 2 whole days on debugging this, but still have no idea. I am looking forward to any advice and support!
BTW The Refreshfilename Function works fine, so I did not paste it here.
Cause
Since I do not know what RefreshFilename(); does, I cannot tell exactly what the cause is.
However, I see a major flaw in your code, i.e. you create a new scene each time MainWindow::loadimage is called and this causes a memory leak.
When you provide more details I will be more specific here.
Solution
Set the scene once and add a QGraphicsPixmapItem to it, then in the loadImage update the pixmap of the item.
Keep the current number in a class attribute.
Again, I will be more specific once details are added.
Example
In any case (waiting for you to provide a MVCE), I have prepared a working example according to your task description:
#define IMAGE_COUNT 99
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
m_imgNum(0),
m_item(new QGraphicsPixmapItem())
{
auto *widget = new QWidget(this);
auto *layoutMain = new QVBoxLayout(widget);
auto *layoutButtons = new QHBoxLayout();
auto *btnPrev = new QPushButton(tr("Previous"), this);
auto *btnNext = new QPushButton(tr("Next"), this);
auto *view = new QGraphicsView(this);
view->setScene(new QGraphicsScene(this));
view->scene()->addItem(m_item);
layoutButtons->addStretch();
layoutButtons->addWidget(btnPrev);
layoutButtons->addWidget(btnNext);
layoutButtons->addStretch();
layoutMain->addWidget(view);
layoutMain->addLayout(layoutButtons);
setCentralWidget(widget);
resize(640, 480);
loadImage();
connect(btnPrev, &QPushButton::clicked, [this](){
if (m_imgNum > 0)
m_imgNum--;
else
m_imgNum = IMAGE_COUNT;
loadImage();
});
connect(btnNext, &QPushButton::clicked, [this](){
if (m_imgNum < IMAGE_COUNT)
m_imgNum++;
else
m_imgNum = 0;
loadImage();
});
}
void MainWindow::loadImage()
{
m_item->setPixmap(QString("images/image_%1.jpg").arg(m_imgNum, 2, 10, QChar('0')));
}
where m_imgNum, m_item and loadImage are declared in the header as:
private:
inline void loadImage();
int m_imgNum;
QGraphicsPixmapItem *m_item;

How to show the row where QPushButton is clicked in QTableWidget

I would like to delete row where QPushButton is clicked how it is possible to I think it is reasonable to use slots but how to do it don't know , if you have any ideas how to get a row of selected button please share, thanks.
It is my table
It is a code where i add rows to my QTableWidget
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
for(int i = 0; i<20;i++)
ui->tableWidget->insertRow(ui->tableWidget->rowCount());
QVector<QString>vec;
vec<<"first"<<"sec"<<"third"<<"for"<<"fif"<<"first"<<"sec"
<<"third"<<"for"<<"fif";
vec<<"first"<<"sec"<<"third"<<"for"<<"fif"<<"first"<<"sec"
<<"third"<<"for"<<"fif";
for(int i = 0; i<ui->tableWidget->rowCount();i++)
{
for(int j = 0; j<ui->tableWidget->columnCount();j++)
{
if(j == 0)
{
QWidget* pWidget = new QWidget();
QPushButton* btn_edit = new QPushButton();
btn_edit->setText("Remove");
QHBoxLayout* pLayout = new QHBoxLayout(pWidget);
pLayout->addWidget(btn_edit);
pLayout->setAlignment(Qt::AlignCenter);
pLayout->setContentsMargins(0, 0, 0, 0);
pWidget->setLayout(pLayout);
ui->tableWidget->setCellWidget(i, j, pWidget);
continue;
}
QTableWidgetItem*item = new QTableWidgetItem(vec[i]);
item->setFlags(item->flags() ^ Qt::ItemIsEditable);
ui->tableWidget->setItem(i, j, item);
}
}
}
This task can be solved using the removeRow() method but before we must get the row. First of all we will connect all the buttons to a slot inside the loop as shown below:
*.h
private slots:
void onClicked();
*.cpp
[...]
QPushButton* btn_edit = new QPushButton();
btn_edit->setText("Remove");
connect(btn_edit, &QPushButton::clicked, this, &MainWindow::onClicked);
[...]
In the slot we can get the button that emits the signal through the sender() method, then we get QWidget parent (created with the name pWidget), this is the widget that is added to the QTableWidget and its position is relative to it, then we use the method indexAt() to get the QModelIndex associated with the cell, and this has the information of the row through the method row(). All of the above is implemented in the following lines:
void MainWindow::onClicked()
{
QWidget *w = qobject_cast<QWidget *>(sender()->parent());
if(w){
int row = ui->tableWidget->indexAt(w->pos()).row();
ui->tableWidget->removeRow(row);
ui->tableWidget->setCurrentCell(0, 0);
}
}
Note: The setCurrentCell() method is used to set the focus since the last cell that has it has been deleted.
The complete example can be found in the following link.
When you are creating QPushButton just add :
btn_delete = new QPushButton("Remove", ui->tableWidget);
btn_delete->setObjectName(QString("%1").arg(ui->tableWidget->rowCount()));
connect(btn_delete, SIGNAL(clicked()), this, SLOT(CellButtonDeleteClicked()));
And create function CellButtonDeleteClicked()
void CellButtonDeleteClicked()
{
// by this line I can get the sender of signal
QPushButton *pb = qobject_cast<QPushButton *>(QObject::sender());
int row = pb->objectName().toInt();
ui->tableWidget->removeRow(row);
}

how to get row number after comboBox item in QTableWidget in qt

I would like to get a number of a QTableWidget row after selecting some topic in comboBox how it is possible to get the row, thanks.
void MainWindow::metto_stringa(int i)
{
QWidget *w = qobject_cast<QWidget *>(sender()->parent());
if(w)
{
int row = ui->tableWidget->indexAt(w->pos()).row();
ui->lineEdit->setText(QString::number( row ));
}
// ui->lineEdit->setText(QString::number( i ));
}
else if(i == 3)
{
// ui->tableWidget->setCellWidget(ui->tableWidget->rowCount(), i, "");
QString s = "Normal";
QComboBox *combo = new QComboBox;
combo->addItem("Below normal");
combo->addItem("Normal");
combo->addItem("Above normal");
combo->addItem("High");
combo->addItem("Real time");
connect(combo,SIGNAL(currentIndexChanged(int)),this,
SLOT(metto_stringa(int)));
ui->tableWidget->setCellWidget(ui->tableWidget->rowCount()-1, i,combo);
/* ui->tableWidget->setCellWidget(i,4,combo);
QTableWidgetItem*item = new QTableWidgetItem(s);
item->setFlags(item->flags() ^ Qt::ItemIsEditable);
ui->tableWidget->setItem(ui->tableWidget->rowCount()-1, i,
item);*/
continue;
}
In this case you should not use the parent of the QComboBox, you must use the same sender()
void MainWindow::metto_stringa(int index)
{
QWidget *w = qobject_cast<QWidget *>(sender());
if(w)
{
int row = ui->tableWidget->indexAt(w->pos()).row();
ui->lineEdit->setText(QString::number(row));
}
}
In the question I answered before I commented that you must access the widget that you use in the setCellWidget() function, in the previous case the widget had the following form:
QWidget <--- QPushButton
parent() sender()
ie you owe to that widget so we take advantage of sender() and parent() in the previous case. In the current case QComboBox is added directly.

Hide or Show QStackedWidget Items in Qt

I want to show or hide items in QStackedWidget. When I press Enter button it should show a stacked element and when I press say a left button it should hide.
I use QStackedWidget and QListWidget. My code:
mymainwindow.h:
#ifndef MYMAINWINDOW_H
#define MYMAINWINDOW_H
class mymainwindow : public QMainWindow
{
Q_OBJECT
public:
mymainwindow();
protected:
void keyPressEvent(QKeyEvent *event);
private:
QStackedWidget *stack;
QListWidget *list;
QVBoxLayout *vertical;
QWidget *widget;
};
#endif
mymainwindow.cpp:
#include "mymainwindow.h"
mymainwindow::mymainwindow() : QMainWindow()
{
stack = new QStackedWidget();
list = new QListWidget();
stack->addWidget(new QLineEdit("Hello U have clicked the first menu"));
stack->addWidget(new QLineEdit("Second ListWidget Item"));
stack->addWidget(new QLineEdit("Last Widget Item"));
widget = new QWidget();
QLabel *label = new QLabel("Main Window");
list->addItem("New Item 1");
list->addItem("New Item 2");
list->addItem("New Item 3");
list->setFixedSize(200,100);
QVBoxLayout *vertical = new QVBoxLayout();
vertical->addWidget(label);
vertical->addWidget(list);
vertical->addWidget(stack);
widget->setLayout(vertical);
setCentralWidget(widget);
}
void mymainwindow::keyPressEvent(QKeyEvent *event)
{
switch (event->key()) {
case Qt::Key_Down:
connect(list, SIGNAL(currentRowChanged(int)), stack, SLOT(setCurrentIndex(int)));
break;
case Qt::Key_Up:
connect(list, SIGNAL(currentRowChanged(int)), stack, SLOT(setCurrentIndex(int)));
break;
case Qt::Key_Left:
break;
}
}
You will need to handle the Key_Left and Key_Enter cases in your key press event handler. It seems that you would simply like to show and hide the stackwidget based on the press of those two buttons. This is a simple QWidget operation, and the problem is not much related to QStackedWidget.
You would need to change the key press event code as follows:
void mymainwindow::keyPressEvent(QKeyEvent *event)
{
switch (event->key()) {
case Qt::Key_Down:
connect(list,SIGNAL(currentRowChanged(int)),stack,SLOT(setCurrentIndex(int)));
break;
case Qt::Key_Up:
connect(list,SIGNAL(currentRowChanged(int)),stack,SLOT(setCurrentIndex(int)));
break;
case Qt::Key_Left:
stack->show(); // <---- Added
break;
case Qt::Key_Enter: // <---- Added
stack->hide(); // <---- Added
break; // <---- Added
}
}

Why doesnt the old widget get removed?

Because of the mouseEvent, I would expect the red and blue widgets to switch places on every click. Instead the red switches to the blue and then it never switches back, why?
Frame *red = NULL;
Frame *blue = NULL;
bool isRed = true;
Frame::Frame(QWidget *parent) :
QFrame(parent)
{
}
Frame::~Frame(){
printf("deleted.\n");
fflush(0);
}
void QLayout_clear(QLayout* layout, bool deleteWidgets){
QLayoutItem* item;
QLayout* childLayout;
while ((item = layout->takeAt(0)) != NULL){
QWidget* widget = item->widget();
if (widget != NULL){
layout->removeWidget(widget);
if (deleteWidgets){
delete widget;
}
} else if (childLayout = item->layout()){
QLayout_clear(childLayout, deleteWidgets);
}
//delete item;
}
}
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
QVBoxLayout *layout = new QVBoxLayout;
this->setLayout(layout);
red = new Frame;
red->setFixedSize(100,100);
red->setStyleSheet("background-color:red");
blue = new Frame;
blue->setFixedSize(100,100);
blue->setStyleSheet("background-color:blue");
layout->addWidget(red);
}
void Widget::mouseReleaseEvent(QMouseEvent *){
printf("clicked.\n");
fflush(0);
QVBoxLayout *layout = (QVBoxLayout *)this->layout();
if (1){ //it doesnt matter if this is 1 or 0
delete layout;
layout = new QVBoxLayout;
this->setLayout(layout);
} else {
QLayout_clear(layout, false);
}
if (isRed){
layout->addWidget(blue);
isRed = false;
} else {
layout->addWidget(red);
isRed = true;
}
}
Note: here I am using a simple QFrame for the widgets to switch, in my application the widgets are much more complicated and I cannot recreate them every time I want to swap.
I believe it is not good practice from performance point of view to delete and recreate layers each time you want to switch frames, it would be easier to add both frames to the layer and then set visible only frame you want to appear. Also, boolean isRed is not necessary as info about visibility is contained within each frame itself...
Try this:
QFrame *red = NULL;
QFrame *blue = NULL;
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
QVBoxLayout *layout = new QVBoxLayout(this);
red = new QFrame(this);
red->setFixedSize(100,100);
red->setStyleSheet("background-color:red");
red->setHidden(false);
blue = new QFrame(this);
blue->setFixedSize(100,100);
blue->setStyleSheet("background-color:blue");
blue->setHidden(true);
layout->addWidget(red);
layout->addWidget(blue);
this->setLayout(layout);
}
void Widget::mouseReleaseEvent(QMouseEvent *){
printf("clicked.\n");
fflush(0);
red->setHidden(!red->isHidden());
blue->setHidden(!blue->isHidden());
}
The layout destructs its children. Because addWidget takes ownership so delete layout will delete red and blue widgets too.