Related
I have implemented QUndoStack with QGraphicScene and everything is working like charm with individual QGraphicItem movement/rotation, but when it comes to the multi-items movement, I couldn't find any way to do so, since I didn't find any flag/signal that will be emitted after all selected item position has been changed [ NOT DURING].
Here is a sketch code that will represent my goals.
import sys
from typing import Dict
from PyQt5.QtCore import Qt, QPoint
from PyQt5.QtGui import QPen, QColor, QBrush
from PyQt5.QtWidgets import QApplication, QGraphicsItem, QMainWindow, QGraphicsScene, QGraphicsView, \
QGraphicsEllipseItem, QUndoStack, QUndoCommand
import random
class CustomUndoRedoStack(QUndoStack):
instance = None
def __init__(self):
if not CustomUndoRedoStack.instance:
CustomUndoRedoStack.instance = self
super(CustomUndoRedoStack, self).__init__()
#staticmethod
def get_instance():
return CustomUndoRedoStack.instance
class MovingMultiItemsCommand(QUndoCommand):
def __init__(self, new_items_pos: Dict[QGraphicsItem], old_items_pos: Dict[QGraphicsItem]):
super(MovingMultiItemsCommand, self).__init__()
self.new_items_pos = new_items_pos
self.old_items_pos = old_items_pos
CustomUndoRedoStack.get_instance().push(self)
def undo(self) -> None:
for item in self.old_items_pos:
item.setPos(self.old_items_pos[item])
super(MovingMultiItemsCommand, self).undo()
def redo(self) -> None:
for item in self.new_items_pos:
item.setPos(self.new_items_pos[item])
super(MovingMultiItemsCommand, self).redo()
class CustomQGraphicsEllipseItem(QGraphicsEllipseItem):
def __init__(self, x, y):
super(CustomQGraphicsEllipseItem, self).__init__(x, y, 20, 20)
self.setFlag(QGraphicsItem.ItemIsSelectable)
self.setFlag(QGraphicsItem.ItemIsMovable)
self.setPen(QPen(QColor(255, 128, 0), 0.5, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
self.setBrush(QBrush(QColor(255, 128, 20, 128)))
class MyGraphicsView(QGraphicsView):
def __init__(self):
super(MyGraphicsView, self).__init__()
self.setDragMode(QGraphicsView.RubberBandDrag)
self._isPanning = False
self._mousePressed = False
self.setCacheMode(QGraphicsView.CacheBackground)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
class MyGraphicsScene(QGraphicsScene):
def __init__(self):
super(MyGraphicsScene, self).__init__()
self.setBackgroundBrush(QBrush(QColor(50, 50, 50)))
self.old_items_pos = {}
self.new_items_pos = {}
# ------- I want something like the following -----------------------------
def selected_items_before_change(self):
for item in self.selectedItems():
self.old_items_pos[item] = item.pos()
def selected_items_after_change(self):
for item in self.selectedItems():
self.new_items_pos[item] = item.pos()
def selected_item_position_changed(self):
"""
This method must be triggered only and only once after selected items position has been changed (NOT WHILE)
"""
MovingMultiItemsCommand(self.new_items_pos, self.old_items_pos)
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
class MyMainWindow(QMainWindow):
def __init__(self):
super(MyMainWindow, self).__init__()
self.setWindowTitle("Test")
self.resize(800, 600)
self.gv = MyGraphicsView()
self.gv.setScene(MyGraphicsScene())
self.setCentralWidget(self.gv)
self.populate()
def populate(self):
scene = self.gv.scene()
for i in range(500):
x = random.randint(0, 1000)
y = random.randint(0, 1000)
rect = CustomQGraphicsEllipseItem(x, y)
scene.addItem(rect)
def main():
app = QApplication(sys.argv)
ex = MyMainWindow()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
C++ answers will be good as well, but Python is preferable :)
The mouse movement of selectable items is handled by the graphics scene, starting with its mousePressEvent() and ending with the corresponding mouseReleaseEvent().
This means that you can create a QUndoCommand based on the selected items (if any) after the default implementation of mouse button press/release is called. This will be valid for any movement, including when just one item has been moved.
class ItemMovedUndoCommand(QUndoCommand):
def __init__(self, oldPositions, newPositions):
if len(oldPositions) == 1:
pos = oldPositions.values()[0]
text = 'Item moved to {}x{}'.format(
pos.x(), pos.y())
else:
text = '{} items moved'.format(len(oldPositions))
super().__init__(text)
self.oldPositions = oldPositions
self.newPositions = newPositions
def redo(self):
for item, pos in self.newPositions.items():
item.setPos(pos)
def undo(self):
for item, pos in self.oldPositions.items():
item.setPos(pos)
class CustomQGraphicsEllipseItem(QGraphicsEllipseItem):
def __init__(self, x, y):
super(CustomQGraphicsEllipseItem, self).__init__(-10, -10, 20, 20)
self.setPos(x, y)
self.setFlag(QGraphicsItem.ItemIsSelectable)
self.setFlag(QGraphicsItem.ItemIsMovable)
self.setPen(QPen(QColor(255, 128, 0),
0.5, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin))
self.setBrush(QBrush(QColor(255, 128, 20, 128)))
class MyGraphicsView(QGraphicsView):
def __init__(self):
super(MyGraphicsView, self).__init__()
self.setDragMode(QGraphicsView.RubberBandDrag)
self._isPanning = False
self._mousePressed = False
self.setCacheMode(QGraphicsView.CacheBackground)
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
class MyGraphicsScene(QGraphicsScene):
itemsMoved = pyqtSignal(object, object)
def __init__(self):
super(MyGraphicsScene, self).__init__()
self.setBackgroundBrush(QBrush(QColor(50, 50, 50)))
self.oldPositions = {}
def mousePressEvent(self, event):
super().mousePressEvent(event)
if event.button() == Qt.LeftButton:
self.oldPositions = {i:i.pos() for i in self.selectedItems()}
def mouseReleaseEvent(self, event):
super().mouseReleaseEvent(event)
if event.button() == Qt.LeftButton and self.oldPositions:
self.itemsMoved.emit(self.oldPositions,
{i:i.pos() for i in self.oldPositions.keys()})
self.oldPositions = {}
class MyMainWindow(QMainWindow):
def __init__(self):
super(MyMainWindow, self).__init__()
self.setWindowTitle("Test")
self.resize(800, 600)
self.gv = MyGraphicsView()
self.scene = MyGraphicsScene()
self.gv.setScene(self.scene)
self.setCentralWidget(self.gv)
toolBar = QToolBar()
self.addToolBar(Qt.TopToolBarArea, toolBar)
self.undoStack = QUndoStack()
toolBar.addAction(self.undoStack.createUndoAction(self.scene))
toolBar.addAction(self.undoStack.createRedoAction(self.scene))
self.populate()
self.scene.itemsMoved.connect(self.itemsMoved)
def itemsMoved(self, oldPositions, newPositions):
self.undoStack.push(ItemMovedUndoCommand(oldPositions, newPositions))
def populate(self):
scene = self.gv.scene()
for i in range(500):
x = random.randint(0, 1000)
y = random.randint(0, 1000)
rect = CustomQGraphicsEllipseItem(x, y)
scene.addItem(rect)
I use PyQt5 and Python2.7.
I have four Classes. App, UIWidget, PlayStreaming and Thread.
App is parent of UIWidget.
UIWidget is parent of PlayStreaming.
PlayStreaming is parent of Thread.
I like to pass Statusbar message from Thread Class to App Class, so that I can update status.
I use self.parent().set_status_message('') from children classes subsequently to display message with self.statusBar().showMessage('') in App class.
But I have error in UIWidget as
AttributeError: 'QWidget' object has no attribute 'set_status_message'
Aborted (core dumped)
How can I update to Status bar in MainWindow from Child class?
My code is as follow.
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QApplication, QMainWindow
import cv2
import time
import face_recognition.api as face_recognition
class Thread(QtCore.QThread):
changePixmap = QtCore.pyqtSignal(QtGui.QImage)
scaled_size = QtCore.QSize(640, 480)
curScale=1.0
def run(self):
cap = cv2.VideoCapture(-1)
cap.set(3,1280);
cap.set(4,1024);
time.sleep(2)
self.maxHeight=cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
self.maxScale=self.maxHeight/480.0
while True:
ret, frame = cap.read()
if ret:
r=1
face_locations=[]
rescaleSize=int(480*self.curScale)
if(frame.shape[0] > 480 and frame.shape[1] > 640):
r = rescaleSize / float(frame.shape[0])
dim = (int(frame.shape[1] * r), rescaleSize)
face_locations = face_recognition.face_locations(cv2.resize(frame, dim, fx=0.0, fy=0.0))
else:
face_locations = face_recognition.face_locations(frame)
for face_location in face_locations:
top, right, bottom, left = face_location
cv2.rectangle(frame,(int(right/r),int(top/r)),(int(left/r),int(bottom/r)),(0,255,0),2)
rgbImage = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
convertToQtFormat = QtGui.QImage(rgbImage.data, rgbImage.shape[1], rgbImage.shape[0], QtGui.QImage.Format_RGB888)
p = convertToQtFormat.scaled(self.scaled_size, QtCore.Qt.KeepAspectRatio)
self.changePixmap.emit(p)
#QtCore.pyqtSlot(QtCore.QSize)
def scaled(self, scaled_size):
self.scaled_size = scaled_size
#QtCore.pyqtSlot()
def scaleup(self):
self.curScale = self.curScale + 0.1
if self.curScale > self.maxScale:
self.curScale = self.maxScale
self.parent().set_status_message('Cur scale:'+str(self.curScale))
#QtCore.pyqtSlot()
def scaledown(self):
self.curScale = self.curScale - 0.1
if self.curScale < 1.0:
self.curScale = 1.0
self.parent().set_status_message('Cur scale:'+str(self.curScale))
class PlayStreaming(QtWidgets.QLabel):
reSize = QtCore.pyqtSignal(QtCore.QSize)
scaleupSignal = QtCore.pyqtSignal()
scaledownSignal = QtCore.pyqtSignal()
def __init__(self):
super(PlayStreaming, self).__init__()
self.initUI()
#QtCore.pyqtSlot(QtGui.QImage)
def setImage(self, image):
self.label.setPixmap(QtGui.QPixmap.fromImage(image))
def initUI(self):
self.setWindowTitle("Image")
# create a label
self.label = QtWidgets.QLabel(self)
th = Thread(self)
th.changePixmap.connect(self.setImage)
self.scaleupSignal.connect(th.scaleup)
self.scaledownSignal.connect(th.scaledown)
self.reSize.connect(th.scaled)
th.start()
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(self.label, alignment=QtCore.Qt.AlignCenter)
def resizeEvent(self, event):
self.reSize.emit(self.size())
def set_status_message(self, message):
return self.parent().set_status_message(message)
class UIWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(UIWidget, self).__init__(parent)
# Initialize tab screen
self.tabs = QtWidgets.QTabWidget()
self.tab1 = QtWidgets.QWidget()
self.tab2 = QtWidgets.QWidget()
self.tab3 = QtWidgets.QWidget()
# Add tabs
self.tabs.addTab(self.tab1, "Face")
self.tabs.addTab(self.tab2, "Human")
self.tabs.addTab(self.tab3, "Vehicle")
self.display = PlayStreaming()
# Create first tab
self.createGridLayout()
self.tab1.layout = QtWidgets.QVBoxLayout()
self.tab1.layout.addWidget(self.display, stretch=1)
self.tab1.layout.addWidget(self.horizontalGroupBox)
self.tab1.setLayout(self.tab1.layout)
# Add tabs to widget
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.tabs)
def createGridLayout(self):
self.horizontalGroupBox = QtWidgets.QGroupBox("")
self.horizontalGroupBox.setStyleSheet("QGroupBox{ background-color: red; border: none;}")
hlay1 = QtWidgets.QHBoxLayout()
self.TestButton=QtWidgets.QPushButton('Test')
hlay1.addWidget(self.TestButton)
self.RunButton=QtWidgets.QPushButton('Run')
hlay1.addWidget(self.RunButton)
self.ScaleUpButton=QtWidgets.QPushButton('ScaleUp')
self.ScaleUpButton.clicked.connect(self.display.scaleupSignal)
hlay1.addWidget(self.ScaleUpButton)
self.ScaleDownButton=QtWidgets.QPushButton('ScaleDown')
self.ScaleDownButton.clicked.connect(self.display.scaledownSignal)
hlay1.addWidget(self.ScaleDownButton)
hlay2 = QtWidgets.QHBoxLayout()
hlay2.addWidget(QtWidgets.QPushButton('Set Faces'))
hlay2.addWidget(QtWidgets.QPushButton('FacePose'))
hlay2.addWidget(QtWidgets.QPushButton('Gender'))
hlay2.addWidget(QtWidgets.QPushButton('Age'))
hlay2.addWidget(QtWidgets.QPushButton('Recognize'))
layout = QtWidgets.QVBoxLayout()
layout.addLayout(hlay1)
layout.addLayout(hlay2)
self.horizontalGroupBox.setLayout(layout)
def set_status_message(self, message):
return self.statusBar().set_status_message(message)
class App(QMainWindow):
def __init__(self):
super(App,self).__init__()
self.title = 'FaceHumanVehicle'
self.left = 10
self.top = 10
self.width = 1000
self.height = 800
self.initUI()
def initUI(self):
self.setWindowTitle(self.title)
self.setGeometry(self.left, self.top, self.width, self.height)
self.form_widget = UIWidget(self)
self.statusBar().showMessage('')
self.setCentralWidget(self.form_widget)
self.show()
def set_status_message(self, message):
return self.statusBar().showMessage(message)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())
Whenever you add a widget to a layout, it will be automatically re-parented to the widget that contains the layout. So this code:
self.display = PlayStreaming()
# Create first tab
self.createGridLayout()
self.tab1.layout = QtWidgets.QVBoxLayout()
self.tab1.layout.addWidget(self.display, stretch=1)
will mean that self.display.parent() returns self.tab1, which obviously does not have a set_status_message method (since it's just a plain QWidget).
However, it's bad practice to try to directly access gui methods from a thread, so a better solution is to emit a custom signal that sends the status-bar message:
class Thread(QtCore.QThread):
statusMessage = QtCore.pyqtSignal(str)
...
def scaleup(self):
...
self.statusMessage.emit('Cur scale:'+str(self.curScale))
You can then connect this up within your PlayStreaming class and use its window() method to access the top-level main-window:
class PlayStreaming(QtWidgets.QLabel):
...
def initUI(self):
...
th = Thread(self)
th.statusMessage.connect(self.handle_status_message)
def handle_status_message(self, message):
self.window().set_status_message(message)
I use PyQt5 and Python2.7
I have UIWidget class, PlayStreaming class and Thread class.
Once a button from UIWidget is pressed and then dictionary object from PlayStreaming is sent to Thread class.
If I remove 'QVariantMap', I can receive Button click signal, but I can't send data.
How can I solve the problem?
My whole code is as follow.
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QApplication, QMainWindow, QInputDialog
import cv2
import time
import face_recognition.api as face_recognition
class Thread(QtCore.QThread):
changePixmap = QtCore.pyqtSignal(QtGui.QImage)
updateStatus = QtCore.pyqtSignal(str)
scaled_size = QtCore.QSize(640, 480)
curScale=1.0
facearray=[]
dim=(640,480)
processedImage=[]
def run(self):
cap = cv2.VideoCapture(-1)
cap.set(3,1280);
cap.set(4,1024);
time.sleep(2)
self.maxHeight=cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
self.maxScale=self.maxHeight/480.0
while True:
ret, frame = cap.read()
if ret:
r=1
rescaleSize=int(480*self.curScale)
if(frame.shape[0] > 480 and frame.shape[1] > 640):
r = rescaleSize / float(frame.shape[0])
self.dim = (int(frame.shape[1] * r), rescaleSize)
processedImage=cv2.resize(frame, self.dim, fx=0.0, fy=0.0)
face_locations = face_recognition.face_locations(processedImage)
if(len(face_locations) > 0):
encodefaces(facelocs)
else:
processedImage=frame.copy()
face_locations = face_recognition.face_locations(processedImage)
if(len(face_locations) > 0):
encodefaces(facelocs)
for face_location in face_locations:
top, right, bottom, left = face_location
cv2.rectangle(frame,(int(right/r),int(top/r)),(int(left/r),int(bottom/r)),(0,255,0),2)
rgbImage = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
convertToQtFormat = QtGui.QImage(rgbImage.data, rgbImage.shape[1], rgbImage.shape[0], QtGui.QImage.Format_RGB888)
p = convertToQtFormat.scaled(self.scaled_size, QtCore.Qt.KeepAspectRatio)
self.changePixmap.emit(p)
#QtCore.pyqtSlot(QtCore.QSize)
def scaled(self, scaled_size):
self.scaled_size = scaled_size
#QtCore.pyqtSlot()
def scaleup(self):
self.curScale = self.curScale + 0.1
if self.curScale > self.maxScale:
self.curScale = self.maxScale
self.updateStatus.emit('Cur scale:'+str(self.dim))
#QtCore.pyqtSlot()
def scaledown(self):
self.curScale = self.curScale - 0.1
if self.curScale < 1.0:
self.curScale = 1.0
self.updateStatus.emit('Cur scale:'+str(self.dim))
#QtCore.pyqtSlot('QVariantMap')
def getfacestorecognize(self, clickedInfos):
facearray.append(clickedInfos)
print(clickedInfos['x']+' '+clickedInfos['y']+' '+clickedInfos['name'])
def encodefaces(self, facelocs):
if(len(self.facearray) > 0):
for face in facearray:
r=(self.scaled_size[0]/self.dim[0])
x=int(face['x'])*r
y=int(face['y'])*r
#for loc in facelocs:
class PlayStreaming(QtWidgets.QLabel):
reSize = QtCore.pyqtSignal(QtCore.QSize)
scaleupSignal = QtCore.pyqtSignal()
scaledownSignal = QtCore.pyqtSignal()
transferFaceInfosSignal = QtCore.pyqtSignal()#'QVariantMap'
def __init__(self):
super(PlayStreaming, self).__init__()
self.initUI()
self.mousePressEvent = self.showDialog
#QtCore.pyqtSlot(QtGui.QImage)
def setImage(self, image):
self.label.setPixmap(QtGui.QPixmap.fromImage(image))
def initUI(self):
# create a label
self.label = QtWidgets.QLabel(self)
th = Thread(self)
th.changePixmap.connect(self.setImage)
th.updateStatus.connect(self.handle_status_message)
self.scaleupSignal.connect(th.scaleup)
self.scaledownSignal.connect(th.scaledown)
self.transferFaceInfosSignal.connect(th.getfacestorecognize)
self.reSize.connect(th.scaled)
th.start()
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(self.label, alignment=QtCore.Qt.AlignCenter)
def resizeEvent(self, event):
self.reSize.emit(self.size())
def showDialog(self, event):
x = event.pos().x()
y = event.pos().y()
facedata={"x": str(x), "y": str(y), "name": ''}
text, ok = QInputDialog.getText(self, 'Name input dialog',
'Enter name:')
if (ok and str(text)!=''):
facedata['name']=str(text)
self.transferFaceInfosSignal.emit(facedata)
def handle_status_message(self, message):
self.window().set_status_message(message)
class UIWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(UIWidget, self).__init__(parent)
# Initialize tab screen
self.tabs = QtWidgets.QTabWidget()
self.tab1 = QtWidgets.QWidget()
self.tab2 = QtWidgets.QWidget()
self.tab3 = QtWidgets.QWidget()
# Add tabs
self.tabs.addTab(self.tab1, "Face")
self.tabs.addTab(self.tab2, "Human")
self.tabs.addTab(self.tab3, "Vehicle")
self.display = PlayStreaming()
# Create first tab
self.createGridLayout()
self.tab1.layout = QtWidgets.QVBoxLayout()
self.tab1.layout.addWidget(self.display, stretch=1)
self.tab1.layout.addWidget(self.horizontalGroupBox)
self.tab1.setLayout(self.tab1.layout)
# Add tabs to widget
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.tabs)
def createGridLayout(self):
self.horizontalGroupBox = QtWidgets.QGroupBox("")
self.horizontalGroupBox.setStyleSheet("QGroupBox{ background-color: red; border: none;}")
hlay1 = QtWidgets.QHBoxLayout()
self.TestButton=QtWidgets.QPushButton('Test')
hlay1.addWidget(self.TestButton)
self.RunButton=QtWidgets.QPushButton('Run')
hlay1.addWidget(self.RunButton)
self.ScaleUpButton=QtWidgets.QPushButton('ScaleUp')
self.ScaleUpButton.clicked.connect(self.display.scaleupSignal)
hlay1.addWidget(self.ScaleUpButton)
self.ScaleDownButton=QtWidgets.QPushButton('ScaleDown')
self.ScaleDownButton.clicked.connect(self.display.scaledownSignal)
hlay1.addWidget(self.ScaleDownButton)
hlay1.addWidget(QtWidgets.QPushButton('Reset'))
hlay2 = QtWidgets.QHBoxLayout()
hlay2.addWidget(QtWidgets.QPushButton('Set Faces'))
hlay2.addWidget(QtWidgets.QPushButton('FacePose'))
hlay2.addWidget(QtWidgets.QPushButton('Gender'))
hlay2.addWidget(QtWidgets.QPushButton('Age'))
self.RecognizeButton=QtWidgets.QPushButton('Recognize')
self.RecognizeButton.clicked.connect(self.display.transferFaceInfosSignal)
hlay2.addWidget(self.RecognizeButton)
layout = QtWidgets.QVBoxLayout()
layout.addLayout(hlay1)
layout.addLayout(hlay2)
self.horizontalGroupBox.setLayout(layout)
class App(QMainWindow):
def __init__(self):
super(App,self).__init__()
self.title = 'FaceHumanVehicle'
self.left = 10
self.top = 10
self.width = 1000
self.height = 800
self.initUI()
def initUI(self):
self.setWindowTitle(self.title)
self.setGeometry(self.left, self.top, self.width, self.height)
self.form_widget = UIWidget(self)
self.statusBar().showMessage('')
self.setCentralWidget(self.form_widget)
self.show()
def set_status_message(self, message):
return self.statusBar().showMessage(message)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())
From what I understand you want to send the data when you press the button, so the slot connecting to the button should emit the signal, not connect to the signal. For this the data that I see is filled in mousePressEvent must be a member of the class, in this case the logic is to save the position when the image is pressed, and when the button is pressed a dialogue will be opened, a name will be established and the data will be sent.
class PlayStreaming(QtWidgets.QLabel):
reSize = QtCore.pyqtSignal(QtCore.QSize)
scaleupSignal = QtCore.pyqtSignal()
scaledownSignal = QtCore.pyqtSignal()
transferFaceInfosSignal = QtCore.pyqtSignal('QVariantMap') # <--- +++
def __init__(self):
super(PlayStreaming, self).__init__()
self.facedata = {"x": "", "y": "", "name": ""} # <--- +++
self.initUI()
# self.mousePressEvent = self.showDialog <--- ---
# ...
def mousePressEvent(self, event):
self.facedata["x"] = str(event.pos().x())
self.facedata["y"] = str(event.pos().y())
self.showDialog()
super(PlayStreaming, self).mousePressEvent(event)
def showDialog(self):
text, ok = QtWidgets.QInputDialog.getText(self, 'Name input dialog', 'Enter name:')
if ok and text:
self.facedata['name']= text
#QtCore.pyqtSlot()
def send_signal(self)
if self.facedata["name"]:
self.transferFaceInfosSignal.emit(self.facedata)
class UIWidget(QtWidgets.QWidget):
# ...
def createGridLayout(self):
# ...
self.RecognizeButton.clicked.connect(self.display.send_signal)
# ...
On the other hand the signals and slot support all native python types like dict, so you can replace 'QVariantMap' with dict.
How to modify the combobox in wxPython to dropdown a checklistbox so I can select the choices?
something like above. I know that ComboBox class is available and I need to inherit it add the feature of checkbox to dropdown list. But I am not getting the proper way to start with it.
Take a look at the ComboCtrl class. It allows you to provide the window that implements the combo's drop-down. There are some examples in the wxPython demo.
I haven't had the time to grind through ComboCtrl and it looks daunting.
However, an approximation of what you want can be achieved by torturing a wx.Dialog.
import wx
import wx.lib.scrolledpanel as scrolled
class MyFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, -1, "CheckBox Dialog",size=(400,250))
self.panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
self.log = wx.TextCtrl(self.panel, wx.ID_ANY, size=(350,150),style = wx.TE_MULTILINE|wx.TE_READONLY|wx.VSCROLL)
self.button = wx.Button(self.panel, label="Choose Colours")
sizer.Add(self.log, 0, wx.EXPAND | wx.ALL, 10)
sizer.Add(self.button, 0, wx.EXPAND | wx.ALL, 10)
self.panel.SetSizer(sizer)
self.Bind(wx.EVT_BUTTON, self.OnButton)
self.panel.options = ['Red','Green','Black','White','Orange','Blue','Yellow']
self.panel.selected = [0,0,0,0,0,0,0]
def OnButton(self,event):
dlg = ShowOptions(parent = self.panel)
dlg.ShowModal()
if dlg.result:
result_text = 'Selected: '
for item in range(len(dlg.result)):
if dlg.result[item]:
result_text += self.panel.options[item]+' '
self.log.AppendText(result_text+'\n\n')
self.panel.selected = dlg.result
else:
self.log.AppendText("No selection made\n\n")
dlg.Destroy()
class ShowOptions(wx.Dialog):
def __init__(self, parent):
self.options = parent.options
self.selected = parent.selected
o_str = ''
for item in self.options:
o_str = o_str+item+','
wx.Dialog.__init__(self, parent, wx.ID_ANY, "CheckBoxes", size= (400,250))
self.top_panel = wx.Panel(self,wx.ID_ANY)
self.avail_options = wx.TextCtrl(self.top_panel, wx.ID_ANY, o_str,style = wx.TE_READONLY)
self.bot_panel = wx.Panel(self,wx.ID_ANY)
self.scr_panel = scrolled.ScrolledPanel(self,wx.ID_ANY)
top_sizer = wx.BoxSizer(wx.VERTICAL)
scr_sizer = wx.BoxSizer(wx.VERTICAL)
bot_sizer = wx.BoxSizer(wx.VERTICAL)
self.items = []
for item in range(len(self.options)):
self.item = wx.CheckBox(self.scr_panel,-1,self.options[item])
self.item.SetValue(self.selected[item])
self.items.append(self.item)
self.item.Bind(wx.EVT_CHECKBOX, self.Select)
self.saveButton =wx.Button(self.bot_panel, label="Save")
self.closeButton =wx.Button(self.bot_panel, label="Cancel")
self.saveButton.Bind(wx.EVT_BUTTON, self.SaveOpt)
self.closeButton.Bind(wx.EVT_BUTTON, self.OnQuit)
self.Bind(wx.EVT_CLOSE, self.OnQuit)
top_sizer.Add(self.avail_options,0,flag=wx.EXPAND)
for item in self.items:
scr_sizer.Add(item,0)
bot_sizer.Add(self.saveButton,0,flag=wx.CENTER)
bot_sizer.Add(self.closeButton,0,flag=wx.CENTER)
self.scr_panel.SetupScrolling()
self.top_panel.SetSizer(top_sizer)
self.scr_panel.SetSizer(scr_sizer)
self.bot_panel.SetSizer(bot_sizer)
mainsizer = wx.BoxSizer(wx.VERTICAL)
mainsizer.Add(self.top_panel,0,flag=wx.EXPAND)
mainsizer.Add(self.scr_panel,1,flag=wx.EXPAND)
mainsizer.Add(self.bot_panel,0,flag=wx.EXPAND)
self.SetSizer(mainsizer)
self.Select(None)
self.Show()
def Select(self, event):
selection = []
for item in self.items:
x = item.GetValue()
selection.append(x)
selected_text = ''
for item in range(len(selection)):
if selection[item]:
selected_text += self.options[item]+' '
self.avail_options.SetValue(selected_text)
def OnQuit(self, event):
self.result = None
self.Destroy()
def SaveOpt(self, event):
self.result = []
for item in self.items:
x = item.GetValue()
self.result.append(x)
self.Destroy()
app = wx.App()
frame = MyFrame(None)
frame.Show()
app.MainLoop()
I created a widget using wx.ComboCtrl, as suggested by Robin Dunn.
Feel free to use it, or modify it any way you like.
You can also find a more advanced version of it on my GitHub account.
import wx
import wx.lib.mixins.listctrl
import operator
import functools
import contextlib
#contextlib.contextmanager
def asCM(function, *args, **kwargs):
"""Used to build with wxWidgets as context managers to help organize code."""
yield function(*args, **kwargs)
class CheckListCtrl(wx.ComboCtrl):
"""A wxListCtrl-like widget where each item in the drop-down list has a check box.
Modified code from: https://github.com/wxWidgets/Phoenix/blob/master/demo/ComboCtrl.py
"""
def __init__(self, parent, myId = None, initial = None, position = None, size = None, readOnly = False, **kwargs):
"""
parent (wxWindow) – Parent window (must not be None)
initial (str) – Initial selection string
readOnly (bool) - Determiens if the user can modify values in this widget
Example Input: CheckListCtrl(self)
"""
self.parent = parent
#Configure settings
style = []
if (readOnly):
style.append(wx.CB_READONLY)
#Create object
super().__init__(parent,
id = myId or wx.ID_ANY,
value = initial or "",
pos = position or wx.DefaultPosition,
size = size or wx.DefaultSize,
style = functools.reduce(operator.ior, style or (0,)))
self.popup = self.MyPopup(self, **kwargs)
def Append(self, *args, **kwargs):
self.popup.Append(*args, **kwargs)
class MyPopup(wx.ComboPopup):
"""The popup control used by CheckListCtrl."""
def __init__(self, parent, *, popupId = None, multiple = True, prefHeight = None,
image_check = None, image_uncheck = None, lazyLoad = False):
"""
multiple (bool) - Determines if the user can check multiple boxes or not
lazyLoad (bool) - Determines if when Create() is called
- If True: Waits for the first time the popup is called
- If False: Calls it during the build process
prefHeight (int) - What height you would prefer the popup box use
- If None: Will calculate what hight to use based on it's contents
- If -1: Will use the default height
"""
self.parent = parent
self.prefHeight = prefHeight
self._buildVar_myId = popupId
self._buildVar_multiple = multiple
self._buildVar_lazyLoad = lazyLoad
self._buildVar_image_check = image_check
self._buildVar_image_uncheck = image_uncheck
super().__init__()
parent.SetPopupControl(self)
def Create(self, parent):
self.checkList = self.MyListCtrl(self, parent,
myId = self._buildVar_myId,
multiple = self._buildVar_multiple,
image_check = self._buildVar_image_check,
image_uncheck = self._buildVar_image_uncheck)
return True
def Append(self, *args, **kwargs):
self.checkList.Append(*args, **kwargs)
def GetControl(self):
return self.checkList
def GetAdjustedSize(self, minWidth, prefHeight, maxHeight):
if (self.prefHeight is -1):
return super().GetAdjustedSize(minWidth, prefHeight, maxHeight)
elif (self.prefHeight is not None):
return (minWidth, min(self.prefHeight, maxHeight))
return self.checkList.GetBestSize(minWidth, prefHeight, maxHeight)
def LazyCreate(self):
return self._buildVar_lazyLoad
class MyListCtrl(wx.ListCtrl, wx.lib.mixins.listctrl.CheckListCtrlMixin):
"""Modified code from: https://github.com/wxWidgets/wxPython/blob/master/demo/CheckListCtrlMixin.py"""
def __init__(self, parent, root, *, myId = None, multiple = False, image_check = None, image_uncheck = None):
"""
multiple (bool) - Determines if the user can check multiple boxes or not
"""
self.parent = parent
#Configure settings
style = [wx.LC_LIST, wx.SIMPLE_BORDER]
if (not multiple):
style.append(wx.LC_SINGLE_SEL)
#Create object
wx.ListCtrl.__init__(self, root, id = myId or wx.ID_ANY, style = functools.reduce(operator.ior, style or (0,)))
wx.lib.mixins.listctrl.CheckListCtrlMixin.__init__(self, check_image = image_check, uncheck_image = image_uncheck)
def Append(self, value, default = False):
"""Appends the given item to the list.
value (str) - What the item will say
default (bool) - What state the check box will start out at
Example Input: Append("lorem")
Example Input: Append("lorem", default = True)
"""
n = self.GetItemCount()
self.InsertItem(n, value)
if (default):
self.CheckItem(n)
def GetBestSize(self, minWidth, prefHeight, maxHeight):
return (minWidth, min(prefHeight, maxHeight, sum(self.GetItemRect(i)[3] for i in range(self.GetItemCount())) + self.GetItemRect(0)[3]))
# this is called by the base class when an item is checked/unchecked
def OnCheckItem(self, index, state):
print(index, state)
if (__name__ == "__main__"):
class TestFrame(wx.Frame):
def __init__(self):
super().__init__(None, wx.ID_ANY, "Lorem Ipsum")
with asCM(wx.Panel, self, wx.ID_ANY) as myPanel:
with asCM(wx.BoxSizer, wx.VERTICAL) as mySizer:
with asCM(CheckListCtrl, myPanel, prefHeight = None) as myWidget:
myWidget.Append("lorem")
myWidget.Append("ipsum", default = True)
myWidget.Append("dolor")
mySizer.Add(myWidget, 0, wx.ALL, 5)
myPanel.SetSizer(mySizer)
####################################
app = wx.App(False)
frame = TestFrame()
frame.Show()
app.MainLoop()
I have tried to boil down my program to be as simple as possible and still be functional. First is my UI file with the tabs that I want. It has a graph in the Control tab and I want the play button to start the plot.
When I start it I can't get the graph to show up. any help would be appreciated.
from PyQt4 import QtCore, QtGui
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
def _fromUtf8(s):
return s
try:
_encoding = QtGui.QApplication.UnicodeUTF8
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
def _translate(context, text, disambig):
return QtGui.QApplication.translate(context, text, disambig)
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName(_fromUtf8("MainWindow"))
MainWindow.resize(800, 600)
self.centralwidget = QtGui.QWidget(MainWindow)
self.centralwidget.setObjectName(_fromUtf8("centralwidget"))
self.tabWidget = QtGui.QTabWidget(self.centralwidget)
self.tabWidget.setGeometry(QtCore.QRect(0, 0, 801, 421))
self.tabWidget.setStyleSheet(_fromUtf8(""))
self.tabWidget.setObjectName(_fromUtf8("tabWidget"))
self.tab_4 = QtGui.QWidget()
self.tab_4.setObjectName(_fromUtf8("tab_4"))
self.tabWidget.addTab(self.tab_4, _fromUtf8(""))
self.tab_2 = QtGui.QWidget()
self.tab_2.setObjectName(_fromUtf8("tab_2"))
self.tabWidget.addTab(self.tab_2, _fromUtf8(""))
self.tab = QtGui.QWidget()
self.tab.setObjectName(_fromUtf8("tab"))
self.pushButton = QtGui.QPushButton(self.tab)
self.pushButton.setGeometry(QtCore.QRect(290, 340, 75, 31))
icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap(_fromUtf8("play_button.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.pushButton.setIcon(icon)
self.pushButton.setObjectName(_fromUtf8("pushButton"))
self.pushButton_2 = QtGui.QPushButton(self.tab)
self.pushButton_2.setGeometry(QtCore.QRect(460, 340, 75, 31))
icon1 = QtGui.QIcon()
icon1.addPixmap(QtGui.QPixmap(_fromUtf8("pause_button.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.pushButton_2.setIcon(icon1)
self.pushButton_2.setObjectName(_fromUtf8("pushButton_2"))
self.pushButton_3 = QtGui.QPushButton(self.tab)
self.pushButton_3.setGeometry(QtCore.QRect(630, 340, 75, 31))
icon2 = QtGui.QIcon()
icon2.addPixmap(QtGui.QPixmap(_fromUtf8("resume_button.png")), QtGui.QIcon.Normal, QtGui.QIcon.Off)
self.pushButton_3.setIcon(icon2)
self.pushButton_3.setObjectName(_fromUtf8("pushButton_3"))
self.graphicsView = QtGui.QGraphicsView(self.tab)
self.graphicsView.setGeometry(QtCore.QRect(205, 1, 591, 341))
self.graphicsView.setObjectName(_fromUtf8("graphicsView"))
self.tabWidget.addTab(self.tab, _fromUtf8(""))
self.tab_3 = QtGui.QWidget()
self.tab_3.setObjectName(_fromUtf8("tab_3"))
self.tabWidget.addTab(self.tab_3, _fromUtf8(""))
MainWindow.setCentralWidget(self.centralwidget)
self.statusbar = QtGui.QStatusBar(MainWindow)
self.statusbar.setObjectName(_fromUtf8("statusbar"))
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
self.tabWidget.setCurrentIndex(2)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow", None))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_4), _translate("MainWindow", "Calibration", None))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_2), _translate("MainWindow", "Material", None))
self.pushButton.setText(_translate("MainWindow", "Start Plot", None))
self.pushButton_2.setText(_translate("MainWindow", "Pause", None))
self.pushButton_3.setText(_translate("MainWindow", "Resume", None))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), _translate("MainWindow", "Control", None))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab_3), _translate("MainWindow", "Operator", None))
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
MainWindow = QtGui.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
The second is my graphing class which does work when I make a gui with code, but I would like to be able to plug it into a program with a ui file.
import sys
import time
import datetime
from PyQt4 import QtCore, QtGui, uic
from PyQt4.QtGui import *
from PyQt4.QtCore import *
import pyqtgraph as pg
import random
from workingraspitab2 import Ui_MainWindow
class Main(QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
self.setupUi(self)
self.graphicsView = QtGui.QStackedWidget()
self.login_widget = LoginWidget(self)
self.graphicsView.addWidget(self.login_widget)
self.pushButton.clicked.connect(self.plotter)#the play button
self.curve = self.login_widget.it1
self.curve2 =self.login_widget.it2
def plotter(self):
self.data =[0]
self.data2 = [0]
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.updater)
self.timer.start(0)
print self.data
def updater(self):
self.data.append(self.data[-1]+10*(0.5-random.random()))
self.curve.setData(self.data, pen=pg.mkPen('b', width=1))
self.data2.append(self.data2[-1]+0.1*(0.5-random.random()))
self.curve2.setData(self.data2, pen=pg.mkPen('r', width=1))
print self.data
class LoginWidget(QtGui.QWidget):
def __init__(self, parent=None):
super(LoginWidget, self).__init__(parent)
pg.setConfigOption('background', 'w')
pg.setConfigOption('foreground', 'k')
self.plot = pg.PlotWidget(title='Force and Extension vs. Time')
p1 = self.plot.plotItem
p2 = pg.ViewBox()
p1.showAxis('right')
p1.scene().addItem(p2)
p1.getAxis('right').linkToView(p2)
p2.setXLink(p1)
self.plot.getAxis('bottom').setLabel('Time', units='s')
self.plot.getAxis('left').setLabel('Force', units='lbf', color="#0000ff")
p1.getAxis('right').setLabel('Extension', units='in.', color="#ff0000")
def updateViews():
p2.setGeometry(p1.vb.sceneBoundingRect())
p2.linkedViewChanged(p1.vb, p2.XAxis)
updateViews()
p1.vb.sigResized.connect(updateViews)
self.it1 = p1.plot()
self.it2 = pg.PlotCurveItem()
p2.addItem(self.it2)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Main()
window.show()
sys.exit(app.exec_())
Had to put everything in the Main class to get it to work. I was hoping for a more elegant solution. I still cant get the background or foreground to change, but it is working and here is the code:
class Main(QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
self.setupUi(self)
pv = self.graphicsView
pv.setTitle('Force and Extension vs. Time')
pg.setConfigOption('background', 'w')
pg.setConfigOption('foreground', 'k')
p1 = pv.plotItem
p2 = pg.ViewBox()
p1.showAxis('right')
p1.scene().addItem(p2)
p1.getAxis('right').linkToView(p2)
p2.setXLink(p1)
pv.getAxis('bottom').setLabel('Time', units='s')
pv.getAxis('left').setLabel('Force', units='lbf', color="#0000ff")
p1.getAxis('right').setLabel('Extension', units='in.', color="#ff0000")
def updateViews():
p2.setGeometry(p1.vb.sceneBoundingRect())
p2.linkedViewChanged(p1.vb, p2.XAxis)
updateViews()
p1.vb.sigResized.connect(updateViews)
self.it1 = p1.plot()
self.it2 = pg.PlotCurveItem()
p2.addItem(self.it2)
self.pushButton.clicked.connect(self.plotter)
self.pushButton_2.clicked.connect(lambda: self.timer.stop())
self.pushButton_3.clicked.connect(self.timer_start)
self.curve = self.it1
self.curve2 = self.it2