I have a program that creates a grid of buttons using PyQt (all QPushButtons in a QGridLayout), like this:
I was wondering how I can drag my mouse while holding mouse1 down to run each button's function for each one that the mouse is dragged over. Right now, when dragging and holding, only the button the click was started on is triggered, and no other buttons can be selected until mouse release. Is there a function that just runs it on press or release, or unselects the button after the function is ran?
For reference, my code for the currently selected button is as follows:
self.button.pressed.connect(lambda: self.click_func(self_global, x, y,
btn_id))
The function isn't important, and would just take up more room here, but I hope you get the point.
You could use an eventFilter. This allows you to send events to another QObject (usually the parent widget of the buttons) to decide what to do with them.
class MyWidget(QtGui.QWidget):
def createButton(self):
button = QtGui.QPushButton()
button.setMouseTracking(True)
button.installEventFilter(self)
return button
def eventFilter(self, obj, event):
if isinstance(obj, QtGui.QPushButton):
if event.type() == QtCore.QEvent.Enter:
if event.buttons() & QtCore.Qt.LeftButton:
print 'Mouse Pressed Over Button'
return False
Looking at your example, you may want to also consider using a QGraphicsView and QGraphicsScene. They are good building blocks for creating truly custom widgets. It's possible you may be able to hack QPushButtons to behave in a way they weren't intended, but for custom drawn widgets, QGraphicsViews are the way to go.
Related
I am creating a node graph and I want to be able to click the empty space in a scene and middle-mouse-drag to navigate without deselecting the currently selected items in the scene. Any suggestions?
I can block the middle click in the view's mousePressEvent and get the right behavior but then I no longer have middle-mouse click events working on items in the scene. I don't mind a middle-click resulting in a single selection when clicking on an item in the scene, but if I middle-click the empty space in the scene I don't want the selection altered.
This didn't cover the more complex behavior I am looking for: PyQt. How to block clear selection on mouse right click?
I didn't try using an eventFilter as I assume the issue would be the same
I am using PyQt/PySide, FWIW.
Before I roll my own workaround I thought I'd post here for the correct way or at least other workaround ideas.
Some workaround ideas:
Block the mousePressEvent to the scene but iterate over child items to deliver it directly
Restore selection while still in the mousePressEvent in the scene. Probably bad for performance at scale but simple I suppose.
Any feedback would be great!
[Edit:]
Here is my python version of the answer. Code tested. In my QGraphicsScene derived class:
def mousePressEvent(self, event):
# Prevent the QGraphicsScene default behavior to deselect-all when clicking on
# empty space by blocking the event in this circumstance.
item_under_the_mouse = self.itemAt(event.scenePos())
if event.button() == QtCore.Qt.MidButton and not item_under_the_mouse:
event.accept()
else:
super(GraphScene, self).mousePressEvent(event)
In your QGraphicsScene::mousePressEvent derived implementation, if it's a middle mouse click, check for items under the mouse click. If there aren't any, then accept the event and don't call the base class implementation. If something is under the click, then just call the base implementation; you don't have to try to reimplement that yourself. I think this is the general idea:
void MyScene::mousePressEvent (QGraphicsSceneMouseEvent *evt)
{
if ((evt->buttons () & Qt::MidButton) && items (evt->scenePos ().count ())
{
QGraphicsScene::mousePressEvent (evt);
}
else
{
evt->accept ();
}
}
I'm not sure if the accept is required in this case or not. I haven't compiled or tested this, but hopefully, it's helpful to get you going in the right direction.
I got a QGraphicsScene that contains QGraphicsItems. On clicking such an item I open a dialog. I now want the item to be displayed in an area of the dialog.
I tried a QGraphicsView (in the dialog) and "pointed" it to the item which works allmost perfectly. The problem is, that it is possible to click the item in the dialog which would open a new dialog.
So my question: is there a easy way to tell QGraphicsView to ignore any input events? If not, is there a easy way to display a QGraphicsItem within a widget?
I am feeling so stupid...
QGraphicsView::setInteractive(false) did the trick.
I am still able to move the icon with the mouse wheel but this can probably be avoided by restricting the scene rect with setSceneRect()
You can install an event filter, on the QGraphicsView, which ignores input events. The Qt documentation states:
In your reimplementation of this function, if you want to filter the event out, i.e. stop it being handled further, return true; otherwise return false.
for my application I'm trying to put login control into a QMenu and am struggling with controlling the focus policy.
This is my custom login widget:
class LoginWidget(QWidget):
def __init__(self, parent=None):
super(LoginWidget, self).__init__(parent)
mainLayout = QVBoxLayout()
layoutH = QHBoxLayout()
nameField = QLineEdit()
pwdField = QLineEdit()
pwdField.setEchoMode(QLineEdit.EchoMode(2))
btnSubmit = QPushButton('log in')
btnSubmit.setIcon(IconCache.getIcon('login'))
for w in (nameField, pwdField):
layoutH.addWidget(w)
mainLayout.addLayout(layoutH)
mainLayout.addWidget(btnSubmit)
self.setLayout(mainLayout)
I then add the above widget to my menu like this:
app = QApplication([])
menu = QMenu()
settingsAction = QAction('settings', menu)
loginAction = QWidgetAction(menu)
loginAction.setDefaultWidget(LoginWidget())
menu.addAction(settingsAction)
menu.addAction(loginAction)
btn = QToolButton()
btn.setText('menu button')
btn.setMenu(menu)
btn.setPopupMode(QToolButton.InstantPopup)
btn.show()
sys.exit(app.exec_())
The problem is that when you open the menu, click into the name field to fill in the user name, then hit the tab key, focus jumps to the "settings" action rather than to the password widget inside the LoginWidget.
I tried setFocusPolicy(Qt.StrongFocus) on the LoginWidget as well as it's pwdField but to no avail.
Can this be done?
Thanks in advance,
frank
The QMenu has special handling for Tab / Backtab, which effectively converts them into Up / Down arrow key presses.
However, the real source of the problematic behaviour is the focusNextPrevChild method, which keeps forcing the focus back to the menu. Fortunately, this method is virtual, so it can be overridden in a subclass, like so:
class Menu(QtGui.QMenu):
def focusNextPrevChild(self, next):
return QtGui.QWidget.focusNextPrevChild(self, next)
This will restore normal tabbing between child widgets.
To also enable keyboard navigation from child widgets back to normal menu items, make sure the LoginWidget has a focus proxy, like so:
class LoginWidget(QtGui.QWidget):
def __init__(self, parent=None):
...
self.setFocusPolicy(QtCore.Qt.TabFocus)
self.setFocusProxy(nameField)
http://qt-project.org/doc/qt-4.8/qwidget.html#setTabOrder
http://qt-project.org/doc/qt-4.8/focus.html#tab-or-shift-tab
Tab or Shift+Tab
Pressing Tab is by far the most common way to move focus using the keyboard. (Sometimes in data-entry applications Enter does the same as Tab; this can easily be achieved in Qt by implementing an event filter.)
Pressing Tab, in all window systems in common use today, moves the keyboard focus to the next widget in a circular per-window list. Tab moves focus along the circular list in one direction, Shift+Tab in the other. The order in which Tab presses move from widget to widget is called the tab order.
You can customize the tab order using QWidget::setTabOrder(). (If you don't, Tab generally moves focus in the order of widget construction.) Qt Designer provides a means of visually changing the tab order.
Since pressing Tab is so common, most widgets that can have focus should support tab focus. The major exception is widgets that are rarely used, and where there is some keyboard accelerator or error handler that moves the focus.
For example, in a data entry dialog, there might be a field that is only necessary in one per cent of all cases. In such a dialog, Tab could skip this field, and the dialog could use one of these mechanisms:
If the program can determine whether the field is needed, it can move focus there when the user finishes entry and presses OK, or when the user presses Enter after finishing the other fields. Alternately, include the field in the tab order but disable it. Enable it if it becomes appropriate in view of what the user has set in the other fields.
The label for the field can include a keyboard shortcut that moves focus to this field.
Another exception to Tab support is text-entry widgets that must support the insertion of tabs; almost all text editors fall into this class. Qt treats Ctrl+Tab as Tab and Ctrl+Shift+Tab as Shift+Tab, and such widgets can reimplement QWidget::event() and handle Tab before calling QWidget::event() to get normal processing of all other keys. However, since some systems use Ctrl+Tab for other purposes, and many users aren't aware of Ctrl+Tab anyway, this isn't a complete solution.
So you probably will want to use:
QWidget.setTabOrder( nameField, pwdField )
QWidget.setTabOrder( pwdField, btnSubmit )
or something similar.
Hope that helps.
According to the QGraphicsView docs, dragMode's behavior "only affects mouse clicks that are not handled by any item." It then says "You can define a custom behavior by creating a subclass of QGraphicsView."
In my case I'd like clicks on an item that accepts mouse clicks to call the item's mouse clicks as normal. And I'd like clicks not on those items to start a rubber band drag (also as normal). However, I'd like to be able to ctrl-click the view, and have that start a rubber band drag selection rather than call the item's mouse event methods.
I know how to assess whether or not ctrl is associated with a mouse click event:
if (event->modifiers().testFlag(Qt::ControlModifier))
However, I don't know what to do with this condition, or where exactly to put it. Should I put it in QGraphicsItem, QGraphicsScene, or QGraphicsView? QGraphicsView seems most likely, as the docs somewhat hint.
You need to put the condition to QGraphicsItem like this:
if (event->modifiers().testFlag(Qt::ControlModifier))
{event->setAccepted(false); return;}
Now You can handle the event in Your QGraphicsView.
I'm working on a custom Qt button that allows you to edit the text on the button if you double click it. When the button is double clicked, a QLineEdit appears where the text on the button is allowing the user to edit the text on the button. My requirement is that if the user clicks anywhere in the application window, the QLineEdit should disappear and cancel the edit operation. This works in some cases. Specifically, it works if I click on anything that is capable of text entry. Other portions of the window don't work as expected. I'll click on a blank portion of the application window, and the QLineEdit retains its focus. How can I remove its focus in these cases?
I've found a solution that seems to work, though I'm still open to other options if there are any. I'm using PyQt4, so my example is in python:
Create a subclass of QLineEdit just so I have a new type. I don't want or need this behavior on all QLineEdit instances; just these specific ones.
class MyLineEdit(QtGui.QLineEdit):
pass
Now, in my QMainWindow subclass, I override the mousePressEvent() implementation. It gets the currently focused widget. If that widget is of type MyLineEdit, clear the focus.
class MyMainWindow(QtGui.QMainWindow):
def ...
def mousePressEvent(self, event):
focused_widget = QtGui.QApplication.focusWidget()
if isinstance(focused_widget, MyLineEdit):
focused_widget.clearFocus()
QtGui.QMainWindow.mousePressEvent(self, event)
def ...
This gets me the behavior I'm looking for so that if the user clicks anywhere on the application's window, the focus is cleared.
Edit: I did find one caveat to this. I have a QTreeView in the main window. If the user clicks on the tree view, focus is not removed from the text edit field.
Catch the clicked() signal of your parent widget and call yourLabel->clearFocus() (that unfortunatelly happens to not be a slot, making things more complicated) there.
I followed Grant Limberg instruction here but figured out that, in my case, a simple:
QApplication.focusWidget().clearFocus()
would fix the problem.
I'm not sure if this also works in Qt4 (I'm using PyQt5) but you can change the FocusPolicy of the QMainWindow or parent widget to clear the focus in the QLineEdit. As per https://doc.qt.io/qt-5/qwidget.html#focusPolicy-prop
I've changed the policy of my QMainWindow to Qt.StrongFocus and it worked like the functionality described in the question.
If done in C++ I would do something along the lines of:
connect(myWidgets->MyLineEdit, SIGNAL(returnPressed()), this, SLOT(onLineEditDone());
void onLineEditDone()
{
myWidgets->MyLineEdit->clearFocus();
}
For this particular case I would use editingFinished() instead of returnPressed(), probably, but I would NOT use textChanged(QString).