I have an 2D array QPushButton, how can I get index of the button when user clicks on its? such as When user clicks on the button a[2][3] it will show (2,3) ?
The example looks like this:
Qt 4/5 Using Object Names
You can give your buttons unique object names. The names should ideally be valid C++ identifiers.
// https://github.com/KubaO/stackoverflown/tree/master/questions/button-grid-22641306
#include <QtGui>
#if QT_VERSION_MAJOR >= 5
#include <QtWidgets>
#endif
struct Display : QLabel {
Q_SLOT void onClicked() {
auto const elements = sender()->objectName().split('_');
auto const i = elements.at(1).toInt();
auto const j = elements.at(2).toInt();
setText(QString{"(%1,%2)"}.arg(i).arg(j));
}
Q_OBJECT
};
int main(int argc, char *argv[])
{
QApplication a{argc, argv};
QWidget window;
QGridLayout layout{&window};
QVarLengthArray<QPushButton, 12> buttons(12);
Display display;
const int rows = 4, columns = 3;
for (int i = 0; i < rows; ++ i)
for (int j = 0; j < columns; ++j) {
auto & button = buttons[i*columns+j];
button.setText(QString{"(%1,%2)"}.arg(i).arg(j));
button.setObjectName(QString{"buton_%1_%2"}.arg(i).arg(j));
layout.addWidget(&button, i, j);
display.connect(&button, SIGNAL(clicked()), SLOT(onClicked()));
}
layout.addWidget(&display, rows, 0, 1, columns);
window.show();
return a.exec();
}
#include "main.moc"
Qt 5 - Using Lambdas
In Qt 5 and C++11, you should use functors to generate custom slot for each button, on the fly. For example:
// https://github.com/KubaO/stackoverflown/tree/master/questions/button-grid-22641306
#include <QtWidgets>
int main(int argc, char *argv[])
{
QApplication a{argc, argv};
QWidget window;
QGridLayout layout{&window};
QVarLengthArray<QPushButton, 12> buttons(12);
QLabel display;
const int rows = 4, columns = 3;
for (int i = 0; i < rows; ++ i)
for (int j = 0; j < columns; ++j) {
auto text = QStringLiteral("(%1,%2)").arg(i).arg(j);
auto & button = buttons[i*columns+j];
button.setText(text);
layout.addWidget(&button, i, j);
QObject::connect(&button, &QPushButton::clicked, [&display, text] {
display.setText(text);
});
}
layout.addWidget(&display, rows, 0, 1, columns);
window.show();
return a.exec();
}
Qt 4/5 - Using QSignalMapper
QSignalMapper is pretty much designed for what you want. It lets you map a QObject* to "something else", like a string. For example:
#include <QtGui>
#if QT_VERSION_MAJOR >= 5
#include <QtWidgets>
#endif
int main(int argc, char *argv[])
{
QApplication a{argc, argv};
QSignalMapper mapper;
QWidget window;
QGridLayout layout{&window};
QVarLengthArray<QPushButton, 12> buttons(12);
QLabel display;
const int rows = 4, columns = 3;
for (int i = 0; i < rows; ++ i)
for (int j = 0; j < columns; ++j) {
auto text = QString{"(%1,%2)"}.arg(i).arg(j);
auto & button = buttons[i*columns+j];
button.setText(text);
layout.addWidget(&button, i, j);
mapper.connect(&button, SIGNAL(clicked()), SLOT(map()));
mapper.setMapping(&button, text);
}
display.connect(&mapper, SIGNAL(mapped(QString)), SLOT(setText(QString)));
layout.addWidget(&display, rows, 0, 1, columns);
window.show();
return a.exec();
}
Qt 4/5 - Using the Property System
You can leverage the fact that a QWidget is a QObject. QObjects have a property system, so you can set each button's index as a property, and then retrieve it in the slot connected to the clicked() signal. For example:
#include <QtGui>
#if QT_VERSION_MAJOR >= 5
#include <QtWidgets>
#endif
const char kIndex[] = "index";
struct Display : QLabel {
Q_SLOT void onClicked() {
setText(sender()->property(kIndex).toString());
}
Q_OBJECT
};
int main(int argc, char *argv[])
{
QApplication a{argc, argv};
QWidget window;
QGridLayout layout{&window};
QVarLengthArray<QPushButton, 12> buttons(12);
Display display;
const int rows = 4, columns = 3;
for (int i = 0; i < rows; ++ i)
for (int j = 0; j < columns; ++j) {
auto index = QString{"(%1,%2)"}.arg(i).arg(j);
auto & button = buttons[i*columns+j];
button.setText(index);
button.setProperty(kIndex, index);
layout.addWidget(&button, i, j);
display.connect(&button, SIGNAL(clicked()), SLOT(onClicked()));
}
layout.addWidget(&display, rows, 0, 1, columns);
window.show();
return a.exec();
}
#include "main.moc"
in general, you would have to loop over the array and test the event target for equality with each element until you reach the correct index
Related
In a QTableView, irrespective if there are entries or not. I want to show grid to it (considering its fixed size QTableView), but only for columns, not for rows.
You have to overwrite the paintEvent() method, in addition to setting the setShowGrid() to false.
#include <QApplication>
#include <QPainter>
#include <QStandardItemModel>
#include <QTableView>
#include <QHeaderView>
#include <QPaintEvent>
class TableView: public QTableView{
public:
using QTableView::QTableView;
protected:
void paintEvent(QPaintEvent *event){
QTableView::paintEvent(event);
if (horizontalHeader()->count() == 0 || verticalHeader()->count() == 0)
return;
QPainter painter(viewport());
QStyleOptionViewItem option;
option.init(this);
const int gridHint = style()->styleHint(QStyle::SH_Table_GridLineColor, &option, this);
const QColor gridColor = static_cast<QRgb>(gridHint);
const QPen gridPen = QPen(gridColor, 0, gridStyle());
painter.setPen(gridPen);
int w = horizontalHeader()->offset();
for(int i=0; i<horizontalHeader()->count(); ++i){
w += horizontalHeader()->sectionSize(i);
painter.drawLine(w-1, 0, w-1,viewport()->height());
}
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
TableView view;
QStandardItemModel model(5, 5);
for(int i=0; i< model.rowCount(); ++i)
for(int j=0; j < model.columnCount(); ++j)
model.setItem(i, j, new QStandardItem(QString("%1-%2").arg(i).arg(j)));
view.setModel(&model);
view.setShowGrid(false);
view.show();
return a.exec();
}
I have subclassed QTreeWidget (called it ToolsSelectorWidget) and enabled reordering in it by overriding QTreeWidget::dropEvent()
void ToolsSelectorWidget::dropEvent(QDropEvent *event) {
QModelIndex droppedIndex = indexAt(event->pos());
if( !droppedIndex.isValid() || droppedIndex.parent().isValid()) {
return;
}
QTreeWidget::dropEvent(event);
}
Also, I am adding QWidgets (QPushButton, QLineEdit) to top level items of QTreeWidget:
ToolsSelectorWidget::ToolsSelectorWidget(QWidget *parent) : QTreeWidget(parent) {
header()->hide();
setSelectionMode(QAbstractItemView::SingleSelection);
setDragEnabled(true);
viewport()->setAcceptDrops(true);
setDropIndicatorShown(true);
setDragDropMode(QAbstractItemView::InternalMove);
for(int i=0; i<4; ++i) {
QTreeWidgetItem *part = new QTreeWidgetItem(this);
part->setFlags(part->flags() & Qt::ItemFlag((~Qt::ItemIsDropEnabled)));
setItemWidget(part, 0, new QLabel("Part" + QString::number(i) + " Preview", this));
setItemWidget(part, 1, new QLineEdit("Part" + QString::number(i) + " Name", this));
setItemWidget(part, 2, new QCheckBox("Part" + QString::number(i) + " Visible", this));
setItemWidget(part, 3, new QCheckBox("Part" + QString::number(i) + " Locked", this));
}
}
So now I have 4 top level items each containing 4 QWidgets. It's populating them fine, but when I rearrange them by drag and drop the QWidgets disappear and I end up having an empty row. What should I do to preserve them?
Before:
After Part2 has been moved and is under Part4, it's children have been preserved, but it's conents, which are QWidgets, are gone:
Why are widgets deleted?
When the drag and drop is performed, the data of the selected items is coded (roles and associated values) and saved in a QMimeData. When the drop is accepted, the source items are deleted and new items are created with the information stored in the QMimeData, inside the saved information there is no widgets information since this does not have relation with the model. And since the items are deleted, their widgets are also deleted.
To check it we can use the following example
#include <QApplication>
#include <QLabel>
#include <QTreeWidget>
#include <QDebug>
static void on_destroyed(){
qDebug()<<"destroyed";
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QTreeWidget w;
w.setSelectionMode(QAbstractItemView::SingleSelection);
w.setDragEnabled(true);
w.viewport()->setAcceptDrops(true);
w.setDropIndicatorShown(true);
w.setDragDropMode(QAbstractItemView::InternalMove);
for(int i=0; i< 5; i++){
QTreeWidgetItem *it = new QTreeWidgetItem(&w);
QLabel *lbl = new QLabel(QString::number(i));
QObject::connect(lbl, &QObject::destroyed, on_destroyed);
w.setItemWidget(it, 0, lbl);
}
w.show();
return a.exec();
}
It shows that the widgets will emit the signal they destroy when you drag and drop the items.
Possible workaround:
One possible solution is to remove the widgets before accepting the drop and set them in the new items which I have not implemented.
I have explored another solution, it is to change the QTreeWidget for a QTreeView + QStandardItemModel. In the case of the QCheckBox, the checkboxes with the Qt::ItemIsUserCheckable flag are enabled, in the case of the QLineEdit a delegate will be used and to always be shown, the openPersistentEditor() method must be used.
#include <QApplication>
#include <QStandardItemModel>
#include <QTreeView>
#include <QHeaderView>
#include <QDropEvent>
#include <QStyledItemDelegate>
#include <QLineEdit>
class ToolsSelectorDelegate: public QStyledItemDelegate{
public:
using QStyledItemDelegate::QStyledItemDelegate;
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &) const{
QLineEdit *le = new QLineEdit(parent);
return le;
}
void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &) const{
QRect r(option.rect);
r.adjust(2, 2, -2, -2);
editor->setGeometry(r);
}
};
class ToolsSelectorWidget: public QTreeView{
QStandardItemModel model;
public:
ToolsSelectorWidget(QWidget *parent=nullptr): QTreeView(parent){
setItemDelegate(new ToolsSelectorDelegate(this));
setModel(&model);
header()->hide();
setSelectionMode(QAbstractItemView::SingleSelection);
setDragEnabled(true);
viewport()->setAcceptDrops(true);
setDropIndicatorShown(true);
setDragDropMode(QAbstractItemView::InternalMove);
for(int i=0; i<4; ++i) {
QList<QStandardItem *> items;
for(const QString & text: {"Preview", "Name", "Visible", "Locked"}){
QStandardItem *it = new QStandardItem(QString("Part%1 %2").arg(i).arg(text));
it->setFlags(it->flags() & ~Qt::ItemIsDropEnabled & ~Qt::ItemIsEditable);
items.append(it);
if(text == "Visible" || text == "Locked"){
it->setFlags(it->flags() | Qt::ItemIsUserCheckable);
it->setCheckState(Qt::Unchecked);
}
else if (text == "Name") {
it->setFlags(it->flags() | Qt::ItemIsEditable);
}
}
for(const QString & children: {"The", "quick", "Brown", "fox", "jump...", "over", "the", "lazy", "dog"})
items.first()->appendRow(new QStandardItem(children));
model.invisibleRootItem()->appendRow(items);
for( int i = 0; i < model.rowCount(); ++i )
openPersistentEditor(model.index(i, 1));
}
}
protected:
void dropEvent(QDropEvent *event) {
QModelIndex droppedIndex = indexAt(event->pos());
if( !droppedIndex.isValid() || droppedIndex.parent().isValid())
return;
QTreeView::dropEvent(event);
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
ToolsSelectorWidget w;
w.show();
return a.exec();
}
Is there any function which I can use in order to pad my QImage object?
I have tried to search over the net unsuccefuly.
Thx in advance.
Here is my code:
#include "mainwindow.h"
#include <QApplication>
#include "qimage.h"
#include <QImage>
#include <QLabel>
#include <QColor>
#include "qcolor.h"
#include <Qdebug>
#include <QGraphicsPixmapItem>
#include <QGraphicsScene>
#include <QGraphicsView>
int main(int argc, char *argv[])
{
printf("Init!");
qDebug() << "C++ Style Debug Message";
QApplication a(argc, argv);
QGraphicsScene scene;
QGraphicsView view(&scene);
int height;
int width;
unsigned char *p, *p_begin;
QImage img("C:\\Users\\Owner\\Pictures\\2013-09-26\\IMG_0836.JPG");
height = img.height();
width = img.width();
p = (unsigned char *)malloc(height * width * sizeof(unsigned char));
p_begin = p;
qDebug() << "Begin For Loop";
for (int row = 0; row < height; ++row)
{
for (int col = 0; col < width; ++col)
{
QColor clrCurrent( img.pixel( col, row ));
*p = (unsigned char)((clrCurrent.green() * 0.587) + (clrCurrent.blue() * 0.114) + (clrCurrent.red() * 0.299));
p++;
}
}
qDebug() << "Finished First Loop!";
p = p_begin;
for (int row = 0; row < height; ++row)
{
for (int col = 0; col < width; ++col)
{
QColor clrCurrent(img.pixel(col, row));
clrCurrent.setBlue((int)(*p));
clrCurrent.setGreen((int)(*p));
clrCurrent.setRed((int)(*p));
img.setPixel(col, row, clrCurrent.rgba());
p++;
}
}
QPixmap pixmap = QPixmap::fromImage(img);
QGraphicsPixmapItem *item = new QGraphicsPixmapItem(pixmap);
scene.addItem(item);
view.show();
return a.exec();
}
Hi I edited my question, i added my code in order to give you more feeling of what is going on. any help would be appreciated.
The QImage offers no way to change its size. You need to create a new, larger image, erase its contents, start a QPainter on it, then draw the source image in the center of the new image. That way you'll have padding.
Below is a function that returns a padded version of the image, with a given color used for padding, and a test harness for it.
// https://github.com/KubaO/stackoverflown/tree/master/questions/image-pad-35968431
#include <QtGui>
template <typename T>
QImage paddedImage(const QImage & source, int padWidth, T padValue) {
QImage padded{source.width() + 2*padWidth, source.height() + 2*padWidth, source.format()};
padded.fill(padValue);
QPainter p{&padded};
p.drawImage(QPoint(padWidth, padWidth), source);
return padded;
}
int main() {
QImage source{64, 64, QImage::Format_ARGB32_Premultiplied};
source.fill(Qt::red);
auto padded = paddedImage(source, 16, Qt::blue);
padded.save("test.png");
}
Output:
Hi I've got a GridLayout which has 64 GraphicsViews on it (I know it's alot but it's the only way i could think of doing this at this point in time).
Now i'm currently just drawing a random line on each of these graphics views on a timer tick. This works but only for the 8 of the Graphics,
Create Graphics Views
void Simulation::createGraphicsViews(){
for(int i = 0; i < 64; i++){
for(int j = 0; j < 8; j++){
graphicsScene[i] = new QGraphicsScene();
graphicsView[i] = new QGraphicsView(graphicsScene[i]);
simui->gridLayout->addWidget(graphicsView[i], i/8, j);
}
}
}
Random Line in each graphics view
for(int x = 0; x < 64; x++){
x1 = qrand()%(50+1) - 1;
y1 = qrand()%(50+1)-1;
x2 = qrand()%(50+1)-1;
y2 = qrand()%(50+1)-1;
graphicsScene[x]->addLine(x1,y1,x2,y2);
qDebug() << "adding line to" << x << "at" << x1 <<","<<y1<<","<<x2<<","<<y2;
}
show updated graphics view
for(int x = 0; x < 64; x++){
graphicsView[x]->show();
qDebug()<<"showing" << x;
}
I've looked through it for the last 2 hours trying multiple approaches none of which have fixed this problem, I'm assuming it's probably something stupid but I just can't figure it out
Any help is greatly appreciated
Thank you
Also if i try to update any of the Graphics Views other than the ones which work they still don't update.
https://gist.github.com/gazza126/f43d5b0377649782a35d -- Full Code (that does anything)
The below works. Make sure that you enable C++11 in your .pro file: add CONFIG += c++11 to the project file.
#include <QApplication>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QGraphicsLineItem>
#include <QGridLayout>
#include <QTime>
#include <QTimer>
#include <array>
class View : public QGraphicsView
{
public:
View(QWidget *parent = 0) : QGraphicsView(parent) {
setRenderHint(QPainter::Antialiasing);
}
void resizeEvent(QResizeEvent *) {
fitInView(-1, -1, 2, 2, Qt::KeepAspectRatio);
}
};
template <typename Container>
void updateScenes(Container & views)
{
auto angle = 360.0/1000.0 * (QTime::currentTime().msecsSinceStartOfDay() % 1000);
for (auto & view : views) {
auto scene = view.scene();
scene->clear();
auto * line = scene->addLine(-1, 0, 1, 0, QPen(Qt::darkBlue, 0.1));
line->setRotation(angle);
}
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QGraphicsScene s;
QTimer timer;
QWidget window;
QGridLayout layout(&window);
std::array<View, 64> views;
int i = 0;
for (auto & view : views) {
view.setScene(new QGraphicsScene(&view));
layout.addWidget(&view, i/8, i%8);
++ i;
}
QObject::connect(&timer, &QTimer::timeout, [&views]{ updateScenes(views); });
timer.start(50);
window.show();
return a.exec();
}
I'm pretty new to QT, and I'm trying to take a list from a text file and output it into QT with nice formatting.
I managed to get the list printed on the window, but it has to be able to be sorted.
I have the radio buttons set up right now so that one of them displays the list and the other clears the list.
The problem is that when I switch from the list to the cleared list back to the list the program segfaults and I don't understand why.
The files are here.
winelist.cpp
#include "winelist.h"
#include "ui_winelist.h"
#include <QFile>
#include <QString>
#include <QStandardItemModel>
wineList::wineList(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::wineList)
{
ui->setupUi(this);
ui->ratingButton->setChecked(true);
fillList();
model->setHorizontalHeaderItem(0, new QStandardItem(QString("Wine Name")));
model->setHorizontalHeaderItem(1, new QStandardItem(QString("Vintage")));
model->setHorizontalHeaderItem(2, new QStandardItem(QString("Rating")));
model->setHorizontalHeaderItem(3, new QStandardItem(QString("Price")));
ui->listOutput->setModel(model);
}
wineList::~wineList()
{
delete ui;
}
void wineList::on_sortButton_clicked()
{
if( ui->ratingButton->isChecked())
{
for (int i = 0; i < 100; i++) {
model->setItem(i,0,wList[i].wineName);
model->setItem(i,1,wList[i].vintage);
model->setItem(i,2,wList[i].rating);
model->setItem(i,3,wList[i].price);
}
}
else
{
for(int i = 0; i < 100; i++) {
for(int j = 0; j < 4; j++) {
model->setItem(i, j, new QStandardItem(QString("")));
}
}
}
ui->listOutput->resizeColumnsToContents();
ui->listOutput->resizeRowsToContents();
}
void wineList::fillList()
{
Wine wine;
QString line;
QStringList lineElements;
QFile wineText(":/winelist.txt");
if (wineText.open(QIODevice::ReadOnly))
{
while ((line = line.fromUtf8(wineText.readLine())) != "")
{
lineElements = line.split(";");
lineElements[0].replace("\t", "");
lineElements[1].replace("\t", "");
wine.wineName = new QStandardItem(QString(lineElements.at(0)));
wine.vintage = new QStandardItem(QString(lineElements.at(1)));
wine.rating = new QStandardItem(QString::number(lineElements.at(2).toInt()));
wine.price = new QStandardItem(QString::number(lineElements.at(3).toInt()));
wList.append(wine);
}
}
wineText.close();
}
winelist.h
#ifndef WINELIST_H
#define WINELIST_H
#include <QMainWindow>
#include <QStandardItem>
#include <QStandardItemModel>
namespace Ui {
class wineList;
}
struct Wine {
QStandardItem* wineName;
QStandardItem* vintage;
QStandardItem* rating;
QStandardItem* price;
};
class wineList : public QMainWindow
{
Q_OBJECT
public:
explicit wineList(QWidget *parent = 0);
~wineList();
private slots:
void on_sortButton_clicked();
private:
Ui::wineList *ui;
QVarLengthArray<Wine> wList;
QStandardItemModel *model = new QStandardItemModel(100, 4, this);
void fillList();
void printList(QStandardItemModel *model);
};
#endif // WINELIST_H
main.cpp
#include "winelist.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
wineList w;
w.show();
return a.exec();
}
Clicking sort the first time
Switching Radio Button and clicking sort again
Switching Radio Button back and clicking sort again
Any Help is appreciated, I am completely lost here.
In the on_sortButton_clicked you're trying to read data from a list, but not doing any range checks. Instead, you've hardcoded 100 there.
You should rewrite this:
for (int i = 0; i < 100; i++) {
model->setItem(i,0,wList[i].wineName);
to this:
for (int i = 0; i < wList.size(); i++) {
model->setItem(i,0,wList[i].wineName);
--upd---
When you initially populate your model, it takes ownership over items from wList. When you replace model items with empty ones, it deletes initial items from wList. After this your wList is no move valid, because it contains Wine structs with dangling pointers. That's why when you try to populate your model second time, it crashes.