I have a problem with a QWidget derivate (NewPayment). It's a simple window, with some controls and a QDialogButtonBox . It has 2 slots:
void NewPayment::on_buttonBox_accepted() {
//(some action going in here)
this->close();
}
void NewPayment::on_buttonBox_rejected() {
this->close();
}
When I click either OK or Cancel - the slot is triggered as expected. The problem is, that the window does not close. All the contents dissappears, and an empty window is left (window title is left).
The widget exists as a MDISubwindow, and is created like so:
void HurBudClientGUI::addNewPayment(int direction, int contractorid) {
foreach(QMdiSubWindow* it, this->ui.mainArea->subWindowList()) {
if ( NewPayment* np = qobject_cast<NewPayment*>( it->widget() ) ) {
if (np->getContractorID() == contractorid) {
this->ui.mainArea->setActiveSubWindow(it);
return;
}
}
}
NewPayment* np = new NewPayment(direction, contractorid, this);
np->setAttribute(Qt::WA_DeleteOnClose);
this->ui.mainArea->addSubWindow(np);
np->show();
}
The interesting part is, that when I either:
click on the 'X' in tre upper right corner
call QMdiArea::closeActiveSubWindow() from the main window
call QMdiArea::closeAllSubWindows() from the main window
the window is closed properly. I have overwritten a QWidget::closeEvent(QCloseEvent * event) for my class:
void NewPayment::closeEvent(QCloseEvent * event) {
qDebug() << "[" << __FUNCTION__ << "]:" << "event: " << event << "; sender:" << sender();
}
and it shows preety much the same event every time - no matter how I try to close it:
[ NewPayment::closeEvent ]: event: QCloseEvent(Close, 0x40bd64, type = 19) ; sender: QDialogButtonBox(0x4dfa7a8, name = "buttonBox") // I hit cancel
[ NewPayment::closeEvent ]: event: QCloseEvent(Close, 0x40b634, type = 19) ; sender: QObject(0x0) // I hit the 'X' in the window corner
[ NewPayment::closeEvent ]: event: QCloseEvent(Close, 0x40b468, type = 19) ; sender: QObject(0x0) // I hit "close active sub window" from parent window
[ NewPayment::closeEvent ]: event: QCloseEvent(Close, 0x40b454, type = 19) ; sender: QObject(0x0) // I hit "close all sub windows" from parent window
The best part is, that when I hit "cancel" (the windows is cleared, but stays open), and then click "X" or whatever - the window is closed, but the control does not pass through my NewPayment::closeEvent (i have a brakepoint there - and it does not fire) .
It works preety much the same in other windows. What is strange, that I'm preety sure that it worked previously (+- a week ago) for other windows (they closed after clicking OK ant performing all necessary operations) . I guess I will end up analyzyig diff from SVN, but maybe someone had similar issue? I have had very little sleep lately, so maybe I missed something trivial?
I will appreciate any help.
What do you expect, a widget is not a window. You get output from closing the widget, but that's not the same as closing the window.
You need to have a handle to the window if you want to close it. You could:
keep the pointer returned from addSubWindow()
create the window in advance, create the widget parented to the window, set the window's widget to the widget, and then use the widget's parent() to access the window.
I followed #ddriver 's suggestion and ended up with
void NewPayment::on_buttonBox_rejected() {
if (QMdiSubWindow* psw = qobject_cast<QMdiSubWindow*>(this->parent()) ) {
psw->close();
} else {
this->close();
}
}
Now it works as it was supposed to.
Related
I have a Qt app with several window shortcuts defined in a Qt Designer form action. The shortcuts works well until they are pressed while the focus is on a widget that handles the same combination (overriding my window shortcut).
I would like to have the opposite behavior: window shortcuts overriding focused widget shortcuts.
I tried using eventFilter and I can catch the desired events, but I'm not able to resend them in a way that the global shortcuts are called. I could use a big switch and call the actions myself, but of course, I would like to avoid that.
I used postEvent and sendEvent inside the eventFilter using the MainWindow as the receiver, but the events are ignored:
bool MainWindow::eventFilter(QObject*, QEvent* event) {
if (event->type() == QEvent::KeyPress) {
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
if (keyEvent->key() == Qt::Key_Z
&& keyEvent->modifiers() == Qt::ControlModifier) {
//Calling the triggers directly works
ui->actionUndo->trigger();
return true;
} else if (keyEvent->modifiers().testFlag(
Qt::KeypadModifier)) {
QKeyEvent* ev2
= new QKeyEvent(keyEvent->type(), keyEvent->key(), 0);
qDebug() << keyEvent << ev2;
//This sendEvent doesn't work
QApplication::sendEvent(ui->widget, ev2);
event->accept();
return true;
} else {
return false;
}
}
return false;
}
As one of the solutions you can install QEvent::ShortcutOverride event filters:
For QEvent::ShortcutOverride the receiver needs to explicitly accept
the event to trigger the override. Calling ignore() on a key event
will propagate it to the parent widget. The event is propagated up the
parent widget chain until a widget accepts it or an event filter
consumes it.
That event would be called when some widget tries to override a shortcut event, e.g. just a simple example:
I have just a new Qt app with one lineEdit and window menu with Ctrl+V shortcut (overrides the paste shortcut in lineEdit).
Here how it works:
1.Create the filtering method that would ignore (return true) the shortcut overrides (I've used MainWindow::eventFilter in sample app, however you can use any filtering object you need or want). It might be better to follow Qt docs and use the accept()/ignore() as stated above, however on my app it worked just fine without them just returning true/false.
2.Install the event filter from p.1 to widget that should ignore shortcut action if overriding.
3.I've added the menu action with Ctrl+V shortcut in designer. In code below you would see the "Hello from window shortcut!" - the menu action result, when trying to Paste (Ctrl+V) instead of actual lineEdit paste operation.
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->lineEdit->installEventFilter(this);
}
bool MainWindow::eventFilter(QObject *watched, QEvent *event)
{
if (event->type() == QEvent::ShortcutOverride) {
QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
// Ignore only the Ctrl + V shortcut override, you can customize check for your needs
if (keyEvent->modifiers().testFlag(Qt::ControlModifier) && keyEvent->key() == 'V') {
qDebug() << "Ignoring" << keyEvent->modifiers() << "+" << (char)keyEvent->key() << "for" << watched;
event->ignore();
return true;
}
}
return QMainWindow::eventFilter(watched, event);
}
void MainWindow::on_action1_triggered()
{
qDebug() << "Hello from window shortcut!";
}
Sample debug output:
Ignoring QFlags(ControlModifier) + V for QLineEdit(0x575b10, name = "lineEdit")
Hello from window shortcut!
Note: Unfortunatelly you should install such filters for all widgets you want not to override the shortcuts manually.
UPDATE: Shortly - you are ignoring the underlying widget shortcut event and propagating it to the parent widget.
Below is comparison for Ctrl-Z (triggers Undo in edit) and Ctrl-V (ignored in edit instead of Paste, and triggers menu Action):
Block I - events in beginning are the same for both Ctrl-Z and ignored Ctrl-V:
QLineEdit recieves QKeyEvent(ShortcutOverride, Key_Control, ControlModifier)
QLineEdit recieves QKeyEvent(KeyPress, Key_Control, ControlModifier)
MainWindow recieves QKeyEvent(KeyPress, Key_Control, ControlModifier)
QLineEdit recieves QKeyEvent(ShortcutOverride, Key_Z, ControlModifier)
Block II - where the difference happens...
For Ctrl-Z - lineEdit receives the Ctrl+Z KeyPress event, triggering the Undo operation:
QLineEdit recieves QKeyEvent(KeyPress, Key_Z, ControlModifier)
Here the MainWindow recieves no events, not depending of has it Ctrl+Z action shortcuts or not, it's just swallowed by QLineEdit
For Ctrl-V - MainWindow receives the Ctrl+V ShortcutOverride event propagated from QLineEdit:
Ignoring "Ctrl+V" for QLineEdit code executes in filterEvent
MainWindow recieves QKeyEvent(ShortcutOverride, Key_V, ControlModifier)
"Hello from window shortcut!" code from menu Action triggered slot executed.
Here the QLineEdit receives no events after filter tells it to ignore ShortcutOverride, the MainWindow shortcut executed instead
Block III - events in ending are also the same for both Ctrl-Z and ignored Ctrl-V - just key release events:
QLineEdit recieves QKeyEvent(KeyRelease, Key_Z, ControlModifier)
MainWindow recieves QKeyEvent(KeyRelease, Key_Z, ControlModifier)
QLineEdit recieves QKeyEvent(KeyRelease, Key_Control)
MainWindow recieves QKeyEvent(KeyRelease, Key_Control)
P.S. I really don't know why it happens the exactly so - but this is just how it works :)
I am using QProgressDialogand I disable the close (x) button when I start the progress bar.
progress->setWindowFlags(progress->windowFlags() & ~Qt::WindowCloseButtonHint);
After the operation is complete in QProcess, in the finished slot, I am re-enabling the close button but it doesn't work. It instead closes the progress window. I have tried both lines below but it does the same.
progress->setWindowFlags(progress->windowFlags() | Qt::WindowCloseButtonHint);
or
progress->setWindowFlags(progress->windowFlags() | Qt::WindowCloseButtonHint | Qt::CustomizeWindowHint);
Why is it not working the way it should?
I figured out problem. You dialog is hidden and there are no way to solve that. You can only show() it again.
As doc said:
Note: This function calls setParent() when changing the flags for a
window, causing the widget to be hidden. You must call show() to make
the widget visible again.
From Qt source:
void QWidget::setWindowFlags(Qt::WindowFlags flags)
{
if (data->window_flags == flags)
return;
Q_D(QWidget);
if ((data->window_flags | flags) & Qt::Window) {
// the old type was a window and/or the new type is a window
QPoint oldPos = pos();
bool visible = isVisible();
setParent(parentWidget(), flags);
^^^^^^^^^
// if both types are windows or neither of them are, we restore
// the old position
if (!((data->window_flags ^ flags) & Qt::Window)
&& (visible || testAttribute(Qt::WA_Moved))) {
move(oldPos);
}
// for backward-compatibility we change Qt::WA_QuitOnClose attribute value only when the window was recreated.
d->adjustQuitOnCloseAttribute();
} else {
data->window_flags = flags;
}
}
And as doc said again:
Note: The widget becomes invisible as part of changing its parent,
even if it was previously visible. You must call show() to make the
widget visible again.
For example:
MainWindow w;w.show();
w.setWindowFlags(w.windowFlags() & ~Qt::WindowCloseButtonHint);
w.setWindowFlags(w.windowFlags() | Qt::WindowCloseButtonHint);
w.show();
I've disabled X button in Qt from my dialog using this line:
myDialog->setWindowFlags(Qt::Dialog | Qt::Desktop)
but I couldn't detect Alt + F4 using this code:
void myClass::keyPressEvent(QKeyEvent *e)
{
if ((e->key()==Qt::Key_F4) && (e->modifiers()==Qt::AltModifier))
doSomething();
}
what should I do to detect Alt+F4 or disable it in Qt?
Pressing Alt+F4 results in a close event being sent to your top level window. In your window class, you can override closeEvent() to ignore it and prevent your application from closing.
void MainWindow::closeEvent(QCloseEvent * event)
{
event->ignore();
}
If you left the close button (X) visible, this method would also disable it from closing your app.
This is usually used to give the application a chance to decide if it wants to close or not or ask the user by displaying an "Are you sure?" message box.
The code below prevents a dialog close when pressed Alt+F4, [X] or Escape, but not by calling SomeDialog::close() method.
void SomeDialog::closeEvent(QCloseEvent *evt) {
evt->setAccepted( !evt->spontaneous() );
}
void SomeDialog::keyPressEvent(QKeyEvent *evt) {
// must be overridden but empty if the only you need is to prevent closing by Escape
}
good luck to all of us ;)
Also you can handle the event in your dialog's class (at least if it's modal dlg):
void MyDialog::closeEvent(QCloseEvent* e)
{
if ( condition )
e->ignore();
else
__super::closeEvent(e);
}
I would simply like to catch the mouseRelease event when a user drags an item from a QTableView and releases the left button.
I want to highlight possible dropping zones in my app, like changing the background of a widget.
I start by detecting the drag-start by re-implementing:
void LibraryTableView::startDrag( Qt::DropActions supportedActions )
{
m_dragReleased = false;
emit dragStart();
QTableView::startDrag(supportedActions);
}
and emitting my own signal.
Now that the dropzone has changed, I need to catch the release event to redraw the dropzone as before whether the drag and drop succeded or not !
I tried different models, reimplementing the 4 mouse events and eventFilter, but the Mouse Release Event is never catched.
Here is my code:
void LibraryTableView::mouseDoubleClickEvent( QMouseEvent* event )
{
QTableView::mouseDoubleClickEvent(event);
}
void LibraryTableView::mouseMoveEvent( QMouseEvent* event )
{
qDebug() << "move";
QTableView::mouseMoveEvent(event);
}
void LibraryTableView::mousePressEvent( QMouseEvent* event )
{
qDebug() << "press";
QTableView::mousePressEvent(event);
}
void LibraryTableView::mouseReleaseEvent( QMouseEvent* event )
{
qDebug() << "real"; // Never called when drag ends ...
QTableView::mouseReleaseEvent(event);
}
So, it is a bug ?
If you know a trick, it would help me a lot.
Thanks !
Edit: I cannot reimplement dropEvent for every widget in my application, if the user drags and drop an element in another application, I still want to catch the release event ...
As said above it's been 3 years but thank to the last answer I found an even easier solution for this problem.
void LibraryTableView::startDrag( Qt::DropActions supportedActions )
{
m_dragReleased = false;
emit dragStart();
QTableView::startDrag(supportedActions);
//CODE HERE WILL BE EXECUTED ONLY WHEN THE MOUSE BUTTON HAS
//BEEN RELEASED SO YOU CAN DO THE FOLLOWING
emit dragStop();
}
Three years since this question was asked and this Qt problem still exists in Qt 5.4. Lately I had the same problem: Drops outside the application and no MouseReleaseEvent. The solution is simple and not really a trick:
The mousePressEvent, which starts the drag&drop looks like this (simplified):
void DragNDropListView::mousePressEvent(QMouseEvent *event){
....
QDrag *drag = new QDrag(this);
QMimeData *mimeData = new QMimeData;
mimeData->setData("application/x-xxxxxx", QByteArray());
drag->setMimeData(mimeData);
drag->exec();
// The d&d ended here. Inside the widget where the drag
// started, a mouseReleaseEvent would have been fired.
// Outside this widget not.
// drag->mimeData() is here still available.
}
}
}
The trick is simple: drag->exec() starts its own event loop, which is exited when the mouse button is released. The mouse position after drag->exec(); can be determined with QCursor.
For QTableView you have to reimplement three function for dnd support:
void dragEnterEvent ( QDragEnterEvent * event ) - in this function the mouse enters the widget
void QAbstractItemView::dragMoveEvent ( QDragMoveEvent * event ) - in this function you can update your drop zone highlighting
void QAbstractItemView::dropEvent ( QDropEvent * event ) - in this function you decide whether to accept the event
I encountered a similar issue and was not happy to find out that the MouseReleaseEvent was not getting fired at the end of a drag.
I just used the DragLeaveEvent and toggled my variables off, as I would have done in the MouseReleaseEvent. If user dragged off the app, and then back on, those previously toggled off variables would get re-enabled in the DragMoveEvent (assuming it is accepted).
That was my trick, at least. Hope it helps.
I have a QMainWindow, and want to handle the "clicked" signal from a smaller widget (such as tableview) inside it.
Originally I connect the signal to a slot of this QMainWindow, this is the most common approach.
Now I need to tell which mouse button is clicked, and do different things for left and right button, I found that the "clicked" signal don't have the mouse event information.
I tried to implement the "mousePressEvent" function,but there are still some problem. if the mouse action is acted on the smaller widget, the MainWindow won't go into its mousePressEvent.
Some document says that we can tell the button by QQApplication::mousebuttons()
http://bugreports.qt-project.org/browse/QTBUG-1067
and I also found some sample code. However, this is for "press event", but I want to get the mouse button for "click event".
Follows is the sample code :
connect(moduleTree,SIGNAL(itemPressed(QTreeWidgetItem *, int)),this,SLOT(SlotItemClicked(QTreeWidgetItem *, int)));
void CGuiMainwindow::SlotItemClicked(QTreeWidgetItem *item, int column)
{
if (qApp->mouseButtons() == Qt::LeftButton)
{ return; }
if (qApp->mouseButtons() == Qt::RightButton)
{
......
}
}
When I try to do this, neither of the 2 if statements will be satisfied, I don't know why. the qApp->mouseButtons() always return 0, how can I get the clicked mouse button by QApplication::mouseButtons?
In my code, the slot looks like that :
void clickItem( const QModelIndex & idx){.....}
You get 0 becouse clicked is emited after mouse release, not at mouse press. What do you want to achieve ? Maybe try settings on you widget contextMenuPolicy to custom, and than connect to signal contextMenuRequested (for the right click) and clicked for the left click ?
for "connect" use this:
connect(moduleTree,SIGNAL(itemClicked(QTreeWidgetItem *,int )),this
,SLOT(SlotItemClicked(QTreeWidgetItem *, int)));
define a global flag:
public:
Qt::MouseButton mouseClickedBtnFlag;
and then reimplement "mouseReleaseEvent":
CGuiMainwindow::mouseReleaseEvent ( QMouseEvent * event )
{
mouseClickedBtnFlag = event->button();
}
and then:
void CGuiMainwindow::SlotItemClicked(QTreeWidgetItem *item, int column)
{
if (mouseClickedBtnFlag == Qt::LeftButton)
{ return; }
if (mouseClickedBtnFlag == Qt::RightButton)
{
......
}
}
Qt::MouseButtons is a QFlags type. You can't test it with == operator. Use & operator for testing:
if(QApplication::mouseButtons() & Qt:LeftButton) {
...
}