The code creates a single QTableView. Left column is pre-populated with QLineEdits delegates. Right column is not populated with any delegates.
When the left-column's delegated QLineEdit is clicked the 'clicked' signal is blocked by the delegated item and tableView "cell" never gets selected.
For the tableView item to get selected the mousePressEvent should be able to go all the way through the delegate item to to tableView. With the exception of the row 0 all other rows of indexes are not selected. How to make it work for all model indexes?
from PyQt4.QtCore import *
from PyQt4.QtGui import *
app = QApplication([])
class LineEdit(QTextEdit):
def __init__(self, parent=None):
super(LineEdit, self).__init__(parent)
def mousePressEvent(self, event):
tableView = self.parent().parent()
tableView.mousePressEvent(event)
class Delegate(QItemDelegate):
def createEditor(self, parent, option, index):
return LineEdit(parent)
def onClick(index):
print 'tableView.onClick:', index
tableView = QTableView()
tableView.setModel(QStandardItemModel(4, 2))
tableView.clicked.connect(onClick)
tableView.setItemDelegate(Delegate())
for row in range(4):
tableView.openPersistentEditor(tableView.model().index(row, 0))
tableView.show()
app.exec_()
Solution posted by user1034749:
from PyQt4.QtCore import *
from PyQt4.QtGui import *
app = QApplication([])
class LineEdit(QTextEdit):
def __init__(self, parent=None):
super(LineEdit, self).__init__(parent)
def mouseReleaseEvent(self, event):
super(LineEdit, self).mouseReleaseEvent(event)
table = self.parent().parent() # added by me here
tableView.selectRow(0) # to fix the issue with tableView row not getting selected on delegated click.
event.ignore()
def mousePressEvent(self, event):
super(LineEdit, self).mousePressEvent(event)
event.ignore()
class Delegate(QItemDelegate):
def createEditor(self, parent, option, index):
return LineEdit(parent)
def onClick(index):
print 'tableView.onClick:', index
selectedIndexes = tableView.selectionModel().selectedRows()
tableView = QTableView()
tableView.setSelectionBehavior(QTableView.SelectRows)
tableView.setModel(QStandardItemModel(4, 2))
tableView.clicked.connect(onClick)
tableView.setItemDelegate(Delegate())
for row in range(4):
tableView.openPersistentEditor(tableView.model().index(row, 0))
tableView.show()
app.exec_()
to pass through event, you need just ignore it, like this:
def mouseReleaseEvent(self, event):
print "mouse release"
super(LineEdit, self).mouseReleaseEvent(event)
event.ignore()
def mousePressEvent(self, event):
print "mouse press"
super(LineEdit, self).mousePressEvent(event)
event.ignore()
Related
I am trying to build a hover Dialog but i am stuck at the interaction between QComboBox selection and QDialog's leaveEvent. it looks like when i try to click on combobox to select something, it triggers a leaveEvent which then hides my QDialog. Why is this happening? What can i try to ensure that the Dialog is only hidden when I move my mouse outside of the Dialog?
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import sys
class hoverDialog(QDialog):
def __init__(self, parent=None):
super().__init__()
self.setAttribute(Qt.WA_DeleteOnClose)
self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint)
self.v = QVBoxLayout()
self.combobox = QComboBox()
self.combobox.addItems(['Work-around','Permanent'])
self.textedit = QPlainTextEdit()
self.v.addWidget(self.combobox)
self.v.addWidget(self.textedit)
self.setLayout(self.v)
#self.setMouseTracking(True)
def leaveEvent(self, event):
self.hide()
return super().leaveEvent(event)
class Table(QWidget):
def __init__(self, parent=None):
super().__init__()
self.label4 = QLabel()
self.label4.setText("hover popup")
self.label4.installEventFilter(self)
self.checkbox1 = QCheckBox()
self.pushButton3 = QPushButton()
self.pushButton3.setText('Generate')
self.pushButton3.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred)
self.pushButton3.clicked.connect(self.buttonPressed)
self.hbox5 = QHBoxLayout()
self.hbox5.addWidget(self.checkbox1)
self.hbox5.addWidget(self.label4)
self.hbox5.addWidget(self.pushButton3)
self.vbox1 = QVBoxLayout()
self.vbox1.addLayout(self.hbox5)
self.setLayout(self.vbox1)
self.autoResolve = hoverDialog(self)
def eventFilter(self, obj, event):
if obj == self.label4 and event.type() == QtCore.QEvent.Enter and self.autoResolve.isHidden():
self.onHovered()
return super().eventFilter(obj, event)
def onHovered(self):
pos = QtGui.QCursor.pos()
self.autoResolve.move(pos)
self.autoResolve.show()
def buttonPressed(self):
if self.checkbox.isChecked():
print('do something..')
if __name__ == '__main__':
app = QApplication(sys.argv)
form = Table()
form.show()
app.exec_()
Looking at the sources, I believe that the origin of the problem is in Qt's behavior whenever a new popup widget is shown.
I cannot guarantee this at 100%, but in any case the simplest solution is to hide the widget only if the combo popup is not shown:
def leaveEvent(self, event):
if not self.combobox.view().isVisible():
self.hide()
Note that your approach is not really perfect: since the dialog could be shown with a geometry that is outside the current mouse position, it will not be able to hide itself until the mouse actually enters it. You should probably filter FocusOut and WindowDeactivate events also.
Assuming that I have a QPushButton named button, I successfully do the following to allow click event:
class UI(object):
def setupUi(self, Dialog):
# ...
self.button.clicked.connect(self.do_something)
def do_something(self):
# Something here
My question is: how can I call do_something() when the tab key is pressed? For instance, if I have a QlineEdit named tb_id, after entering a value and press tab key, do_something() method should be called in the same way as what clicked does above. How can I do that using pyqt5?
Thank you so much.
To get what you want there are many methods but before pointing it by observing your code I see that you have generated it with Qt Designer so that code should not be modified but create another class that uses that code so I will place the base code:
from PyQt5 import QtCore, QtWidgets
class UI(object):
def setupUi(self, Dialog):
self.button = QtWidgets.QPushButton("Press Me")
lay = QtWidgets.QVBoxLayout(Dialog)
lay.addWidget(self.button)
class Dialog(QtWidgets.QDialog, UI):
def __init__(self, parent=None):
super(Dialog, self).__init__(parent)
self.setupUi(self)
self.button.clicked.connect(self.do_something)
#QtCore.pyqtSlot()
def do_something(self):
print("do_something")
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = Dialog()
w.resize(640, 480)
w.show()
sys.exit(app.exec_())
Also, I recommend you read the difference between event and signal in the world of Qt in What are the differences between event and signal in Qt since you speak of click event but in the world of Qt one must say clicked signal.
Now if going to the point there are the following options:
Using keyPressEvent:
class Dialog(QtWidgets.QDialog, UI):
def __init__(self, parent=None):
super(Dialog, self).__init__(parent)
self.setupUi(self)
self.button.clicked.connect(self.do_something)
#QtCore.pyqtSlot()
def do_something(self):
print("do_something")
def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key_Tab:
self.do_something()
Using an event filter:
class Dialog(QtWidgets.QDialog, UI):
def __init__(self, parent=None):
super(Dialog, self).__init__(parent)
self.setupUi(self)
self.button.clicked.connect(self.do_something)
#QtCore.pyqtSlot()
def do_something(self):
print("do_something")
def eventFilter(self, obj, event):
if obj is self and event.type() == QtCore.QEvent.KeyPress:
if event.key() == QtCore.Qt.Key_Tab:
self.do_something()
return super(Dialog, self).eventFilter(obj, event)
Using activated signal of QShorcut:
class Dialog(QtWidgets.QDialog, UI):
def __init__(self, parent=None):
super(Dialog, self).__init__(parent)
self.setupUi(self)
self.button.clicked.connect(self.do_something)
shortcut = QtWidgets.QShortcut(QtGui.QKeySequence(QtCore.Qt.Key_Tab), self)
shortcut.activated.connect(self.do_something)
#QtCore.pyqtSlot()
def do_something(self):
print("do_something")
From the previous methods I prefer the latter because you do not need to overwrite anything and you can connect to several functions.
On the other hand, only events are detected when the focus is in the Qt window.
I assume you put your widget in QDialog widget, so if you want to implement your own key press event, you should override the keyPressEvent of your Dialog widget,
then it can behave as you like.
Here's an example,
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QDialog
class UI(QDialog):
def __init__(self):
super(UI, self).__init__()
# ...
self.button.clicked.connect(self.do_something)
def do_something(self):
# Something here
def keyPressEvent(self, event):
# when press key is Tab call your function
if event.key() == Qt.Key_Tab:
self.do_something()
I think the title is fairly self explanatory. I'm working to create a small standalone app that requires the user to drag and drop audio files onto buttons to in turn associate the file with a corresponding button on a piece of hardware by using the filepath, etc...
I've followed a ton of drag and drop tutorials for widgets, and my friend has for lists, however I'm beginning to believe that it can't be done for a button? I'm aware that you can drag and drop text onto a button. I am not fully up to speed with Qt yet so there may just be a glaring error that I'm missing.
Here is the code, many thanks!
import sys
from PyQt4 import QtGui, QtCore
class Button(QtGui.QPushButton):
def __init__(self, parent):
super(Button, self).__init__(parent)
self.setAcceptDrops(True)
self.setDragDropMode(QAbstractItemView.InternalMove)
def dragEnterEvent(self, event):
if event.mimeData().hasUrls():
event.acceptProposedAction()
else:
super(Button, self).dragEnterEvent(event)
def dragMoveEvent(self, event):
super(Button, self).dragMoveEvent(event)
def dropEvent(self, event):
if event.mimeData().hasUrls():
for url in event.mimeData().urls():
path = self.addItem(url.path())
print path
event.acceptProposedAction()
else:
super(Button,self).dropEvent(event)
class MyWindow(QtGui.QWidget):
def __init__(self):
super(MyWindow,self).__init__()
self.setGeometry(100,100,300,400)
self.setWindowTitle("Filenames")
self.btn = QtGui.QPushButton()
self.btn.setGeometry(QtCore.QRect(90, 90, 61, 51))
self.btn.setText("Change Me!")
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.btn)
self.setLayout(layout)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = MyWindow()
window.show()
sys.exit(app.exec_())
There are three problems with your posted code, the main being that you aren't even using the custom Button class that you made. You are adding just a regular button to your window with:
self.btn = QtGui.QPushButton()
instead of:
self.btn = Button(self)
Also, QPushButtons don't have a setDragDropMode() method, so you'll need to comment that line out. I'm not sure what it does anyway.
Also, QPushButton doesn't have an addItem() method so I'm not sure what that's about unless you were planning on implementing it. I replaced it below with just printing the file path.
Here is a working version of your code, that just prints the file path of any file dragged into the button:
import sys
from PyQt4 import QtGui, QtCore
class Button(QtGui.QPushButton):
def __init__(self, parent):
super(Button, self).__init__(parent)
self.setAcceptDrops(True)
#self.setDragDropMode(QAbstractItemView.InternalMove)
def dragEnterEvent(self, event):
if event.mimeData().hasUrls():
event.acceptProposedAction()
else:
super(Button, self).dragEnterEvent(event)
def dragMoveEvent(self, event):
super(Button, self).dragMoveEvent(event)
def dropEvent(self, event):
if event.mimeData().hasUrls():
for url in event.mimeData().urls():
print str(url.toLocalFile())
event.acceptProposedAction()
else:
super(Button,self).dropEvent(event)
class MyWindow(QtGui.QWidget):
def __init__(self):
super(MyWindow,self).__init__()
self.setGeometry(100,100,300,400)
self.setWindowTitle("Filenames")
self.btn = Button(self)
self.btn.setGeometry(QtCore.QRect(90, 90, 61, 51))
self.btn.setText("Change Me!")
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.btn)
self.setLayout(layout)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
window = MyWindow()
window.show()
sys.exit(app.exec_())
I need to enable a button in my app whenever something is dropped to my custom QTreeWidget.
I sub-classed QTreeWidget to implement drag and drop of custom data. But I'm not able to find a way to get notified when something is dropped into my custom QTreeWidget. I couldn't find a QTreeWidget signal to do this. Of course, the QTreeWidget's dropEvent() will be called each time something is dropped but that doesn't help much to achieve what I'm trying to do.
Here's where I instantiate the custom QTreeWidget that accepts drops from another widget,
from PyQt4 import QtCore, QtGui
import MyTreeWidget
class TestWindow(QtGui.QDialog):
def __init__(self, parent=None):
super(TestWindow, self).__init__(parent)
self.myTreeWidget = MyTreeWidget.MyTreeWidget()
...
#self.myTreeWidget.onItemDropped.connect(self.doSomethingOnItemDropped) <== I am looking for something like this
def doSomethingOnItemDropped(self):
# Enable certain button
...
And then, here is how I sub-classed QTreeWidget,
import sys
from PyQt4 import QtGui, QtCore
class MyTreeWidget(QtGui.QTreeWidget):
def __init__(self, parent=None):
super(MyTreeWidget, self).__init__(parent)
self.setAcceptDrops(True)
def dropEvent(self, event):
if (event.mimeData().hasFormat('application/x-icon-and-text')):
event.acceptProposedAction()
data = event.mimeData().data("application/x-icon-and-text")
stream= QtCore.QDataStream(data, QtCore.QIODevice.ReadOnly)
text = QtCore.QString()
icon = QtGui.QIcon()
stream >> text >> icon
item = QtGui.QTreeWidgetItem(self)
item.setText(0, text)
item.setIcon(0, icon)
self.addTopLevelItem(item)
else:
event.ignore()
def dragEnterEvent(self, event):
if (event.mimeData().hasFormat('application/x-icon-and-text')):
event.accept()
else:
event.ignore()
def dragMoveEvent(self, event):
if event.mimeData().hasFormat("application/x-icon-and-text"):
event.setDropAction(QtCore.Qt.CopyAction)
event.accept()
else:
event.ignore()
Any ideas? Thanks!
UPDATE : This is what worked for me
Based on comment by #ekhumoro I defined a custom signal itemDropped for my custom QTreeWidget which emits in dropEvent() event handler.
import sys
from PyQt4 import QtGui, QtCore
class MyTreeWidget(QtGui.QTreeWidget):
itemDropped = QtCore.pyqtSignal()
def __init__(self, parent=None):
super(MyTreeWidget, self).__init__(parent)
self.setAcceptDrops(True)
def dropEvent(self, event):
if (event.mimeData().hasFormat('application/x-icon-and-text')):
event.acceptProposedAction()
data = event.mimeData().data("application/x-icon-and-text")
stream= QtCore.QDataStream(data, QtCore.QIODevice.ReadOnly)
text = QtCore.QString()
icon = QtGui.QIcon()
stream >> text >> icon
item = QtGui.QTreeWidgetItem(self)
item.setText(0, text)
item.setIcon(0, icon)
self.addTopLevelItem(item)
self.itemDropped.emit()
else:
event.ignore()
And in my app,
from PyQt4 import QtCore, QtGui
import MyTreeWidget
class TestWindow(QtGui.QDialog):
def __init__(self, parent=None):
super(TestWindow, self).__init__(parent)
self.myTreeWidget = MyTreeWidget.MyTreeWidget()
self.myTreeWidget.itemDropped.connect(self.doSomethingOnItemDropped)
...
def doSomethingOnItemDropped(self):
# Enable certain button
...
You could define a custom signal and emit it from dropEvent:
class MyTreeWidget(QtGui.QTreeWidget):
itemDropped = QtCore.pyqtSignal()
def dropEvent(self, event):
if (event.mimeData().hasFormat('application/x-icon-and-text')):
...
self.itemDropped.emit()
In PySide2 you can use this:
class MyTreeWidget(QtGui.QTreeWidget):
itemDropped = QtCore.Signal()
def dropEvent(self, event):
if (event.mimeData().hasFormat('application/x-icon-and-text')):
self.itemDropped.emit()
The goal is to be able to drag-and-drop ListWidget items onto the comboBox. The dropped items should be added to the combobox. Ideally we want to avoid any tricks with reading listWidget's .currentItem() or .selectedItems() and etc... Ideas?
from PyQt4 import QtGui, QtCore
import sys, os
class MyClass(object):
def __init__(self):
super(MyClass, self).__init__()
self.name=None
def setName(self, arg):
self.name=arg
def getName(self):
return self.name
class DropableComboBox(QtGui.QComboBox):
def __init__(self):
self.model_mime_type = 'application/x-qabstractitemmodeldatalist'
super(DropableComboBox, self).__init__()
self.setAcceptDrops(True)
def dragEnterEvent(self, event):
if event.mimeData().hasFormat(self.model_mime_type) or event.mimeData().hasFormat('text/plain'):
event.accept()
else:
event.ignore()
def dropEvent(self, event):
if event.mimeData().hasUrls():
event.setDropAction(QtCore.Qt.CopyAction)
event.accept()
links = []
for url in event.mimeData().urls():
links.append(str(url.toLocalFile()))
self.emit(QtCore.SIGNAL("dropped"), links)
else:
super(DropableComboBox, self).dropEvent(event)
self.emit(QtCore.SIGNAL("dropped"))
class Dialog_01(QtGui.QMainWindow):
def __init__(self):
super(QtGui.QMainWindow,self).__init__()
myQWidget = QtGui.QWidget()
myBoxLayout = QtGui.QVBoxLayout()
myQWidget.setLayout(myBoxLayout)
self.setCentralWidget(myQWidget)
self.listWidget = QtGui.QListWidget()
self.listWidget.setDragDropMode(QtGui.QAbstractItemView.DragDrop)
self.listWidget.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
self.listWidget.currentItemChanged.connect(self.item_clicked)
for i in range(3):
my_item=QtGui.QListWidgetItem()
name='ListWidget Item '+str(i)
my_item.setText(name)
self.listWidget.addItem(my_item)
myObject=MyClass()
myObject.setName(name)
my_item.setData(QtCore.Qt.UserRole, myObject)
myBoxLayout.addWidget(self.listWidget)
self.ComboBox = DropableComboBox()
for i in range(3):
self.ComboBox.addItem("Combobox Item " + str(i))
self.ComboBox.currentIndexChanged.connect(self.combobox_selected)
self.connect(self.ComboBox, QtCore.SIGNAL("dropped"), self.droppedOnCombobox)
myBoxLayout.addWidget(self.ComboBox)
def item_clicked(self, arg=None):
print arg.data(QtCore.Qt.UserRole).toPyObject().getName()
def combobox_selected(self, index):
myObject=self.ComboBox.itemData(index).toPyObject()
if hasattr(myObject, 'getName'): print myObject.getName()
def droppedOnCombobox(self):
print "Drop!"
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
dialog_1 = Dialog_01()
dialog_1.show()
dialog_1.resize(480,320)
sys.exit(app.exec_())
The "proper" way to do this would be to unpack the mimedata using a QDataStream. However, this would seem to require the use of a QMap, which is not available in PyQt. So instead, it can be done in a slightly hacky (or should that be "tricky"?) way by getting a proxy model to the dirty work for us:
class DropableComboBox(QtGui.QComboBox):
def __init__(self):
super(DropableComboBox, self).__init__()
self.model_mime_type = 'application/x-qabstractitemmodeldatalist'
self.setAcceptDrops(True)
self._proxymodel = QtGui.QStandardItemModel(self)
def dropEvent(self, event):
if event.mimeData().hasUrls():
event.setDropAction(QtCore.Qt.CopyAction)
event.accept()
links = []
for url in event.mimeData().urls():
links.append(str(url.toLocalFile()))
self.emit(QtCore.SIGNAL("dropped"), links)
elif event.mimeData().hasFormat(self.model_mime_type):
self._proxymodel.setRowCount(0)
self._proxymodel.dropMimeData(
event.mimeData(), QtCore.Qt.CopyAction,
0, 0, QtCore.QModelIndex())
for index in range(self._proxymodel.rowCount()):
item = self._proxymodel.item(index, 0)
self.addItem(item.text())
# no point calling the base-class dropEvent here,
# because it's a NO-OP in QComboBox
self.emit(QtCore.SIGNAL("dropped"))
NB:
This will copy the items from the list-widget, rather than moving them (which you didn't ask for). Also, if you want to prevent duplicates being added, use setDuplicatesEnabled. And if you want to alter how the items are added, use setInsertPolicy.