I currently have a QtableView that is attached to a QstandardItemModel. I want to insert a clickable Qlabel in the last column of each row. Initially I wanted to go with a QPushButton but it turns out that it requires extra overhead so now I am planning to go with a clickable Qlabel. Any suggetsions on how I could do that ? Also I would appreciate it if someone could sugget options that are available for simulating click events in a TableView using QStandardItemModel
connect(ui.tableView,SIGNAL(clicked(const QModelIndex& ) ),
this,SLOT( itemClicked(const QModelIndex& ) ) );
slot:
void itemClicked( const QModelIndex& idx) {
int row = idx.row();
int column = idx.column();
}
if you really need just a clickable label you can create class derived from QLabel and add custom signals to handle click event:
class CustomWidget : public QLabel {
Q_OBJECT
public:
explicit CustomWidget(const QString& text, QWidget *parent = 0);
signals:
void released(void);
void clicked(void);
protected:
void mousePressEvent(QMouseEvent* e);
void mouseReleaseEvent(QMouseEvent* e);
private:
bool mousePressed;
};
CustomWidget::CustomWidget(const QString& text, QWidget* parent)
: QLabel(text, parent), mousePressed(false) {
}
void CustomWidget::mousePressEvent(QMouseEvent* e) {
mousePressed = true;
}
void CustomWidget::mouseReleaseEvent(QMouseEvent* e) {
emit released();
if(mousePressed) {
emit clicked();
mousePressed = false;
}
}
full code snippet:
http://www.qtcentre.org/archive/index.php/t-42296.html?s=e9f0fd408147a1cd1048f252967895a0
Related
I am trying to run some code when I select a new index in a QTreeView
In RoverPlanner.h
namespace Ui {
class RoverPlanner;
}
class RoverPlanner : public QWidget
{
Q_OBJECT
public:
explicit RoverPlanner(QWidget *parent = nullptr);
void save_paths_from_tree(QTreeView* treeView);
void load_paths_into_tree(QTreeView* treeView);
std::vector<cuarl_path::Path> get_paths(const char* filename) const;
void update_segment_editor();
cuarl_path::Segment* addSegment();
~RoverPlanner();
private Q_SLOTS:
void treeSelectionChanged(const QModelIndex& prevIndex, const QModelIndex& nextIndex);
private:
Ui::RoverPlanner *ui;
};
In RoverPlanner.cpp
RoverPlanner::RoverPlanner(QWidget *parent) :
QWidget(parent),
ui(new Ui::RoverPlanner)
{
ui->setupUi(this);
QPushButton* btnLoadPaths = this->findChild<QPushButton*>("btn_load_paths");
QPushButton* btnSavePaths = this->findChild<QPushButton*>("btn_save_paths");
QPushButton* btnExecutePath = this->findChild<QPushButton*>("btn_execute_path" );
QPushButton* btnAddSegment = this->findChild<QPushButton*>("btn_add_to_path");
QTreeView* treeView = this->findChild<QTreeView*>("tree_paths");
connect(btnLoadPaths, &QPushButton::clicked, this, [=]() { load_paths_into_tree(treeView); });
connect(treeView->selectionModel(), &QItemSelectionModel::currentChanged, this, &RoverPlanner::treeSelectionChanged); // This does not seem to properly bind the index chan
}
void RoverPlanner::treeSelectionChanged(const QModelIndex &prevIndex, const QModelIndex &nextIndex) {
std::cout << "Test" << std::endl;
}
//other functions
When I click on the items, it does not output anything in the console
I'm confused because this seems to be the way to correctly connect the treeview selected index changed. What did I do wrong?
selectionModel gets replaced each time a new model is set for QTreeView.
void QAbstractItemView::setModel(QAbstractItemModel *model):
This function will create and set a new selection model, replacing any model that was previously set with setSelectionModel().
That means you need to reconnect the &QItemSelectionModel::currentChanged signal each time you set a new model.
I have been researching for a while how to store images loaded on a QGraphicsView into rows of a QTableView using a QPushButton in a programmatic way but the information I found so far are not that many.
I have 1 QGraphicsView, a QPushButton (Send button) and a QTableView and a QLineEdit. When I upload images using the load button I show them both on the QGraphicsView and on the QLineEidt (I show the path of the image), if I click the Send button, the text of the QLineEdit should be added in the first row of the QTableView (which is happening) and the image should be stored inside the QTableView.
However, the image on the QGraphicsView is not being stored to the QTableView and nothing is being passed.
Currently this is what happens:
The expected behavior would be:
I created an ItemDelegate class that takes care of the resizing of the image on the QGraphicsView to be stored inside the QTableView
That part is shown below:
This is the mainwindow.h
#include <QGraphicsView>
#include <QGraphicsScene>
#include "imagedelegate.h"
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
void addData();
void paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const;
private slots:
void on_sendBtn_clicked();
void on_loadBtn_clicked();
private:
Ui::MainWindow *ui;
QStandardItemModel *model;
QGraphicsScene *leftScene;
};
#endif // MAINWINDOW_H
and here is the mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "imagedelegate.h"
#include <QGraphicsPixmapItem>
#include <QBuffer>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
leftScene = new QGraphicsScene(this);
ui->graphicsView->setScene(leftScene);
ui->graphicsView->show();
model = new QStandardItemModel();
ui->tableView->setModel(model);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::addData()
{
// Path on the first column
QStandardItem *pathAItem = new QStandardItem(ui->lineEdit->text());
// Image on the second column - not working yet
//QStandardItem *image1 = new QStandardItem(/*ui->graphicsView->*/);
QPixmap image1;
QByteArray img1Array;
QBuffer buffer1(&img1Array);
buffer1.open(QIODevice::WriteOnly);
image1.save(&buffer1, "PNG");
QList<QStandardItem*> row;
row << pathAItem;
model->setColumnCount(1);
model->appendRow(row);
}
void MainWindow::on_sendBtn_clicked()
{
addData();
}
void MainWindow::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const
{
QString colName = index.model()->headerData(index.column(), Qt::Horizontal).toString();
if(colName == "image1")
{
QPixmap iconPix;
if(!iconPix.loadFromData(index.model()->data(index).toByteArray())) {
}
iconPix = iconPix.scaledToHeight(32);
painter->drawPixmap(option.rect.x(),option.rect.y(),iconPix);
} else {
// QStyledItemDelegate::paint(painter, option, index);
}
}
The entire code will compile if you copy and paste so that you can see the issue I have.
Please shed light on this matter.
I wanted to answer to this question hoping that could also be useful to others. As suggested by Jeremy Friesner, the best (and fast in comparison to a QItemDelegate) way to send images into a QTableView using a QPushButton is to modify the void MainWindow::addData() function by using a QImage and pass it to a setData(QVariant(QPixmap::fromImage), Qt::DecorationRole) so that the entire function can be written as follows:
FIRST OPTION:
void MainWindow::on_sendBtn_clicked()
{
addData();
}
void MainWindow::addData()
{
QStandardItem *pathAItem = new QStandardItem(ui->pathLineEdit_A->text());
QStandardItem *pathBItem = new QStandardItem(ui->pathLineEdit_B->text());
QImage image1(ui->graphicsViewLeft->grab().toImage());
QStandardItem *item1 = new QStandardItem();
item1->setData(QVariant(QPixmap::fromImage(image1.scaled(42,42, Qt::KeepAspectRatio,Qt::SmoothTransformation))), Qt::DecorationRole);
ui->bookMarkTableView->setModel(model);
QImage image2(ui->graphicsViewRight->grab().toImage());
QStandardItem *item2 = new QStandardItem();
item2->setData(QVariant(QPixmap::fromImage(image2.scaled(42,42, Qt::KeepAspectRatio,Qt::SmoothTransformation))), Qt::DecorationRole);
ui->bookMarkTableView->setModel(model);
QList<QStandardItem*> row;
row << pathAItem << pathBItem << item1 << item2;
model->appendRow(row);
}
SECOND OPTION
If it is necessary to use a QItemDelgate I am posting that part of the code too (it is working as I already tried it):
In the imagedelegate.h is necessary to provide a QSize as follows:
class ImageDelegate : public QStyledItemDelegate
{
public:
ImageDelegate(QObject * parent = nullptr);
void paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const;
QSize sizeHint(const QStyleOptionViewItem & option, const QModelIndex & index) const;
After that on your imagedelegate.cpp the implementation is:
#include "imagedelegate.h"
ImageDelegate::ImageDelegate(QObject * parent) : QStyledItemDelegate(parent)
{}
QSize ImageDelegate::sizeHint(const QStyleOptionViewItem & option, const QModelIndex & index) const
{
return QSize(32,32);
Q_UNUSED(option);
Q_UNUSED(index);
}
void ImageDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const
{
qDebug() << (index.model()->headerData(index.column(), Qt::Horizontal).toString());
QString colName = index.model()->headerData(index.column(), Qt::Horizontal).toString();
if(colName == "image1" || colName == "image2")
{
QPixmap iconPix;
if(!iconPix.loadFromData(index.model()->data(index).toByteArray())) {
}
iconPix = iconPix.scaledToHeight(32);
painter->drawPixmap(option.rect.x(),option.rect.y(),iconPix);
} else {
QStyledItemDelegate::paint(painter, option, index);
}
}
In my case I had two columns in which I needed to save the images, so you can expand it for how many columns as you like and I also set a QSize of (32,32) but this is up to the developer.
I hope this will save your programming time and this is the final result! :)
I am having a delete button in every row. I am trying to use clicked() SIGNAL of QTableview to get the current index and then do something accordingly, but this slot is not called in this case. For some reason it doesn't work, am I making some mistake in connecting clicked() SIGNAL?
void MyClass::myFunction()
{
ComboBoxItemDelegate* myDelegate;
myDelegate = new ComboBoxItemDelegate();
model = new STableModel(1, 8, this);
filterSelector->tableView->setModel(model);
filterSelector->tableView->setItemDelegate(myDelegate);
connect(filterSelector->tableView, SIGNAL(clicked(const QModelIndex&)), this, SLOT(slotHandleDeleteButton(const QModelIndex&)));
exec();
}
void MyClass::slotHandleDeleteButton(const QModelIndex& index)
{
if(index.column() == 8)
model->removeRow(index.row());
}
One of the solutions can look like this:
class Button : public QPushButton
{
Q_OBJECT
public:
Button(int row, QWidget *parent = 0) : QPushButton(parent), m_row(row)
{
connect(this, SIGNAL(clicked()), this, SLOT(onClicked()));
}
signals:
void clicked(int row);
private slots:
void onClicked()
{
emit clicked(m_row);
}
private:
int m_row;
};
Button class contains a custom signal clicked(int) with the row number as an argument. To use it in your table view you need to do:
Button *btn = new Button(rowNumber, this);
connect(btn, SIGNAL(clicked(int)), this, SLOT(onButtonClicked(int)));
I want to show a text like "Please select one option" on the combobox, and don't show the text in the list, so I set setEditable to true, then set the text to the lineEdit, but after this, only the dropdown button (arrow) is clickable, how can we make the entire combobox clickable? I'm using the QComboBox as below:
QComboBox* combbox= new QComboBox;
combbox->setEditable(true);
combbox->lineEdit()->setReadOnly(true);
combbox->addItem("Option1");
combbox->addItem("Option2");
combbox->lineEdit()->setText("Please select one option");
I resolved this problem as below:
class QTComboBoxButton : public QLineEdit
{
Q_OBJECT
public:
QTComboBoxButton(QWidget *parent = 0);
~QTComboBoxButton();
protected:
void mousePressEvent(QMouseEvent *);
};
QTComboBoxButton::QTComboBoxButton(QWidget *parent /* = 0 */) :
QLineEdit(parent)
{
}
QTComboBoxButton::~QTComboBoxButton()
{
}
void QTComboBoxButton::mousePressEvent(QMouseEvent * e)
{
QComboBox* combo = dynamic_cast<QComboBox*>(parent());
if(combo)
combo->showPopup();
}
QComboBox* combbox= new QComboBox;
combbox->setEditable(true);
combbox->setLineEdit(new QTComboBoxButton(combbox));
combbox->lineEdit()->setReadOnly(true);
combbox->addItem("Option1");
combbox->addItem("Option2");
combbox->lineEdit()->setText("Please select one option");
Making a QComboBox editable is problematic in terms of UI.
I propose a different approach, reimplement a QComboBox and make a default item and remove it if the user clicks the combobox:
#include "mycombo.h"
MyCombo::MyCombo(QWidget *parent) :
QComboBox(parent),
defaultText_("Please select one option")
{
addItem(defaultText_);
}
void MyCombo::mousePressEvent(QMouseEvent* event)
{
if(this->currentText() == defaultText_)
{
this->removeItem(0);
}
QComboBox::mousePressEvent(event);
}
And then just create this combobox and insert the items where you want
MyCombo *combbox = new MyCombo(this);
combbox->addItem("Option1");
combbox->addItem("Option2");
You can use this lib : libqxt
You can find it here : https://bitbucket.org/libqxt/libqxt/wiki/Home
Use the object QxtCheckComboBox, with it you can check multiple items in your ComboBox.
The answer from IdlChina has one drawback: when you click the combobox when it's already shown, it hides and immediately shows again. I have a slightly different approach that doesn't have that problem.
class ButtonComboBox : public QComboBox
{
public:
ButtonComboBox(QWidget *parent = nullptr)
: QComboBox(parent),
_isPopupShown(false),
_timer(new QTimer(this))
{
auto lineEdit = new QLineEdit;
lineEdit->installEventFilter(this);
setLineEdit(lineEdit);
_timer->setSingleShot(true);
_timer->setInterval(100);
}
protected:
bool eventFilter(QObject *object, QEvent *event) override
{
if (object == lineEdit() && event->type() == QEvent::MouseButtonPress) {
if (!_timer->isActive()) {
if (!_isPopupShown)
showPopup();
else if (_isPopupShown)
hidePopup();
return true;
}
}
return false;
}
void showPopup() override
{
QComboBox::showPopup();
_isPopupShown = true;
_timer->start();
}
void hidePopup() override
{
QComboBox::hidePopup();
_isPopupShown = false;
_timer->start();
}
private:
bool _isPopupShown;
QTimer *_timer;
};
I have a reimplemented QDoubleSpinBox. I would like to catch mouseDoubleClickEvent to enable the user to change singleStep by way of QInputDialog::getDouble().
My problem is that when I reimplement the mouseDoubleClickEvent I only catch double clicks that occur over the arrow buttons. I actually want to ignore double clicks that occur in the arrows and only catch double clicks that occur in the text field. I have a feeling that I need to reimplement the mouseDoubleClickEvent of a child of the QDoubleSpinBox, but I'm not sure how to reimplement a child event nor how to select the correct child See my attempt at limiting to a child QRect in code: I think I need to specify which child...?
Thanks.
Edit: corrected class declaration/definition name mismatch.
MyQDoubleSpinBox.h
class MyQDoubleSpinBox : public QDoubleSpinBox
{
Q_OBJECT
public:
MyQDoubleSpinBox(QString str, QWidget *parent = 0);
~MyQDoubleSpinBox();
public slots:
void setStepSize(double step);
private:
double stepSize;
QString name;
protected:
void mouseDoubleClickEvent(QMouseEvent *e);
};
MyQDoubleSpinBox.cpp
#include "MyQDoubleSpinBox.h"
MyQDoubleSpinBox::MyQDoubleSpinBox(QString str, QWidget *parent)
: QDoubleSpinBox(parent), stepSize(1.00), name(str)
{
this->setMinimumWidth(150);
this->setSingleStep(stepSize);
this->setMinimum(0.0);
this->setMaximum(100.0);
}
MyQDoubleSpinBox::~MyQDoubleSpinBox()
{
}
void MyQDoubleSpinBox::setStepSize(double step)
{
this->setSingleStep(step);
}
void MyQDoubleSpinBox::mouseDoubleClickEvent(QMouseEvent *e)
{
if( this->childrenRect().contains(e->pos()) )
{
bool ok;
double d = QInputDialog::getDouble(this,
name,
tr("Step Size:"),
this->singleStep(),
0.0,
1000.0,
2,
&ok);
if(ok)
this->setSingleStep(d);
}
}
A bit of the hack getting ref to child, but it works =)
MyQDoubleSpinBox.h:
class MyQDoubleSpinBox : public QDoubleSpinBox
{
Q_OBJECT
public:
MyQDoubleSpinBox(QString str, QWidget *parent = 0);
~MyQDoubleSpinBox();
public slots:
void setStepSize(double step);
private:
double stepSize;
QString name;
protected:
bool eventFilter(QObject *, QEvent *e);
};
MyQDoubleSpinBox.cpp
MyQDoubleSpinBox::MyQDoubleSpinBox(QString str, QWidget *parent)
: QDoubleSpinBox(parent), stepSize(1.00), name(str)
{
this->setMinimumWidth(150);
this->setSingleStep(stepSize);
this->setMinimum(0.0);
this->setMaximum(100.0);
QLineEdit *editor = this->findChild<QLineEdit *>("qt_spinbox_lineedit");
editor->installEventFilter(this);
}
MyQDoubleSpinBox::~MyQDoubleSpinBox()
{
}
void MyQDoubleSpinBox::setStepSize(double step)
{
this->setSingleStep(step);
}
bool MyQDoubleSpinBox::eventFilter(QObject *, QEvent *e)
{
if (e->type() == QMouseEvent::MouseButtonDblClick)
{ bool ok;
double d = QInputDialog::getDouble(this,
name,
tr("Step Size:"),
this->singleStep(),
0.0,
1000.0,
2,
&ok);
if(ok)
this->setSingleStep(d);
}
return false;
}
Instead of overwriting events, i got ref to underlying QLineEdit and assigned event filter to it. In event filter catch only mouse double click.
You have direct access to QLineEdit using this->lineEdit()
MyQDoubleSpinBox::MyQDoubleSpinBox(QString str, QWidget *parent)
: QDoubleSpinBox(parent), stepSize(1.00), name(str)
{
this->setMinimumWidth(150);
this->setSingleStep(stepSize);
this->setMinimum(0.0);
this->setMaximum(100.0);
QLineEdit *editor = this->lineEdit(); // change here
editor->installEventFilter(this);
}