I created a simple QListView added a QStringListModel that allow items to be added and their text edited. But I don't want to allow empty fields to be added and I partially achieved this by using the dataChanged signal from the model which is emitted if the list item loses focus without text inserted or the user presses the Enter key without adding text.
However if you press the Esc key, the field remains empty and no dataChanged signal is emitted. How can I get notified if the field was left empty without overloading the QListView class which would be tedious(I used the designer to create the form)?
Is there another signal that is emitted or method I can use to achieve that?
Thanks!
Use event filters in your main GUI class:
void GUI::GUI()
{
ui->mListView->installEventFilter(this);
}
bool GUI::eventFilter(QObject *object, QEvent *event)
{
if (object == ui->mListView && event->type() == QEvent::KeyPress) {
QKeyEvent *ke = static_cast<QKeyEvent *>(event);
if (ke->key() == Qt::Key_Escape)
// special Esc handling here
}
else
return false;
}
It's simple to use your custom widgets in Qt Designer. Right click on the QListView and choose Promote to ... there add a new class and apply it to the widget.
http://qt-project.org/doc/qt-4.8/designer-using-custom-widgets.html
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 have implemented a sort of gestures listener in the parent widget. If the mouse event is a tap ( press and release with out any move events) then the children of that widget should handle the event, if not, and the events describe a swipe, then the parent widget should handle the events. Is there any way to redirect events to the parents first, then rebroadcast them so that the appropriate child could handle it if the need arises.
I think http://qt-project.org/doc/qt-4.8/qobject.html#installEventFilter is what you are looking for. So what you do is that you set any class inheriting from QObject and set it as the event filter. So to quote from the DOCs:
An event filter is an object that receives all events that are sent to this object.
The filter can either stop the event or forward it to this object.
The example in the link is for a QKeyEvent but can obviously be adapted to use various mouse functions as well. Below is a dummy example that would 'eat' the first click on a QPushButton while if you double click, the event would go through as normal (note that in this particular example first you would call the 'eat' sequence and upon the second click in a short time the button would take over).
bool MyWidget::eventFilter(QObject *obj, QEvent *event)
{
if( obj != pushButton ) {
return QObject::eventFilter(obj, event);
}
if (event->type() == QEvent::MouseButtonPress ) {
QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
qDebug("Intercepted mouse click with button %d", mouseEvent->button());
return true;
} else {
// standard event processing
return QObject::eventFilter(obj, event);
}
}
Does this help?
This is sort of a chicken and egg problem. I'd like my widget window to be closed when the mouse clicks outside. As I understand it, there will be no mouse events for my widget for a click occurring outside of it. There is a SetFocus slot, but where is its counterpart or focus loss? "focusOutEvent" doesn't get called for my class.
My widget window is a child window of a widget always shown on my main window and it's a "Qt::ToolTip", so I assume some problems could arise from that fact. Any way around that?
My Goal: I have a custom toolbar widget where buttons on it may have “drop down” widgets. These drop down widgets have no standard windows frame. I don’t want them to “steal” caption focus from the main window and I want them to disappear as soon as the user clicks ANYWHERE on the screen outside of their region. I have having serious difficulties finding a strategy that’s not compromise on Qt to get this done.
Am I missing something? (bet I am).
I used:
setWindowFlags(Qt::FramelessWindowHint | Qt::Popup);
This seems to work well on OSX and Windows. My window appears correctly, does not steal the focus from my main window's caption, and the focus loss event is called correctly as soon as I click outside of it.
If your widget could have focus, and 'steal' the caption focus of some of your other widgets, it would have been easier. Something like this could work:
class ToolBarWidget : public QWidget
{
Q_OBJECT
public:
explicit ToolBarWidget(QWidget * parent = 0)
{
setFocusPolicy(Qt::ClickFocus);
}
protected:
void focusOutEvent(QFocusEvent * event)
{
close();
}
}
And when you create any of your widgets you'd do:
ToolBarWidget * pWidget = new ToolBarWidget(this);
pWidget->show();
pWidget->setFocus();
Done! Well, I guess not quiet. first, you don't want the ToolBarWidget to get any focus in the first place. And second, you want for the user to be able to click anywhere and the ToolBarWidget to be hidden.
So, you may keep track of every ToolBarWidget that you create. For example, in a 'QList ttWidgets' member variable. Then, whenever you create a new ToolBarWidget, you'd do this:
ToolBarWidget * pWidget = new ToolBarWidget(this);
pWidget->installEventFilter(this);
pWidget->show();
and in your main widget class, implement the eventFilter() function. Something like:
bool MainWidget::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::FocusOut ||
event->type() == QEvent::KeyPress ||
event->type() == QEvent::MouseButtonPress)
{
while (!ttWidgets.isEmpty()) {
ToolBarWidget * p = ttWidgets->takeFirst();
p->close();
p->deleteLater();
}
}
return MainWidget::eventFilter(obj, event);
}
And that will work. Because this way, even though your ToolTabWidgets aren't getting focus, some other widget in your main widget has focus. And once that changes (whether the user clicked out of your window, or on another control inside it, or in this case, a key or mouse button is pressed, the control will reach that eventFilter() function and close all your tab widgets.
BTW, in order to capture the MouseButtonPress, KeyPress etc. from the other widgets, you would either need to installEventFilter on them too, or just reimplement the QWidget::event(QEvent * event) function in your main widget, and look for those events there.
you can do this by using QDesktopWidget.h like this
void MainWindow::on_actionAbout_triggered()
{
AboutDialog aboutDialog;
//Set location of player in center of display
aboutDialog.move(QApplication::desktop()->screen()->rect().center() -aboutDialog.rect().center());
// Adding popup flags so that dialog closes when it losses focus
aboutDialog.setWindowFlags(Qt::Popup);
//finally opening dialog
aboutDialog.exec();
}
This is what worked for me in order to not steel the focus from the main application:
.h
bool eventFilter(QObject *obj, QEvent *event) override;
.cpp
bool Notification::eventFilter(QObject *obj, QEvent *event)
{
if(event->type() == QEvent::MouseButtonPress)
deleteLater();
return QObject::eventFilter(obj, event);
}
...
// somewhere else (i.e. constructor, main window,...)
qApp->installEventFilter(this);
OP's own answer is great for Qt versions below 4.8, but as they mention in their answer, it does not work for versions above that. The Qt::Popup widget will not disappear when the mouse is clicked outside of the widget, and it will sink all of the input that would normally close it.
Upon further investigation, this is only an issue for non-dialog widgets. A QDialog using Qt::Popup will properly close when the user clicks outside of it, but any other QWidget, like a QFrame, will not. So in order to work around this behavior change in Qt 4.8, all that is necessary is to wrap the widget in a QDialog.
How to get Click Event of QLineEdit in Qt ?
I am not able to see any SLOT related to click in QLineEdit ?
I don't think subclassing a QLineEdit is the right choice. Why subclass if you don't need to? You could instead use event filters. Check out QObject::eventFilter.
Example:
MyClass::MyClass() :
edit(new QLineEdit(this))
{
edit->installEventFilter(this);
}
bool MyClass::eventFilter(QObject* object, QEvent* event)
{
if(object == edit && event->type() == QEvent::FocusIn) {
// bring up your custom edit
return false; // lets the event continue to the edit
}
return false;
}
You need to reimplement focusInEvent in a new class extending QLineEdit. The following links are going to help you.
http://doc.qt.io/qt-5/qwidget.html#focusInEvent
QLineEdit - focus event
How to know if a QLineEdit got focus?
QLineEdit Focus Event
Although there is no "clicked" or "entered" event. You can use the
void cursorPositionChanged(int old, int new)
Signal. It is emitted when the user clicks the lineedit (if it is enabled) and also on a few other occasions so you have to verify which of the events actually happened but I think this is still easier than subclassing or using the event listener for some applications.
I dono if this will help,
i had to call a function once a text is entered. This is how i did it.
connect(ui->passwordSetLineEdit,SIGNAL(textEdited(QString)),this,SLOT(onTextEdit(QString)));
when a text is entered textEdited signal will be emited, thus my onTextEdit function will be called.
There is no signals like clicked() for QLineEdit, but you can subclass it and emit such signal in your custom implementation of mouseReleaseEvent.
I used this solution many times
def clickable(widget): # make this function global
class Filter(QObject):
clicked = pyqtSignal()
def eventFilter(self, obj, event):
if obj == widget and event.type() == QEvent.MouseButtonRelease and obj.rect().contains(event.pos()):
self.clicked.emit()
return True
else:
return False
filter = Filter(widget)
widget.installEventFilter(filter)
return filter.clicked
clickable(self.lineedit).connect(self.test) #use this in class
def test(self):
print("lineedit pressed")
pass
Just use the Pushbutton clicked event. Change the backcolor of the Pushbutton into Transparent then remove the text of it. Lay it in front of the LineEdit then use the setfocus property of the LineEdit when the push button was clicked. That's the easiest way to get the clicked event and use it in LineEdit.. 😉
I am implementing QAbstractTableModel and I would like to insert a QPushButton in the last column of each row. When users click on this button, a new window is shown with more information about this row.
Do you have any idea how to insert the button? I know about delegating system but all examples are only about "how to edit color with the combo box"...
You can use
QPushButton* viewButton = new QPushButton("View");
tableView->setIndexWidget(model->index(counter,2), viewButton);
The model-view architecture isn't made to insert widgets into different cells, but you can draw the push button within the cell.
The differences are:
It will only be a drawing of a pushbutton
Without extra work (perhaps quite a bit of extra work) the button won't be highlighted on mouseover
In consequence of #1 above, you can't use signals and slots
That said, here's how to do it:
Subclass QAbstractItemDelegate (or QStyledItemDelegate) and implement the paint() method. To draw the pushbutton control (or any other control for that matter) you'll need to use a style or the QStylePainter::drawControl() method:
class PushButtonDelegate : public QAbstractItemDelegate
{
// TODO: handle public, private, etc.
QAbstractItemView *view;
public PushButtonDelegate(QAbstractItemView* view)
{
this->view = view;
}
void PushButtonDelegate::paint(
QPainter* painter,
const QStyleOptionViewItem & option,
const QModelIndex & index
) const
{
// assuming this delegate is only registered for the correct column/row
QStylePainter stylePainter(view);
// OR: stylePainter(painter->device)
stylePainter->drawControl(QStyle::CE_PushButton, option);
// OR: view->style()->drawControl(QStyle::CE_PushButton, option, painter, view);
// OR: QApplication::style()->drawControl(/* params as above */);
}
}
Since the delegate keeps you within the model-view realm, use the views signals about selection and edits to popup your information window.
You can use setCellWidget(row,column,QWidget*) to set a widget in a specific cell.