I am trying to make a make a program that allows a user to right click a cell in a gridView and choose from a menu of actions.
I have the menu display, but am having trouble getting the signal and slot to work without getting errors. I am working in Qt 5.5 C++11, not very advanced with Qt or C++ yet so any help would be greatly appreciated
using namespace std;
QStandardItemModel* model;
QStandardItem *value;
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
model = new QStandardItemModel(9,9,this);
ui->tableView->setModel(model);
ui->tableView->setShowGrid(true);
ui->tableView->setWordWrap(true);
//to allow menu
ui->tableView->setContextMenuPolicy(Qt::CustomContextMenu);
for(int row = 0; row < 9; row++){
ui->tableView->setRowHeight(row, 75);
for(int col = 0; col < 9; col++){
ui->tableView->setColumnWidth(col, 75);
QFont f("Consolas");
f.setPointSize(10);
value = new QStandardItem(QString("1 2 3\n4 5 6\n7 8 9"));
if ((row < 3 && col < 3) || (row > 5 && col < 3)
|| (row < 3 && col > 5) || (row > 5 && col > 5)
|| ((row > 2 && row < 6) && (col > 2 && col < 6))){
QBrush b(QColor("Moccasin"));
value->setBackground(b);
}
value->setFont(f);
value->setTextAlignment(Qt::AlignCenter);
model->setItem(row,col,value);
}
}
//connects model so functions run when a cell's text is changed
connect(model, SIGNAL(itemChanged(QStandardItem*)), this, SLOT(on_cell_changed(QStandardItem*)));
//this sets up the menu
connect(ui->tableView, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(menuRequest(QPoint)));
}
this is the main window part of the program. It is (going to be) a sudoku solver, so the model represents the 9x9 grid and the for loop is populating the grid with choices for input (but that's not the thing I'm having trouble with). I need help with the menu part below (the above was just for context):
void MainWindow::menuRequest(QPoint pos)
{
QModelIndex index = ui->tableView->indexAt(pos);
std::cout << "MainWindow::menuRequest - at" << " QModelIndex row = " <<
index.row() << ", column = " << index.column() << std::endl;
QMenu menu(this);
QMenu setValues("Initialize Grid", this);
QAction *setValue1;
QAction *setValue2;
QAction *setValue3;
QAction *setValue4;
QAction *setValue5;
QAction *setValue6;
QAction *setValue7;
QAction *setValue8;
QAction *setValue9;
setValue1 = setValues.addAction("Set value to 1");
setValue2 = setValues.addAction("Set value to 2");
setValue3 = setValues.addAction("Set value to 3");
setValue4 = setValues.addAction("Set value to 4");
setValue5 = setValues.addAction("Set value to 5");
setValue6 = setValues.addAction("Set value to 6");
setValue7 = setValues.addAction("Set value to 7");
setValue8 = setValues.addAction("Set value to 8");
setValue9 = setValues.addAction("Set value to 9");
connect(menu, SIGNAL(triggered(QAction*)), this, SLOT(on_menu_clicked(QAction*)));
menu.addMenu(&setValues);
QAction *action = menu.exec(
ui->tableView->viewport()->mapToGlobal(pos));
}
void MainWindow::on_menu_clicked(QAction*){
cout << "test menu click worked";
}
The connect statement in the MenuRequest function is throwing an error:
connect(menu, SIGNAL(triggered(QAction*)), this, SLOT(on_menu_clicked(QAction*)));
C2664: 'QMetaObject::Connection QObject::connect(const QObject *, const char *, const char *, Qt::ConnectionType)const' : cannot convert argument 1 from 'QMenu' to 'const QObject *'
When it is supposed to go to the on_menu_click function it throws the above error
I know that means that menu isn't of the QObject type but I'm not sure how to fix this. Any help would be greatly appreciated
As stated in the comment your first argument in the connect() function needs to be a pointer. The documentation States the following:
connect(const QObject * sender, const char * signal,
const QObject * receiver, const char * method,
Qt::ConnectionType type = Qt::AutoConnection)
http://doc.qt.io/qt-5/qobject.html#connect
Related
Say I'm creating an image gallery view that has 3 columns (i.e. it displays 3 images on each row). I can add new images to the end by using division and remainder:
int row = images.size() / 3;
int col = images.size() % 3;
gridLayout->addWidget(myImage, row, col);
The problem arises when I want to remove an image from the middle; it now leaves that middle row with just two images instead of "shifting" everything back and filling each row with 3 images (except the last row).
It seems like QGridLayout doesn't have too many features, am I missing something here or is the only option to implement everything myself? Or am I using the wrong tool (QGridLayout) in the first place?
AFAIK, there is no auto-relayout in QGridLayout. I'm even not sure whether QGridLayout is intended for this purpose.
IMHO, QTableView or QTableWidget might be the better choice.
(Concerning this, QAbstractItemModel::moveRows() comes into my mind.)
However, this doesn't mean that it cannot be achieved.
I made an MCVE to demonstrate this – testQDeleteFromLayoutShift.cc:
#include <cassert>
#include <vector>
#include <QtWidgets>
// comment out to get rid of console diagnostic output
#define DIAGNOSTICS
class PushButton: public QPushButton {
public:
PushButton(const QString &text, QWidget *pQParent = nullptr):
QPushButton(text)
{ }
#ifdef DIAGNOSTICS
virtual ~PushButton() { qDebug() << "Destroyed:" << this << text(); }
#else // (not) DIAGNOSTICS
virtual ~PushButton() = default;
#endif // DIAGNOSTICS
};
// number of columns in grid
const int wGrid = 3;
// fill grid with a certain amount of buttons
std::vector<QPushButton*> fillGrid(QGridLayout &qGrid)
{
const int hGrid = 5;
std::vector<QPushButton*> pQBtns; pQBtns.reserve(wGrid * hGrid);
unsigned id = 0;
for (int row = 0; row < hGrid; ++row) {
for (int col = 0; col < wGrid; ++col) {
QPushButton *pQBtn = new PushButton(QString("Widget %1").arg(++id));
qGrid.addWidget(pQBtn, row, col);
pQBtns.push_back(pQBtn);
}
}
#ifdef DIAGNOSTICS
qDebug() << "qGrid.parent().children().count():"
<< dynamic_cast<QWidget*>(qGrid.parent())->children().count();
qDebug() << "qGrid.count():"
<< qGrid.count();
#endif // DIAGNOSTICS
return pQBtns;
}
// delete a button from grid (shifting the following)
void deleteFromGrid(QGridLayout &qGrid, QPushButton *pQBtn)
{
qDebug() << "Delete button" << pQBtn->text();
// find index of widget in grid
int i = 0;
const int n = qGrid.count();
while (i < n && qGrid.itemAt(i)->widget() != pQBtn) ++i;
assert(i < n);
// find item position in grid
int row = -1, col = -1, rowSpan = 0, colSpan = 0;
qGrid.getItemPosition(i, &row, &col, &rowSpan, &colSpan);
// remove button from layout
QLayoutItem *pQItemBtn = qGrid.itemAt(i);
qGrid.removeItem(pQItemBtn);
// reposition all following button layouts
for (int j = i + 1; j < n; ++j) {
QLayoutItem *pQItem = qGrid.takeAt(i);
const int row = (j - 1) / wGrid, col = (j - 1) % wGrid;
qGrid.addItem(pQItem, row, col);
}
delete pQBtn;
#if 1 // diagnostics
qDebug() << "qGrid.parent().children().count():"
<< dynamic_cast<QWidget*>(qGrid.parent())->children().count();
qDebug() << "qGrid.count():"
<< qGrid.count();
#endif // 0
}
// application
int main(int argc, char **argv)
{
qDebug() << "Qt Version:" << QT_VERSION_STR;
QApplication app(argc, argv);
// setup GUI
QWidget qWin;
qWin.setWindowTitle(QString::fromUtf8("Demo Delete from QGridLayout (with shift)"));
QGridLayout qGrid;
qWin.setLayout(&qGrid);
std::vector<QPushButton*> pQBtns = fillGrid(qGrid);
qWin.show();
// install signal handlers
for (QPushButton *const pQBtn : pQBtns) {
QObject::connect(pQBtn, &QPushButton::clicked,
[&qGrid, pQBtn](bool) {
QTimer::singleShot(0, [&qGrid, pQBtn]() {
deleteFromGrid(qGrid, pQBtn);
});
});
}
// runtime loop
return app.exec();
}
A Qt project file to build this – testQDeleteFromLayoutShift.pro:
SOURCES = testQDeleteFromLayoutShift.cc
QT += widgets
Output in Windows 10 (built with VS2017):
Qt Version: 5.13.0
qGrid.parent().children().count(): 16
qGrid.count(): 15
After clicking on button “Widget 8”:
Delete button "Widget 8"
Destroyed: QPushButton(0x25521e3ef10) "Widget 8"
qGrid.parent().children().count(): 15
qGrid.count(): 14
After clicking on button “Widget 6”
Delete button "Widget 6"
Destroyed: QPushButton(0x25521e3f7d0) "Widget 6"
qGrid.parent().children().count(): 14
qGrid.count(): 13
After clicking on button “Widget 12”
Delete button "Widget 12"
Destroyed: QPushButton(0x25521e46ad0) "Widget 12"
qGrid.parent().children().count(): 13
qGrid.count(): 12
Notes:
I used lambdas for the signal handlers of QPushButton::clicked() to pass the related QGridLayout and QPushButton* to the handler deleteFromGrid() – IMHO the most convenient way.
deleteFromGrid() is called via a QTimer::singleShot() with delay 0. If I would call deleteFromGrid() in the signal handler of QPushButton::clicked() directly, this would result in some kind of Harakiri due to the last line in deleteFromGrid(): delete pQBtn;.
The nested lambdas might look a bit scaring, sorry.
While writing this answer I recalled an older of mine:
SO: qgridlayout add and remove sub layouts
which might be of interest.
How can I add a QSting and an int to a tableView at same time when a button is clicked?
What I want is to have the first column with names and the second column with numbers. Both items need to be added at the same row when a button is clicked.
Col 1--- Col 2---
First Name 1
Second Name 2
Third Name 3
Here is what I have which adds two strings, how can I convert the second cell to be an int?
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
model = new QStandardItemModel();
model->setRowCount(0);
ui->tableView->setModel(model);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_pushButton_clicked()
{
QStandardItem *userName = new QStandardItem(ui->lineEdit_Name->text());
QStandardItem *userNumber = new QStandardItem(ui->lineEdit_Number->text());
QList<QStandardItem*> row;
row <<userName << userNumber;
model->appendRow(row);
}
Thanks a lot
Does it really have to be an integer or can it just look like an integer?
Conventionally in tables, text is displayed left-aligned and numbers are displayed right-aligned. You can get this effect by setting the alignment for your second column:
QStandardItem *userNumber = new QStandardItem(ui->lineEdit_Number->text());
userNumber->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
If you need to use the data as an integer somewhere else in your program, you can convert the QString to an integer when you need it.
Ok, i will gather all in this answer:
First:
every data in a QStandardItemModel is a QVariant, so you can query the data either
to the model with
virtual QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const
to the QStandardItem with:
virtual QVariant data ( int role = Qt::UserRole + 1 ) const
Both will return a QVariant. You can get the int with
int toInt(bool * ok = 0) const
Note that the bool is optional, but will return true if the conversion was posible.
Also, you can check if it can be converted to an int:
bool QVariant::canConvert(int targetTypeId) const
yourVariant.canConvert( QMetaType::Int ) should return true.
To get what you want. I would use takeRow method from QStandardItemModel:
QList<QStandardItem *> takeRow(int row)
That its, the reverse to what you doing to append the row. So you know that you can ask the second element of the returned QList for the Int value.
for(i=0; i<height; i++)
{
for(j=0; j<width; j++)
{
button[i][j] = new QPushButton("Empty", this);
button[i][j]->resize(40, 40);
button[i][j]->move(40*j, 40*i);
connect(button[i][j], SIGNAL(clicked()), this, SLOT(changeText(button[i][j])));
}
}
If i changed function changeText with function (fullScreen for example) it works
but when i use a slot defined by me (changeText) this Error Appears and i don't know how to solve it
QObject::connect: No such slot buttons::changeText(&button[i][j])
and this is the function changeText:
void buttons::changeText(QPushButton* button)
{
button->setText("Fish");
}
NOTE: in the header file i defined the slot like this :
class buttons : public QWidget
Q_OBJECT
public slots:
void changeText(QPushButton* button);
slot can have less arguments then signal but type of arguments it has must match exactly with types of arguments in connected signal.
you can't have dynamic slot like that.
probably what you need is a QSignalMapper.
here is sample:
QSignalMapper *map = new QSignalMapper(this);
connect (map, SIGNAL(mapped(QString)), this, SLOT(changeText(QString)));
for(i=0; i<height; i++)
{
for(j=0; j<width; j++)
{
button[i][j] = new QPushButton("Empty", this);
button[i][j]->resize(40, 40);
button[i][j]->move(40*j, 40*i);
connect(button[i][j], SIGNAL(clicked()), map, SLOT(map()));
map->setMapping(button[i][j], QString("Something%1%2").arg(i).arg(j));
}
}
Probably you can remove a table.
If the SIGNAL doesn't provide certain parameter, the SLOT can't recieve it.
The signal clicked() doesn't provide any parameter. SLOTs receiving it shouldn't have any, either. In any case, you can have a SLOT receiving less parameters than the SIGNAL provides (ignoring some others), but not otherwise. You can, however, get to know the sender of the signal, cast it to QPushButton* and work on it:
void buttons::changeText()
{
QPushButton *pb = qobject_cast<QPushButton *>(sender());
if (pb){
pb->setText("fish");
} else {
qDebug() << "Couldn't make the conversion properly";
}
}
QButtonGroup is a class that has been designed as a handy collection for buttons. It give you direct access to the button which triggered the slot. It also provide you the possibility to register button with a given id. This can be useful if you want to retrieve easily some meta information from the button id.
QButtonGroup* buttongrp = new QButtonGroup();
for(i=0; i<height; i++)
{
for(j=0; j<width; j++)
{
button[i][j] = new QPushButton("Empty", this);
button[i][j]->resize(40, 40);
button[i][j]->move(40*j, 40*i);
buttongrp->addButton(button[i][j], i << 16 + j);
}
}
QObject::connect(buttongrp, SIGNAL(buttonClicked(int)),
this, SLOT(getCoordinates(int)));
QObject::connect(buttongrp, SIGNAL(buttonClicked(QAbstractButton *)),
this, SLOT(changeText(QAbstractButton * button)));
...
void MyObject::changeText(QAbstractButton * button)
{
button->setText("Fish");
}
void MyObject::getCoordinates(int id){
int i = id >> 16;
int j = ~(i << 16) & id;
//use i and j. really handy if your buttons are inside a table widget
}
Usually you don't need to connect to both slots. For the id I assumed that height and width are less that 2^16.
Retrospectively, It seems to me you are reimplementing some of the functions of the button group.
I have a table and each row in the table has a checkbox in it's first column. I need to make it so I can detect which checkboxes are checked and delete those rows when a button is pressed.
QWidget * chkWidget = new QWidget();
QHBoxLayout *center = new QHBoxLayout();
center->setAlignment( Qt::AlignCenter );
center->addWidget( new QCheckBox );
chkWidget->setLayout( center );
ui->data_table->setCellWidget(rowCount,0, chkWidget);
Was this done right? If so how do I access the checkboxes at each row?
I talk about a QTableWidget. You can use a QList.You save your QCheckBox into this QList and use it, when there is some change
Maybe you should check out the documentation
QTableWidget: Link
QList: Link
Here is a solution. I cannot run it at the moment, so please tell me if it works. Please validate the row value. I am not sure if it's possible, that row can have the value -1 when you delete the last row ;)
#include "TestTableWidget.h"
#include "ui_TestTableWidget.h"
TestTableWidget::TestTableWidget(QWidget *parent) : QMainWindow(parent), ui(new Ui::TestTableWidget)
{
ui->setupUi(this);
tableWidget = new QTableWidget(this);
tableWidget->setColumnCount(1); // Just an example
ui->gridLayout->addWidget(tableWidget);
connect(tableWidget, SIGNAL(itemSelectionChanged()), this, SLOT(slotChange()));
for(int i = 1; i < 10; i++)
{
addRow("Row " + QString::number(i));
}
}
TestTableWidget::~TestTableWidget()
{
delete ui;
}
void TestTableWidget::addRow(QString text)
{
int row = tableWidget->rowCount();
qDebug() << "Current row count is " + QString::number(row);
// Add new one
QTableWidgetItem *item = new QTableWidgetItem(text);
tableWidget->insertRow(row);
tableWidget->setItem(row, 0, item);
// Add item to our list
}
void TestTableWidget::slotChange()
{
int row = tableWidget->currentRow();
qDebug() << "Change in table. Current row-index: " + QString::number(row);
// This value is zero-based, so you can use it in our list
}
I am just trying to add widgets into my table widget and I am trying the code below but all the time I run the program, the first widget is added but the rest is not added. Can you please help me for this situation ?
if(req.at(index).request.CodedValue.size() > 1 )
{
int rowNumber = -1;
for ( int paramNumber = 0 ; paramNumber < req.at(index).request.params.size(); paramNumber++)
{
if(req[index].request.params[paramNumber].semantic == "DATA")
{
rowNumber++;
QComboBox* reqComboBox = new QComboBox();
QLineEdit* tableReqLineEdit = new QLineEdit();
for ( int codedCounter = 0; codedCounter < req.at(index).request.CodedValue.at(paramNumber).trams.size(); codedCounter++)
{
// you should look for the subfunctions and add according to them
reqComboBox->addItem((req[index].request.CodedValue[paramNumber].trams[codedCounter].valueName));
QObject::connect(reqComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(on_tableCombobox_currentIndex());
}
ui.tableWidget->setCellWidget(rowNumber,1,reqComboBox);
}
}
}
Use qDebug in order to see how many times the for loop is executed. Probably it is executed only once:
#include <QDebug>
...
rowNumber++;
qDebug() << rowNumber;
...
Try the following:
for (int i=0; i<ui.tableWidget->rowCount(); i++)
{
ui.tableWidget->setCellWidget(i,1,new QLineEdit);
}
How many line edits do you see?
Notice that you should use the setRowCount in order to set the number of rows of your table widget.