I have some trouble to distinguish between a single and a double mouse click event. I have created an event filter but a mouse double click also gives me a single signal back. In my code I have to separate both events to connect to different functions. Can anybody suggest me how to do this?
Here is an example. What I want is, if a double mouse click happen, only the MouseButtonDblClick should give a signal and not the LeftButton and MouseButtonDblClick:
# coding: utf-8
import sys
from PyQt4 import QtCore, QtGui
class MyDialog(QtGui.QDialog):
def __init__(self, parent=None):
super(MyDialog, self).__init__(parent)
self.button1 = QtGui.QPushButton("Button 1")
self.button2 = QtGui.QPushButton("Button 2")
hbox = QtGui.QHBoxLayout()
hbox.addWidget(self.button1)
hbox.addWidget(self.button2)
self.setLayout(hbox)
self.button1.installEventFilter(self)
def eventFilter(self, obj, event):
if event.type() == QtCore.QEvent.MouseButtonPress:
if event.button() == QtCore.Qt.LeftButton:
#If image is left clicked, display a red bar.
print 'one left'
elif event.button() == QtCore.Qt.RightButton:
print 'one right'
elif event.type() == QtCore.QEvent.MouseButtonDblClick:
#If image is double clicked, remove bar.
print 'two'
return super(MyDialog, self).eventFilter(obj, event)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
w = MyDialog()
w.show()
sys.exit(app.exec_())
Thank you in advance!
Stefanie
It's kind of a hack, but it should do the trick.
Also, I used new-style signals instead of your event filter, something you should consider.
Here, the ClickHandler class counts the number of clicks between the first click and the timeout event of its timer.
from PyQt4 import QtCore, QtGui
class ClickHandler():
def __init__(self, time):
self.timer = QtCore.QTimer()
self.timer.setInterval(time)
self.timer.setSingleShot(True)
self.timer.timeout.connect(self.timeout)
self.click_count = 0
def timeout(self):
if self.click_count == 1:
print('Single click')
elif self.click_count > 1:
print('Double click')
self.click_count = 0
def __call__(self):
self.click_count += 1
if not self.timer.isActive():
self.timer.start()
class MyDialog(QtGui.QDialog):
def __init__(self, parent=None):
super(MyDialog, self).__init__(parent)
self.button1 = QtGui.QPushButton("Button 1")
hbox = QtGui.QHBoxLayout()
hbox.addWidget(self.button1)
self.setLayout(hbox)
self.click_handler = ClickHandler(300)
self.button1.clicked.connect(self.click_handler)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
w = MyDialog()
w.show()
sys.exit(app.exec_())
EDIT : A second cleaner version with a CustomButton class that handles left and right click signals:
from PyQt4 import QtCore, QtGui
class CustomButton(QtGui.QPushButton):
left_clicked= QtCore.pyqtSignal(int)
right_clicked = QtCore.pyqtSignal(int)
def __init__(self, *args, **kwargs):
QtGui.QPushButton.__init__(self, *args, **kwargs)
self.timer = QtCore.QTimer()
self.timer.setInterval(250)
self.timer.setSingleShot(True)
self.timer.timeout.connect(self.timeout)
self.left_click_count = self.right_click_count = 0
def mousePressEvent(self, event):
if event.button() == QtCore.Qt.LeftButton:
self.left_click_count += 1
if not self.timer.isActive():
self.timer.start()
if event.button() == QtCore.Qt.RightButton:
self.right_click_count += 1
if not self.timer.isActive():
self.timer.start()
def timeout(self):
if self.left_click_count >= self.right_click_count:
self.left_clicked.emit(self.left_click_count)
else:
self.right_clicked.emit(self.right_click_count)
self.left_click_count = self.right_click_count = 0
class MyDialog(QtGui.QDialog):
def __init__(self, parent=None):
super(MyDialog, self).__init__(parent)
self.button1 = CustomButton("Button 1")
hbox = QtGui.QHBoxLayout()
hbox.addWidget(self.button1)
self.setLayout(hbox)
self.button1.left_clicked[int].connect(self.left_click)
self.button1.right_clicked[int].connect(self.right_click)
def left_click(self, nb):
if nb == 1: print('Single left click')
else: print('Double left click')
def right_click(self, nb):
if nb == 1: print('Single right click')
else: print('Double right click')
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
w = MyDialog()
w.show()
sys.exit(app.exec_())
You should have a look at this thread on Double-Click-Capturing.
A Timer might do the job. However, it is probably a bad idea to have unrelated single clicks and double clicks (see Bill's answer to "Distinguish between single and double click events in Qt").
Related
I want to move color data from button to button.
Is there any other way to subclass pushbutton with eventfilter?
The event.pos() value was different when eventfilter was installEventFilter on pushbutton.
from PySide2 import QtWidgets, QtCore, QtGui
from functools import partial
class DragTest(QtWidgets.QMainWindow):
def __init__(self):
super(DragTest, self).__init__()
cent = QtWidgets.QWidget()
self.setCentralWidget(cent)
layout = QtWidgets.QHBoxLayout(cent)
self.color1_btn = QtWidgets.QPushButton()
self.color1_btn.clicked.connect(partial(self.color_btn_click, widget=self.color1_btn))
self.color2_btn = QtWidgets.QPushButton()
self.color2_btn.clicked.connect(partial(self.color_btn_click, widget=self.color2_btn))
layout.addWidget(self.color1_btn)
layout.addWidget(self.color2_btn)
self.btn1 = QtWidgets.QPushButton()
self.btn2 = QtWidgets.QPushButton()
layout.addWidget(self.btn1)
layout.addWidget(self.btn2)
def color_btn_click(self, widget):
color = QtWidgets.QColorDialog.getColor()
if color.isValid():
print "red: {0}, green: {1}, blue: {2}".format(*color.getRgb())
widget.setStyleSheet("background-color:rgb({0},{1},{2})".format(*color.getRgb()))
widget.setProperty("color", color.getRgb())
def eventFilter(self, obj, event):
super(DragTest, self).eventFilter(obj, event)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
win = DragTest()
win.show()
sys.exit(app.exec_())
You can set up the drag operation in eventFilter if that's your preference. Make sure acceptDrops is True for the buttons, and then catch the mouse move and drag events in the event filter. QMimeData has a colorData property to store the QColor object.
class DragTest(QtWidgets.QMainWindow):
def __init__(self):
super(DragTest, self).__init__()
cent = QtWidgets.QWidget()
self.setCentralWidget(cent)
layout = QtWidgets.QHBoxLayout(cent)
self.color1_btn = QtWidgets.QPushButton(acceptDrops=True)
self.color1_btn.clicked.connect(partial(self.color_btn_click, widget=self.color1_btn))
self.color2_btn = QtWidgets.QPushButton(acceptDrops=True)
self.color2_btn.clicked.connect(partial(self.color_btn_click, widget=self.color2_btn))
layout.addWidget(self.color1_btn)
layout.addWidget(self.color2_btn)
self.color1_btn.installEventFilter(self)
self.color2_btn.installEventFilter(self)
self.color1_btn.color = self.color2_btn.color = None
self.btn1 = QtWidgets.QPushButton()
self.btn2 = QtWidgets.QPushButton()
layout.addWidget(self.btn1)
layout.addWidget(self.btn2)
def color_btn_click(self, widget):
color = QtWidgets.QColorDialog.getColor()
if color.isValid():
self.set_color(widget, color)
def set_color(self, widget, color):
widget.setStyleSheet("background-color:rgb({0},{1},{2})".format(*color.getRgb()))
widget.color = color
def eventFilter(self, obj, event):
if obj in {self.color1_btn, self.color2_btn}:
if event.type() == QtCore.QEvent.MouseMove and obj.color:
mimedata = QtCore.QMimeData()
mimedata.setColorData(obj.color)
pixmap = QtGui.QPixmap(20, 20)
pixmap.fill(QtCore.Qt.transparent)
painter = QtGui.QPainter(pixmap)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
painter.setBrush(obj.color)
painter.setPen(QtGui.QPen(obj.color.darker(150), 2))
painter.drawEllipse(pixmap.rect().center(), 8, 8)
painter.end()
drag = QtGui.QDrag(obj)
drag.setMimeData(mimedata)
drag.setPixmap(pixmap)
drag.setHotSpot(pixmap.rect().center())
drag.exec_(QtCore.Qt.CopyAction)
elif event.type() == QtCore.QEvent.DragEnter:
event.accept() if event.mimeData().hasColor() else event.ignore()
elif event.type() == QtCore.QEvent.Drop:
self.set_color(obj, event.mimeData().colorData())
event.accept()
return super(DragTest, self).eventFilter(obj, event)
I want to execute other command on click and double click. When doubleclick is executed, click does not want to be executed.
from PySide2 import QtWidgets, QtCore, QtGui
class TestView(QtWidgets.QTableView):
custom_clicked = QtCore.Signal(QtCore.QModelIndex)
custom_double_clicked = QtCore.Signal(QtCore.QModelIndex)
def __init__(self, parent=None):
super(TestView, self).__init__(parent=parent)
self.timer = QtCore.QTimer(self)
self.timer.setInterval(250)
self.timer.setSingleShot(True)
self.timer.timeout.connect(self.timeout)
self.click_number = 0
def mousePressEvent(self, event):
super(TestView, self).mousePressEvent(event)
if event.button() == QtCore.Qt.LeftButton:
self.index = self.indexAt(event.pos())
self.click_number += 1
if not self.timer.isActive():
self.timer.start()
def timeout(self):
if self.click_number < 2:
self.custom_clicked.emit(self.index)
elif self.click_number > 1:
self.custom_double_clicked.emit(self.index)
self.click_number = 0
def click_command(index):
print "single", index
def double_click_command(index):
print "double", index
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
tableView = TestView()
model = QtGui.QStandardItemModel()
item = QtGui.QStandardItem()
item.setData("test", QtCore.Qt.DisplayRole)
model.setItem(0, item)
tableView.setModel(model)
tableView.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
tableView.custom_clicked[QtCore.QModelIndex].connect(click_command)
tableView.custom_double_clicked[QtCore.QModelIndex].connect(double_click_command)
tableView.show()
sys.exit(app.exec_())
This code works well when there is no item.
However, it will break if you double-click the item.
Timer seems to be the cause, but I'm looking for a reason.
To detect the double click you should only use the mouseDoubleClickEvent so you only have to detect that it was not pressed a second time in a certain time:
class TestView(QtWidgets.QTableView):
custom_clicked = QtCore.Signal(QtCore.QModelIndex)
custom_double_clicked = QtCore.Signal(QtCore.QModelIndex)
def __init__(self, parent=None):
super(TestView, self).__init__(parent)
self.timer = QtCore.QTimer(self)
self.timer.timeout.connect(self.timeout)
self.timer.setInterval(
QtGui.QGuiApplication.styleHints().mouseDoubleClickInterval()
)
self.timer.setSingleShot(True)
def mousePressEvent(self, event):
super(TestView, self).mousePressEvent(event)
if event.button() == QtCore.Qt.LeftButton:
self.index = self.indexAt(event.pos())
self.timer.start()
def mouseDoubleClickEvent(self, event):
super(TestView, self).mouseDoubleClickEvent(event)
if event.button() == QtCore.Qt.LeftButton:
index = self.indexAt(event.pos())
self.custom_double_clicked.emit(index)
self.timer.stop()
def timeout(self):
self.custom_clicked.emit(self.index)
I have made a splitter. I have been trying to bring a right-click menu when the splitter handle is right-clicked.
Here is the code I have created. Currently, it doesn't recognise the right clicks on the handle of the splitter. (note: this code currently updates count if right-clicked in the first frame of the splitter i,e Top left frame)
from PyQt5.QtWidgets import *
from PyQt5 import QtGui
from PyQt5.QtCore import Qt
# This class is to create the outer window
class OuterLayout(QMainWindow):
def __init__(self):
super().__init__()
self.window()
def window(self):
self.setMinimumSize(1000, 900)
self.showMaximized()
self.setWindowIcon(QtGui.QIcon('Images/Logo_small.png'))
self.setWindowTitle('Splitter')
self.menu_bar()
inner_layout = SplitterLayout()
layout = inner_layout.add_layout()
self.setCentralWidget(layout)
def menu_bar(self):
menu_bar = self.menuBar()
file_menu = menu_bar.addMenu('File')
self.file(file_menu)
edit_menu = menu_bar.addMenu('Edit')
self.edit(edit_menu)
def file(self, file):
new = QAction('New', self)
file.addAction(new)
def edit(self, edit):
pass
# This class creates the splitter window
class SplitterLayout(QWidget):
def __init__(self):
super(QWidget, self).__init__()
self.count = 0
self.splitter_handle_width = 3 # This is to set the width of the handle
# This is a method to add a new splitter window
def add_layout(self):
left = QFrame()
left.setFrameShape(QFrame.StyledPanel)
bottom = QFrame()
bottom.setFrameShape(QFrame.StyledPanel)
splitter1 = QSplitter(Qt.Horizontal)
splitter1.setHandleWidth(self.splitter_handle_width)
lineedit = QLineEdit()
lineedit.setStyleSheet('background-color:green')
splitter1.addWidget(left)
splitter1.addWidget(lineedit)
splitter1.setSizes([200, 200])
print(splitter1.handle(3))
splitter1.mousePressEvent = self.splitter_clicked
splitter2 = QSplitter(Qt.Vertical)
splitter2.setHandleWidth(self.splitter_handle_width)
splitter2.addWidget(splitter1)
splitter2.addWidget(bottom)
return splitter2
def splitter_clicked(self, event):
self.count += 1
print('splitter_double clicked' + str(self.count))
# def mousePressEvent(self, event):
# if event.button == Qt.RightButton:
# print('Right mouse clicked')
#
# elif event.button == Qt.LeftButton:
# print('Left mouse clicked')
def main():
splitter = QApplication([])
outer_layout = OuterLayout()
outer_layout.show()
splitter.exec_()
if __name__ == '__main__':
main()
The trick here is to create a custom QSplitterHandle class and override QSplitterHandle.mousePressEvent and a custom QSplitter class where you override createHandle so that it returns the custom QSplitterHandle instead of the standard one, i.e.
class MySplitter(QSplitter):
def createHandle(self):
return MySplitterHandle(self.orientation(), self)
class MySplitterHandle(QSplitterHandle):
def mousePressEvent(self, event):
if event.button() == Qt.RightButton:
print('Right mouse clicked')
elif event.button() == Qt.LeftButton:
print('Left mouse clicked')
super().mousePressEvent(event)
Finally, to use the custom QSplitter you need to replace all occurrences of QSplitter with MySplitter in SplitterLayout.
I hava a PyQt5 GUI app having 2 buttons("read" and "write"). I want to print a word ("read" or"write") 1000000 times when a button is focused. But with the default focus the focus event triggers for first button ("read" in this case) and print "read" 1000000 times before the contents of the GUI render. Can I make the app to trigger the focusIn() event after the contents of the GUI rendered?
from PyQt5 import QtCore, QtWidgets
import sys
class Start_Gui(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Start_Gui, self).__init__(parent)
self.setGeometry(200, 100, 500, 250)
self.setWindowTitle("hi")
self.button1 = QtWidgets.QPushButton("read", self)
self.button2 = QtWidgets.QPushButton("write", self)
self.button1.move(100, 50)
self.button2.move(100, 100)
for button in (self.button1, self.button2):
button.installEventFilter(self)
def eventFilter(self, obj, event):
if event.type() == QtCore.QEvent.FocusIn:
if self.button1 is obj:
self.instruct('read')
elif self.button2 is obj:
self.instruct('write')
return super(Start_Gui, self).eventFilter(obj, event)
def instruct(self, type):
if type == 'read':
for i in range(1000000):
print("read")
if type == 'write':
for i in range(1000000):
print("write")
gui_app = QtWidgets.QApplication(sys.argv)
gui = Start_Gui()
gui.show()
gui_app.exec_()
Can anyone help?
You need to start new threads, I don't know your exact requirements but here is an example:
from PyQt5.QtCore import QObject, pyqtSignal
from PyQt5 import QtCore, QtWidgets
import sys
class Start_Gui(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Start_Gui, self).__init__(parent)
self.setGeometry(200, 100, 500, 250)
self.setWindowTitle("hi")
self.button1 = QtWidgets.QPushButton("read", self)
self.button2 = QtWidgets.QPushButton("write", self)
self.button1.move(100, 50)
self.button2.move(100, 100)
self.readWorkerThread = None
self.writeWorkerThread = None
self.readWorker = None
self.writeWorker = None
for button in (self.button1, self.button2):
button.installEventFilter(self)
def eventFilter(self, obj, event):
if event.type() == QtCore.QEvent.FocusIn:
if self.button1 is obj:
self._instructRead()
elif self.button2 is obj:
self._instructWrite()
return super().eventFilter(obj, event)
def _instructRead(self):
if self.readWorkerThread and self.readWorkerThread.isRunning():
print('read thread is already running.')
return
self.readWorker = InstructWorker('read', 100000)
self.readWorkerThread = QtCore.QThread()
self.readWorker.moveToThread(self.readWorkerThread)
self.readWorkerThread.started.connect(self.readWorker.run)
self.readWorkerThread.start()
def _instructWrite(self):
if self.writeWorkerThread and self.writeWorkerThread.isRunning():
print('write thread is already running.')
return
self.writeWorker = InstructWorker('write', 100000)
self.writeWorkerThread = QtCore.QThread()
self.writeWorker.moveToThread(self.writeWorkerThread)
self.writeWorkerThread.started.connect(self.writeWorker.run)
self.writeWorkerThread.start()
class InstructWorker(QObject):
reportProgress = pyqtSignal(int, int)
def __init__(self, instructType, instructNum):
super().__init__()
self._instructType = instructType
self._instructNum = instructNum
def run(self):
self._instruct()
def _instruct(self):
for i in range(self._instructNum):
print('{} {}'.format(self._instructType, i))
self.reportProgress.emit(i, self._instructNum)
self.thread().terminate()
gui_app = QtWidgets.QApplication(sys.argv)
gui = Start_Gui()
gui.show()
gui_app.exec_()
Hope it helps.
User defined button that will hold an image and is moveable causes screen smearing when moved to the left and causes a screen smear to the right of the widget. Any ideas?
Image of smearing to the right of the playing card - vertical grey lines.
As i stated this only happens when moving the button to the left.
#!/usr/bin/python3
# -*- coding: utf-8 -*-
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from functools import partial
import datetime,psutil,sys
class playingCard(QPushButton):
def __init__(self, Text = '', parent = None):
super(playingCard, self).__init__()
self.ftop = 10
self.fleft = 10
self.fwidth = 87
self.fheight = 124
self.initUI()
def initUI(self):
self.setGeometry(self.fleft, self.ftop, self.fwidth+2, self.fheight+2)
self.setText('')
pixmap = QPixmap('clubs1.png')
pixmap = pixmap.scaled(self.fwidth,self.fheight, Qt.KeepAspectRatio, Qt.FastTransformation)
buttonicon = QIcon(pixmap)
self.setIcon(buttonicon)
self.setIconSize( QSize(self.fwidth,self.fheight))
self.setFixedSize( QSize(self.fwidth+2,self.fheight+2))
def mousePressEvent(self, event):
self.__mousePressPos = None
self.__mouseMovePos = None
if event.button() == Qt.LeftButton:
self.__mousePressPos = event.globalPos()
self.__mouseMovePos = event.globalPos()
super(playingCard, self).mousePressEvent(event)
def mouseMoveEvent(self, event):
if event.buttons() == Qt.LeftButton:
# adjust offset from clicked point to origin of widget
currPos = self.mapToGlobal(self.pos())
globalPos = event.globalPos()
diff = globalPos - self.__mouseMovePos
newPos = self.mapFromGlobal(currPos + diff)
self.move(newPos)
self.__mouseMovePos = globalPos
super(playingCard, self).mouseMoveEvent(event)
def mouseReleaseEvent(self, event):
if self.__mousePressPos is not None:
moved = event.globalPos() - self.__mousePressPos
if moved.manhattanLength() > 3:
event.ignore()
return
super(playingCard, self).mouseReleaseEvent(event)
class Example(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def addAction(self,name,shortcut,status):
self.actions[name] = QAction(name, self)
self.actions[name].setShortcut(shortcut)
self.actions[name].setStatusTip(status)
if name == 'Exit':
self.actions[name].triggered.connect( self.close )
else:
self.actions[name].triggered.connect( partial(self.viewEvent,name) )
def hastab(self,tabname):
return self.tabWidget.findChild(QWidget, tabname) != None
def ontab(self,tabname):
currentIndex = self.tabWidget.currentIndex()
currentTitle = self.tabWidget.tabText(currentIndex)
return tabname == currentTitle
def gettab(self,tabname):
page = self.tabWidget.findChild(QWidget, tabname)
return self.tabWidget.indexOf(page)
def initUI(self):
self.actions = dict() # holds the QActions
self.tabs = dict()
self.tabWidget = QTabWidget()
self.tabWidget.setTabsClosable(True)
self.tabWidget.tabCloseRequested.connect(self.closeTab)
self.setCentralWidget(self.tabWidget)
self.addAction('Exit', 'Ctrl+Q','Exit application')
self.addAction('Game','Ctrl+G','Game')
self.statusBar()
menubar = self.menuBar()
fileMenu = menubar.addMenu('&File')
fileMenu.addAction( self.actions['Game'] )
fileMenu.addAction( self.actions['Exit'] )
self.setWindowTitle('Main window')
self.showMaximized()
def closeTab (self, currentIndex):
currentQWidget = self.tabWidget.widget(currentIndex)
title=self.tabWidget.tabText(currentIndex)
currentQWidget.deleteLater()
self.tabWidget.removeTab(currentIndex)
del self.tabs[title]
del self.tables[title]
self.timers[title].stop()
del self.timers[title]
def keyPressEvent(self, e):
currentIndex=self.tabWidget.currentIndex()
title = None
if currentIndex != -1:
title=self.tabWidget.tabText(currentIndex)
if e.key() == Qt.Key_F11:
if self.isMaximized():
self.showNormal()
else:
self.showMaximized()
def viewEvent(self, name):
if name in self.tabs:
return
self.tabs[name] = QWidget()
vbox = QVBoxLayout()
vbox.addWidget( playingCard() )
# Add box layout, add table to box layout and add box layout to widget
self.tabs[name].layout = vbox
self.tabs[name].setLayout(self.tabs[name].layout)
self.tabWidget.addTab(self.tabs[name],name)
def closeEvent(self, event):
reply = QMessageBox.question(self, 'Message',
"Are you sure to quit?", QMessageBox.Yes |
QMessageBox.No, QMessageBox.No)
if reply == QMessageBox.Yes:
event.accept()
else:
event.ignore()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())