I'm using QTableView to visualize some particular images. The user may click on the images of the TableView and that image will be magnified in another bigger window of QLabel type. I'm able to make this possible using the mouse clicks on the TableView, but I'd like to enable it for keyboard buttons "up" and "down". What I mean is that, once the user click on one of the images listed on the TableView, if the user changes to other images using keyboard buttons "up" and "down", I want to detect key press and connect it to the QLabel which magnifies that particular selected image.
So, what I mean is I actually want to detect keypress on the QTableView. Until now I haven't managed to do it. I'm installing an eventfilteron the viewPort of the QTableView, and in the eventfilter function I can detect the mousebuttonpress, but I cannot detect the keypress.
To show you how I'm approaching the implementation I have made simple program for testing with QTableView andKeypress. Below, I have given the code of the mainWindow implementation of that simple program.
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QStandardItemModel>
#include <QDebug>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->numberTable->viewport()->installEventFilter(this);
connect(ui->FillUp, SIGNAL(clicked()), this, SLOT(fillUp()));
}
void MainWindow::fillUp()
{
model = new QStandardItemModel(3, 3, this);
int counter = 0;
for(int i = 0; i < 3; i++)
{
for(int j = 0; j < 3; j++)
{
QStandardItem * itm = new QStandardItem;
counter++;
itm->setText(QString::number(counter));
model->setItem(i, j, itm);
}
}
ui->numberTable->setModel(model);
ui->numberTable->show();
}
bool MainWindow::eventFilter(QObject * obj, QEvent * ev)
{
if(obj == ui->numberTable->viewport())
{
if(ev->type() == QEvent::MouseButtonPress)
{
qDebug()<<"Mouse button pressed!\n";
}
else if(ev->type() == QEvent::KeyPress)
{
qDebug()<<"Key button pressed\n";
}
}
return QObject::eventFilter(obj, ev);
}
MainWindow::~MainWindow()
{
delete ui;
}
The programs does output "Mouse button pressed", but no output for keyboard pressing. Could you please let me know where I am doing the error?
Thanks
I have the same trouble. Using your idea, if print the type of event I get: "Paint widget" (12) instead "KeyPress" (6).
bool AR_Principal::eventFilter(QObject * obj, QEvent * ev){
qDebug() << ev->type();
if(obj == ui->tableView->viewport())
{
if(ev->type() == QEvent::MouseButtonPress)
{
qDebug()<<"Mouse button pressed";
}
else if(ev->type() == QEvent::KeyPress)
{
qDebug()<<"Key button pressed";
}
else if(ev->type() == QEvent::Paint)
{
qDebug()<<"Paint widget" ;
}
}
return QObject::eventFilter(obj, ev);
}
If use QEvent::Paint event this works. Or like the other answer says add:
ui->tableView->installEventFilter(this);
And dont use the condition:
if(obj == ui->tableView->viewport())
But a more efficient solution its:
connect(ui->tableView->selectionModel(), SIGNAL(currentChanged (const QModelIndex & , const QModelIndex & )), SLOT(selectedItem(const QModelIndex &)));
Where selectedItem(const QModelIndex &) its a private slot function where you can do anything with selected data (using their index)
Related
I'm developing a program that contains a MainWindow and a Widget called Diagrama from QWidget, which is the central widget of my mainwindow.
In this diagrama widget I have the ability to create a label in a the position that I clicked on the screen and the ability to drag an drop those same labels.
But now, I want to add an ability to get a clicked signal of the label every time that I click it.
I know that to enable the clicked signal function of a label, I have to create a class of a custom label, but when I do this and I replace the class QLabel to the customLabel class in the code, the drag and drop function stop working.
void Diagrama::dragEnterEvent(QDragEnterEvent *event)
{....}
void Diagrama::dragMoveEvent(QDragMoveEvent *event)
{....}
void Diagrama::dropEvent(QDropEvent *event)
{....}
void Diagrama::mousePressEvent(QMouseEvent *event)
{....}
I put this for just you guys know that I have the function to the whole process
And now I don't know what to do.
I though that there is a conflict of the function mousePressEvent of my customLabel class and the same function in my Diagrama class.
How can I solve it?
void Diagrama::dragMoveEvent(QDragMoveEvent *event)
{
if (event->mimeData()->hasFormat("application/x-dnditemdata")) {
if (event->source() == this) {
event->setDropAction(Qt::MoveAction);
event->accept();
} else {
event->acceptProposedAction();
}
} else {
event->ignore();
}
}
void Diagrama::dropEvent(QDropEvent *event)
{
if (event->mimeData()->hasFormat("application/x-dnditemdata")) {
QByteArray itemData = event->mimeData()->data("application/x-dnditemdata");
QDataStream dataStream(&itemData, QIODevice::ReadOnly);
QPixmap pixmap;
QPoint offset;
dataStream >> pixmap >> offset;
QLabel *newIcon = new QLabel(this);
newIcon->setPixmap(pixmap);
newIcon->move(event->pos() - offset);
newIcon->show();
newIcon->setAttribute(Qt::WA_DeleteOnClose);
if (event->source() == this) {
event->setDropAction(Qt::MoveAction);
event->accept();
} else {
event->acceptProposedAction();
}
} else {
event->ignore();
}
}
void Diagrama::paintEvent(QPaintEvent *e)
{
QPainter painter(this);
painter.drawImage(0,0,*mImage);
e->accept();
}
void Diagrama::mousePressEvent(QMouseEvent *event)
{
if(modo=="trafo")
{
if(event->button()==Qt::LeftButton){
QLabel *child = static_cast<QLabel*>(childAt(event->pos()));
if (!child)
return;
QPixmap pixmap = *child->pixmap();
QByteArray itemData;
QDataStream dataStream(&itemData, QIODevice::WriteOnly);
dataStream << pixmap << QPoint(event->pos() - child->pos());
QMimeData *mimeData = new QMimeData;
mimeData->setData("application/x-dnditemdata", itemData);
QDrag *drag = new QDrag(this);
drag->setMimeData(mimeData);
drag->setPixmap(pixmap);
drag->setHotSpot(event->pos() - child->pos());
QPixmap tempPixmap = pixmap;
QPainter painter;
painter.begin(&tempPixmap);
painter.fillRect(pixmap.rect(), QColor(127, 127, 127, 127));
painter.end();
child->setPixmap(tempPixmap);
if (drag->exec(Qt::CopyAction | Qt::MoveAction, Qt::CopyAction) == Qt::MoveAction) {
child->close();
} else {
child->show();
child->setPixmap(pixmap);
}
}
else if(event->button()==Qt::RightButton)
{
QLabel *child = new QLabel(this);
child->setPixmap(QPixmap(url_trafo));
child->move(event->x(),event->y());
child->show();
}
}
else if(modo=="linha")
{
if(event->button()==Qt::RightButton){
p_ini=event->pos();
drawing=true;
event->accept();}
else {
event->ignore();
drawing=false;
}
}
}
That is the responsible for the events of drag and drop and the event of appearing a label every time I click on the screen
I tried to create a customLabel class to emit a clicked signal every time I click in the label, but disable the drag and drop event
A click has to be registered on mouse release, not mouse press. A mouse press can evolve into different things, depending on what the user does next. Mouse press plus move or mouse press plus a long delay evolves into a drag operation.
So you need to override both mousePressEvent() as well as mouseReleaseEvent().
In your mousePressEvent() you need to save the time the press happened as well as the position. You then call QLabel::mousePressEvent() and pass it the event, so that QLabel can still detect drag operations.
In your mouseReleaseEvent() you need to compare the current time to the time of the press. If the difference is larger than QApplication::startDragTime, or the position of the mouse release compared to the mouse press position is further away than QApplication::startDragDistance, or the position is outside the label, then you don't treat the mouse release as a click. Finally, forward the event to the overriden QLabel::mouseReleaseEvent() so that the base class knows the mouse press event ended.
Here's an example ClickableQLabel implementation:
#include <QApplication>
#include <QElapsedTimer>
#include <QLabel>
#include <QPoint>
class ClickableQLabel: public QLabel
{
Q_OBJECT
public:
explicit ClickableQLabel(QWidget* parent = nullptr)
: QLabel(parent)
{}
signals:
void clicked();
protected:
void mousePressEvent(QMouseEvent* e) override
{
QLabel::mousePressEvent(e);
if (e->button() != Qt::LeftButton) {
rerurn;
}
mouse_press_time_.start();
mouse_press_pos_ = e->pos();
e->accept();
}
void mouseReleaseEvent(QMouseEvent* e) override
{
QLabel::mouseReleaseEvent(e);
if (!rect().contains(e->pos(), true)
|| e->button() != Qt::LeftButton
|| !mouse_press_time_.isValid()
|| mouse_press_pos_.isNull()
|| mouse_press_time_.hasExpired(QApplication::startDragTime())
|| (e->pos() - mouse_press_pos_).manhattanLength() >= QApplication::startDragDistance())
{
// Not a click.
return;
}
e->accept();
mouse_press_time_.invalidate();
mouse_press_pos_ = QPoint();
emit clicked();
}
private:
QElapsedTimer mouse_press_time_;
QPoint mouse_press_pos_;
};
If you now want something to happen when the label is clicked, connect the clicked() signal.
I want to generate the right click menu from the entry of a QTreeView. Currently I tried this, but I don't want the whole treeView to generate the right click and then me to filter the position on which the mouse is. I want that the signal to be generated from the entry.
connect(mtreeView, SIGNAL(customContextMenuRequested(const QPoint&)),
this, SLOT(showContextMenu(const QPoint&)));
Thanks!
Method 1
It is better to use the ContextMenuEvent rather than MouseReleaseEvent as it is a more portable way to trigger the context menu, will support accessibility on certain platforms, etc... The right click is not the only way to open a context menu.
If you do not want to subclass QTreeView , install an event handler from the main window:
ui->myTreeView->installEventFilter(this);
Then handle the event in the main window filterEvent
bool MainWindow::eventFilter(QObject *target, QEvent *event)
{
if (target == ui->myTreeView)
{
QContextMenuEvent* m = dynamic_cast<QContextMenuEvent*>(event);
if (event->type() == QEvent::ContextMenu && e!=0)
{
//Create context menu here
return true;
}
}
return false;
}
Method 2
Change the context menu mode to a signal:
ui->myTreeView->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->myTreeView, SIGNAL(customContextMenuRequested(QPoint)),
this, SLOT(treeCustomMenu(QPoint)));
Then implement your slot:
void MainWindow::treeCustomMenu(const QPoint & pos)
{
//Implement your menu here using myTreeView->itemAt(pos);
}
What I do is to override mouseReleaseEvent and check manually.
void MyTreeView::mouseReleaseEvent(QMouseEvent *e) {
if (e->button() == Qt::RightButton) {
QTreeWidgetItem *item = itemAt(e->pos());
if (item) {
QMenu m;
m.addAction("hello");
m.addAction("world");
QAction *selected = m.exec(mapToGlobal(e->pos()));
if (selected) {
qDebug() << "selected" << selected->text();
}
}
} else {
QTreeView::mouseReleaseEvent(e);
}
}
What you mean by the entry is not represented by a QObject in Qt. Only the item model is a QObject, but the individual tree nodes are not QObjects in Qt item/view system.
Therefore, they cannot emit any signal
I tried to implement a way for changing the background when an SVG button is pressed and reseting when it is released. My problem is that the mouseReleaseEvent is not called when I hide the QSvgWidget on which the mousePressEvent was called.
Here is my code:
SvgButton.cpp
#include "SvgButton.h"
SVGButton::SVGButton(QByteArray backgroundImage, QWidget *parent) :
QPushButton(parent)
{
this->init(backgroundImage);
}
SVGButton::SVGButton(QString backgroundImagePath, QWidget *parent) : QPushButton(parent)
{
SVGDom normalBackgroundImage(backgroundImagePath);
this->init(normalBackgroundImage.byteArray());
}
void SVGButton::init(QByteArray backgroundImage)
{
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
_backgroundImageWidget = new QSvgWidget();
_backgroundImageWidget->load(backgroundImage);
setLayout(new QHBoxLayout(this));
layout()->addWidget(_backgroundImageWidget);
this->setFlat(true);
}
void SVGButton::select()
{
this -> setStyleSheet("background-color:rgba(0, 0, 0, 10);");
}
void SVGButton::deselect()
{
this -> setStyleSheet("background-color:rgba(0, 0, 0, 0)");
}
int SVGButton::tag()
{
return _tag;
}
void SVGButton::setTag(int tag)
{
_tag = tag;
}
void SVGButton::paintEvent(QPaintEvent *)
{
QStyleOption opt;
opt.init(this);
QPainter p(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
}
SVGButton::~SVGButton()
{
delete _backgroundImageWidget;
}
and BaseNavigationButton.cpp
#include "BaseNavigationButton.h"
const int kButtonWidth = 140;
const int kButtonHeight = 70;
BaseNavigationButton::BaseNavigationButton(QString backgroundImagePath, QString pressedBackgroundImagePath, QWidget *parent)
: SVGButton(backgroundImagePath, parent)
{
this->setMinimumSize(kButtonWidth, kButtonHeight);
if (!pressedBackgroundImagePath.isNull())
{
SVGDom pressedBackgroundImage(pressedBackgroundImagePath);
_pressedBackgroundImageWidget = new QSvgWidget();
_pressedBackgroundImageWidget->load(pressedBackgroundImage.byteArray());
layout()->addWidget(_pressedBackgroundImageWidget);
_pressedBackgroundImageWidget->hide();
}
else
{
_pressedBackgroundImageWidget = NULL;
}
}
void BaseNavigationButton::mouseReleaseEvent(QMouseEvent * event)
{
qDebug() << "SVGButton::mouseReleaseEvent";
if (_pressedBackgroundImageWidget) {
_backgroundImageWidget->setVisible(false);
_pressedBackgroundImageWidget->setVisible(true);
//_backgroundImageWidget->show();
//_pressedBackgroundImageWidget->hide();
}
QPushButton::mouseReleaseEvent(event);
//emit released();
}
void BaseNavigationButton::mousePressEvent(QMouseEvent *event)
{
qDebug() << "SVGButton::mousePressEvent";
if(_pressedBackgroundImageWidget)
{
_backgroundImageWidget->setVisible(true);
_pressedBackgroundImageWidget->setVisible(false);
}
QPushButton::mousePressEvent(event);
// emit pressed();
}
BaseNavigationButton::~BaseNavigationButton()
{
if (_pressedBackgroundImageWidget)
{
delete _pressedBackgroundImageWidget;
}
}
The SVGDom basically just create a ByteArray from the SVG images. The code works, it is relatively correct, the only problem is that I described above.
When you hide a QWidget, it lose the focus, and a Widget --or some child widget-- must have the focus in order to the events work on it.
Try this simple example:
Press the mouse button when the cursor is over a button.
Move the pointer out of the button without release the mouse button.
Release the mouse button.
As you will see, this not will trigger the button clicked --clicked is a mousePressEvent followed by a mouseReleaseEvent-- event.
Hence, you cannot receive mouse buttons events from hidden objects.
What can I do to implement the "mouse pressed" style behaviour?
If by "mouse pressed style behaviour" you mean: "I want my widget style change when I press the mouse button".
Well, you can use the setStyleSheet function and applpy a CSS style to your widget. See Qt Style Sheets Examples
I've done many searches (leading me to this and that) and adding a few lines to my classes
MainWindow.cpp
#include <QtGui/QDragEnterEvent>
#include <QtGui/QDragLeaveEvent>
#include <QtGui/QDragMoveEvent>
#include <QtGui/QDropEvent>
#include <QtCore/QMimeData>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent)
{
....
setAcceptDrops(true);
}
void MainWindow::dragEnterEvent(QDragEnterEvent *event)
{
event->acceptProposedAction();
}
void MainWindow::dropEvent(QDropEvent *event)
{
qDebug() << "On Drop Event";
const QMimeData* mimeData = event->mimeData();
if (mimeData->hasUrls())
{
QStringList pathList;
QList<QUrl> urlList = mimeData->urls();
for (int i = 0; i < urlList.size() && i < 32; ++i)
{
pathList.append(urlList.at(i).toLocalFile());
}
if(openFiles(pathList))
event->acceptProposedAction();
}
}
void MainWindow::dragMoveEvent(QDragMoveEvent * event)
{
event->acceptProposedAction();
}
void MainWindow::dragLeaveEvent(QDragLeaveEvent* event)
{
event->accept();
}
But I can't drop a file onto my MainWindow (from the finder). It's not that my code crashes or does not compile, it's just that I literally can't. No reaction from the MainWindow, no highlight, nothing.
What am I missing?
I suspect you should also be overloading the dragMoveEvent: -
void QWidget::dragMoveEvent(QDragMoveEvent * event)
As the docs state: -
This event handler is called if a drag is in progress, and when any of the following conditions occur: the cursor enters this widget, the cursor moves within this widget, or a modifier key is pressed on the keyboard while this widget has the focus. The event is passed in the event parameter.
There's an example of a Qt drag and drop here. Specifically this is a good reference.
Background
I've made a custom widget with QLineEdit and several QPushButtons to use it with custom item delegate:
class LineEditor : public QWidget
{
public:
explicit LineEditor(QWidget *parent = 0) : QWidget(parent) {
setLayout(new QHBoxLayout);
layout()->setContentsMargins(0, 0, 0, 0);
layout()->setSpacing(0);
QLineEdit *edit = new QLineEdit(this);
layout()->addWidget(edit);
layout()->addWidget(new QPushButton(this));
layout()->addWidget(new QPushButton(this));
setFocusProxy(edit);
}
};
class PropertyDelegate : public QItemDelegate
{
public:
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const {
return new LineEditor(parent);
}
bool eventFilter(QObject *object, QEvent *event) {
if (event->type() == QEvent::KeyPress) {
qDebug() << "KeyPress";
}
if (event->type() == QEvent::ShortcutOverride) {
qDebug() << "ShortcutOverride";
}
return QItemDelegate::eventFilter(object, event);
}
};
I'm going to bind them with QListView and QStandardItemModel like this:
QStandardItemModel *model = new QStandardItemModel;
model->appendRow(new QStandardItem("1"));
model->appendRow(new QStandardItem("2"));
model->appendRow(new QStandardItem("3"));
QListView w;
w.setItemDelegate(new PropertyDelegate);
w.setModel(model);
w.show();
Question
Why in the PropertyDelegate::eventFilter when Tab key is pressed there is only QEvent::ShortcutOverride event, but pressing of any other key emits both QEvent::ShortcutOverride and QEvent::KeyPress events?
UPD: I want to implement the moving between lines by pressing Tab and Backtab like with standard widgets.
Well, finally I've done some research about that.
Explanation
When a view calls createEditor function of delegate it also installs the delegate event filter to editor.
QWidget *QAbstractItemViewPrivate::editor(const QModelIndex &index,
const QStyleOptionViewItem &options)
{
Q_Q(QAbstractItemView);
QWidget *w = editorForIndex(index).widget.data();
if (!w) {
QAbstractItemDelegate *delegate = delegateForIndex(index);
if (!delegate)
return 0;
w = delegate->createEditor(viewport, options, index);
if (w) {
w->installEventFilter(delegate);
......
}
However the delegate can catch only events of editor widget, but not events of its children. When the Tab key is pressed the QWidget::event function is called, it uses it to change focus to another widget:
bool QWidget::event(QEvent *event)
{
......
switch (event->type()) {
......
case QEvent::KeyPress: {
QKeyEvent *k = (QKeyEvent *)event;
bool res = false;
if (!(k->modifiers() & (Qt::ControlModifier | Qt::AltModifier))) { //### Add MetaModifier?
if (k->key() == Qt::Key_Backtab
|| (k->key() == Qt::Key_Tab && (k->modifiers() & Qt::ShiftModifier)))
res = focusNextPrevChild(false);
else if (k->key() == Qt::Key_Tab)
res = focusNextPrevChild(true);
if (res)
break;
}
......
}
......
}
Accordingly in my case the focus is set to next QPushButton after QLineEdit and event isn't propagated to the parent (LineEditor).
Solving
The right way to solve the problem is like QSpinBox does it. Because it is also has QLineEdit. In constructor of widget it sets focus proxy for line edit:
edit->setFocusProxy(this);
So all of focus events will reach the main widget. Also the focusPolicy property must be set because it's NoFocus by default:
setFocusPolicy(Qt::WheelFocus);
All we need to do in this moment it is to propagate necessary events to QLineEdit from main widget like this:
bool LineEditor::event(QEvent *e)
{
switch(e->type())
{
case QEvent::ShortcutOverride:
if(m_lineEdit->event(e))
return true;
break;
case QEvent::InputMethod:
return m_lineEdit->event(e);
default:
break;
}
return QWidget::event(e);
}
void LineEditor::keyPressEvent(QKeyEvent *e)
{
m_lineEdit->event(e);
}
void LineEditor::mousePressEvent(QMouseEvent *e)
{
if(e->button() != Qt::LeftButton)
return;
e->ignore();
}
void LineEditor::mouseReleaseEvent(QMouseEvent *e)
{
e->accept();
}
void LineEditor::focusInEvent(QFocusEvent *e)
{
m_lineEdit->event(e);
QWidget::focusInEvent(e);
}
void LineEditor::focusOutEvent(QFocusEvent *e)
{
m_lineEdit->event(e);
QWidget::focusOutEvent(e);
}
This should be enough.
Tricky
As it's said above the delegate can't catch events of editor's children. So to make editor's behavior like "native" I have to duplicate events from children to editor.
LineEditor installs event filter to QLineEdit in constructor:
edit->installEventFilter(this);
Implementation of filter looks like this:
bool LineEditor::eventFilter(QObject *object, QEvent *event)
{
if(event->type() == QEvent::KeyPress)
{
QKeyEvent* keyEvent = static_cast<QKeyEvent *>(event);
if(keyEvent->key() == Qt::Key_Tab || keyEvent->key() == Qt::Key_Backtab)
{
QApplication::postEvent(this, new QKeyEvent(keyEvent->type(), keyEvent->key(), keyEvent->modifiers()));
// Filter this event because the editor will be closed anyway
return true;
}
}
else if(event->type() == QEvent::FocusOut)
{
QFocusEvent* focusEvent = static_cast<QFocusEvent *>(event);
QApplication::postEvent(this, new QFocusEvent(focusEvent->type(), focusEvent->reason()));
// Don't filter because focus can be changed internally in editor
return false;
}
return QWidget::eventFilter(object, event);
}
It's also possible to use qApp->notify(this, event) for QKeyEvent instead of QApplication::postEvent because we filter this events anyway. But it's not possible for QFocusEvent because notify will redirect the event and it will not reach a child.
Note that the standard (QItemDelegate or QStyledItemDelegate) delegate will care about situation when focus is changed internally by itself:
if (event->type() == QEvent::FocusOut || (event->type() == QEvent::Hide && editor->isWindow())) {
//the Hide event will take care of he editors that are in fact complete dialogs
if (!editor->isActiveWindow() || (QApplication::focusWidget() != editor)) {
QWidget *w = QApplication::focusWidget();
while (w) { // don't worry about focus changes internally in the editor
if (w == editor)
return false;
w = w->parentWidget();
}
......