How to replace QPushButton with QLabel in CentralWidget in Qt? - c++

I am trying to learn and build a small minesweeper gui app.Here is how this looks like:
The next thing I want to do is after clicking one button, then the button will be set to hide() and a QLabel will appear in the same place.
My code is like this:
.h
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
void setUI();
void clickedBtnInfo();
private:
Ui::MainWindow *ui;
QWidget *centralWidget;
QGridLayout *centralLayout;
QPushButton *btn[81];
QPushButton *btnSender;
QLabel *lbl[81];
QString clickedBtnName;
private slots:
void btnClicked();
};
.cpp
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
centralWidget = new QWidget(this);
setCentralWidget(centralWidget);
lbl[81] = new QLabel(centralWidget);
setUI();
for(int i = 0; i < 81; i++) {
connect(btn[i], SIGNAL(clicked(bool)), btn[i], SLOT(hide()));
connect(btn[i], SIGNAL(clicked(bool)), this, SLOT(btnClicked()));
}
centralWidget->setLayout(centralLayout);
}
void MainWindow::setUI()
{
...
centralLayout = new QGridLayout(centralWidget);
for(int i = 0; i < 9; i++) {
for(int j = 0; j < 9; j++) {
centralLayout->addWidget(btn[j + i * 9], 0 + i, j);
centralLayout->setSpacing(0);
}
}
...
}
void MainWindow::clickedBtnInfo()
{
btnSender = qobject_cast<QPushButton*>(sender());
clickedBtnName = btnSender->objectName();
}
void MainWindow::btnClicked()
{
clickedBtnInfo();
for(int i = 0; i < 9; i++) {
for(int j = 0; j < 9; j++) {
if(btn[j + i * 9]->objectName() == clickedBtnName) {
centralLayout->addWidget(lbl[j + i * 9], 0 + i, j);
centralLayout->setSpacing(0);
}
}
}
}
When I ran this and clicked one of the buttons, the app just force quit(The program has unexpectedly finished.)
So how can I solve this problem and replace QPushButton with QLabel after clicking? Thanks.

The Problem why your code leads to a crash was correctly pointed out by #G.M. - lbl[81] = new QLabel(centralWidget); will create only 1 label, and place it in the 81st array field. That are 2 errors at once:
If your array is 81 elements long, they are numbered: 0, 1, ..., 79, 80. The last element is 80, because you start counting at 0. So placing something at position 81 is not possible
To actually create 81 new labels, you have to create them in a loop:
Sample code:
for(int i = 0; i < 81; i++) { //goes from 0 to 80
lbl[i] = new QLabel(centralWidget);
lbl[i]->setObjectName(QStringLiteral("Label %1").arg(i));
}
The second line gives each label a custom name. See QString::arg for details.
One more tip: Avoid C-arrays, unless you need high performace/low memory (which is not the case for your example). Instead try to use one of the Qt container classes, e.g. QList or QVector. (You can use std::vector etc. as well, but when working with Qt, I would recommend to use the Qt containers)
For your case, I would recommend QVector, as it performs best with fixed-sized arrays. With both of these changes, update your code to:
class MainWindow : public QMainWindow
{
//...
private:
Ui::MainWindow *ui;
QWidget *centralWidget;
QGridLayout *centralLayout;
QVector<QPushButton> btn;
QPushButton *btnSender;
QVector<QLabel> lbl;
QString clickedBtnName;
};
In your cpp file, update the part where you create the arrays:
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
centralWidget = new QWidget(this);
setCentralWidget(centralWidget);
lbl.resize(81);
for(int i = 0; i < lbl.size(); i++) { //goes from 0 to 80
lbl[i] = new QLabel(centralWidget);
lbl[i]->setObjectName(QStringLiteral("Label %1").arg(i));
}
setUI();
//...
}
void MainWindow::setUI()
{
//keep your code, but remember to prepare the btn vector with:
btn.resize(81);
//then you can fill the vector just like you are used to:
btn[0] = ui->btn0;
//...
}
And the rest stays the same, as these classes allow you to keep the standard array access syntax you know.

Related

Qt connect multiple signal with slot using function

I would like to set the font color and the background color if I click on a button.
I just added these two functions to the mainwindow.h file
public:
void createGrid();
private slots:
void clickCell(int row, int col);
mainwindow.cpp
QVector<QVector<QPushButton*>> buttons(10);
void MainWindow::createGrid() {
QFrame *frame = new QFrame(this);
QGridLayout *layout = new QGridLayout(frame);
layout->setMargin(0);
layout->setSpacing(0);
for(int i = 0; i < 10; ++i){
buttons[i].resize(10);
for(int j = 0; j < 10; ++j){
QPushButton *button = new QPushButton("0");
button->setMinimumSize(50,50);
button->setMaximumSize(50,50);
connect(button,SIGNAL(released()),this,SLOT(clickCell(i,j)));
layout->addWidget(button,i,j);
buttons[i][j] = button;
}
}
setCentralWidget(frame);
}
void MainWindow::clickCell(int row, int col) {
buttons[row][col]->setStyleSheet("background-color: grey; color: red");
}
When I run my code I am getting the following outputs about 100 times:
QObject::connect: No such slot MainWindow::clickCell(i,j) in ..\untitled\mainwindow.cpp:41
QObject::connect: (receiver name: 'MainWindow')
As per the comment: use the new signal/slot syntax to invoke a lambda as the slot. So your connect call should be something like (untested)...
connect(button, &QPushButton::released, this,
[=, this]
{
clickCell(i, j);
});

Qt Can't set centralWidget() properly

I tried to build a simple GUI application. Then I got those warnings:
QLayout: Attempting to add QLayout "" to MainWindow "", which already has a layoutQWidget::setLayout: Attempting to set QLayout "" on MainWindow "", which already has a layout
I googled that you have to set central widget in MainWindow. And here is my implement which is still not working:
.h
class MainWindow : public QMainWindow
{
Q_OBJECT
QWidget *centralWidget;
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
void setButtons();
...
private:
Ui::MainWindow *ui;
QPushButton *btn[9][9];
}
.cpp
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
centralWidget = new QWidget(this);
this->setCentralWidget(centralWidget);
...
}
void MainWindow::setButtons()
{
for(int i = 0; i < 9; i++) {
for(int j = 0; j < 9; j++) {
btn[i][j] = new QPushButton(this);
...
QVBoxLayout *vLayout = new QVBoxLayout(this);
vLayout->addWidget(btn[i][j]);
centralWidget->setLayout(vLayout);
}
}
}
After trying this, I still received the warning messages, how can I solve this problem?
Thanks.
Your code has two basic issues. Firstly, the statement...
QVBoxLayout *vLayout = new QVBoxLayout(this);
will instantiate a new QVBoxLayout with this as its parent. Since this is of type MainWindow * and MainWindow inherits from QMainWindow you are effectively calling QMainWindow::setLayout -- that's the source of the error message...
QLayout: Attempting to add QLayout "" to MainWindow "", which already
has a layout
Secondly, you're creating a new QVBoxLayout on each loop iteration. If you really do want the buttons vertically aligned in a layout try something like...
void MainWindow::setButtons ()
{
QVBoxLayout *vLayout = new QVBoxLayout(centralWidget);
for (int i = 0; i < 9; i++) {
for (int j = 0; j < 9; j++) {
btn[i][j] = new QPushButton;
...
vLayout->addWidget(btn[i][j]);
}
}
}

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 change property of more than one qlineedit at once

I have done some coding for L00 (name of my first QLineEdit).
I want to do the same for other 99 widgets (up to L99). I don't wanna copy paste it 100 hundred times.
void MainWindow::on_L00_returnPressed(){
QString c = ui->L00->text();
char d = QString(c).at(0).toLatin1();
if(d>=65&&d<=90||d>=97&&d<=122)
{
square[0]=d;
ui->MessageBox->setText("Alphabet succesfully entered");
ui->L00->setReadOnly(true);
ui->L00->setStyleSheet("QLineEdit { background: rgb(0, 255, 255);}");
}
else
{
ui->MessageBox->setText("Enter Alphabets Only");
ui->L00->setText("");
}
}
If you add all these widget through Qt Designer the objects will have the same name of the variable, so we can get them through findChild, then apply connect the signal ReturnPressed to a lambda function and not having to create a slot for each QLineEdit.
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
int n = 100;
for(int i=0; i<n; i++){
QString name = QString("L%1").arg(i, 2, 10, QChar('0'));
QLineEdit* w = findChild<QLineEdit *>(name);
connect(w, &QLineEdit::returnPressed, [=](){
QString c = w->text();
char d = QString(c).at(0).toLatin1();
if((d>=65&& d<=90)||(d>=97&&d<=122))
{
square[i]=d;
ui->MessageBox->setText("Alphabet succesfully entered");
w->setReadOnly(true);
w->setStyleSheet("QLineEdit { background: rgb(0, 255, 255);}");
}
else
{
ui->MessageBox->setText("Enter Alphabets Only");
w->clear();
}
});
}
}
Note: I have considered that square[i] corresponds to the QLineEdit L-i.
Another solution is to connect all to the same slot and obtain the object through the function sender().
*.h
private slots:
void onReturnPressed();
*.cpp
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
for(QLineEdit* w: findChildren<QLineEdit *>()){
connect(w, &QLineEdit::returnPressed, this, &MainWindow::onReturnPressed);
}
}
void MainWindow::onReturnPressed()
{
QLineEdit* w =qobject_cast<QLineEdit *>(sender());
int i = w->objectName().remove(0, 1).toInt();
QString c = w->text();
char d = QString(c).at(0).toLatin1();
if((d>=65&& d<=90)||(d>=97&&d<=122))
{
square[i]=d;
ui->MessageBox->setText("Alphabet succesfully entered");
w->setReadOnly(true);
w->setStyleSheet("QLineEdit { background: rgb(0, 255, 255);}");
}
else
{
ui->MessageBox->setText("Enter Alphabets Only");
w->clear();
}
}
For old version:
*.h
private slots:
void onReturnPressed();
.cpp
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
int n = 100;
for(int i=0; i<n; i++){
QString name = QString("L%1").arg(i, 2, 10, QChar('0'));
QLineEdit* w = findChild<QLineEdit *>(name);
if(w)
connect(w, SIGNAL(returnPressed()), this, SLOT(onReturnPressed()));
}
}
void MainWindow::onReturnPressed()
{
QLineEdit* w =qobject_cast<QLineEdit *>(sender());
int i = w->objectName().remove(0, 1).toInt();
QString c = w->text();
char d = QString(c).at(0).toLatin1();
if((d>=65&& d<=90)||(d>=97&&d<=122))
{
square[i]=d;
ui->MessageBox->setText("Alphabet succesfully entered");
w->setReadOnly(true);
w->setStyleSheet("QLineEdit { background: rgb(0, 255, 255);}");
}
else
{
ui->MessageBox->setText("Enter Alphabets Only");
w->clear();
}
}
You can use the QObject::findChildren to do so. It must be called from the parent widget (your MainWindow). You can from it connect the QLineEdit individually, or change all values at once.
Example:
for (auto lineEdit : this->findChildren<QLineEdit*>()) {
// connect lineEdit or change values
}
Problem starts with:
I have done some coding for L00 (name of my first QLineEdit) … 99 widgets (up to L99).
This means that you designed your application incorrectly.
I'm almost sure that you should use QTableView or QTableWidget with QAbstractTableModel or QStandartItemModel.
Also setStyleSheet should be applied on QApplication or at least on top level widget. This is how stylesheet is supposed to be used.

Qt5 paintEvent not called inside QScrollArea

I'm having a bit of a problem with Qt. I'm trying to create a 2D drawing of cells, with QRect, by overloading paintEvent for a custom class which inherits QWidget and which is placed inside a QScrollArea. The problem is, paintEvent does not trigger at all (not on resize events, not when I call repaint() or update(), nor when I launch my program). Here is where I overload paintEvent, in GOL.cpp:
void GOL::paintEvent(QPaintEvent *) {
QPainter painter(this);
//painter.setPen(Qt::black);
int x1Rect = rectPaint.x();
int y1Rect = rectPaint.y();
int x2Rect = x1Rect + rectPaint.width();
int y2Rect = y1Rect + rectPaint.height();
int xCell;
int yCell = 0;
for (int i = 0; i < rows; i++) {
xCell = 0;
for (int j = 0; j < cols; j++) {
if (xCell <= x2Rect && yCell <= y2Rect && xCell + cellSize >= x1Rect &&
yCell + cellSize >= y1Rect) {
if (principalMatrix->get(i,j)) {
painter.fillRect(xCell, yCell, cellSize - 1, cellSize - 1, cellColourAlive);
}
else {
painter.fillRect(xCell, yCell, cellSize - 1, cellSize - 1, cellColourDead);
}
}
xCell += cellSize;
}
yCell += cellSize;
}
}
And my layout is as follows, in DisplayGame.cpp:
DisplayGame::DisplayGame(QWidget *parent, int threads_no, int generations, char* file_in, char* file_out) :
QWidget(parent) {
gol = new GOL(threads_no, generations, file_in, file_out);
QHBoxLayout *title = setupTitle();
QHBoxLayout *buttons = setupButtons();
QVBoxLayout *layout = new QVBoxLayout();
scrlArea = new QScrollArea;
scrlArea->setWidget(gol);
layout->addLayout(title);
layout->addWidget(scrlArea);
layout->addLayout(buttons);
setLayout(layout);
}
I honestly have no idea why it does not draw anything. Any ideas?
I've fixed it by modifying as follows:
DisplayGame::DisplayGame(QWidget *parent, int threads_no, int generations, char* file_in, char* file_out) :
QWidget(parent) {
gol = new GOL(this, threads_no, generations, file_in, file_out);
QSize *adjustSize = new QSize(gol->cellSize, gol->cellSize); //QSize object that is as big as my QRect matrix
adjustSize->setWidth(gol->cellSize * gol->rows);
adjustSize->setHeight(gol->cellSize * gol->cols);
gol->setMinimumSize(*adjustSize);
QVBoxLayout *layout = new QVBoxLayout;
QHBoxLayout *title = setupTitle();
layout->addLayout(title);
QHBoxLayout *buttons = setupButtons();
layout->addLayout(buttons);
QPalette pal(palette()); //Setting the background black, so the white spaces between QRect items cannot be seen (though I could have modified the margins?)
pal.setColor(QPalette::Background, Qt::black);
scrlArea = new QScrollArea(this);
scrlArea->setAutoFillBackground(true);
scrlArea->setPalette(pal);
scrlArea->setWidget(gol);
layout->addWidget(scrlArea);
setLayout(layout);
}
And I've left the paintEvent as it was. It was, ultimately, a size problem, as AlexanderVX said.