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()
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 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.
I am trying to learn OOP and wx Python concurrently, and I am finding creating an instance of a class when using wx difficult. I don't know what I am missing, but see the code below and you can see my confusion with the last line before the loop. In a normal inheritance scenario, I could create an instance of the class DailyTasks and then call a method using that instance. Why not in wx? Is it because I am not using the object class in the parent class and instead have to use wx.Frame and wx.Panel? I hope this makes sense what I am asking. The first part of the code is the behavior that I would expect without wx, and the second group of code is the wx code that is confusing me. Thanks in advance for your help.
----------
class Animals(object):
def __init__(self, name):
self.name = name
def eat(self, food):
print '%s eats the %s'% (self.name, food)
def breath(self):
print '%s breaths through lungs'%(self.name)
class Dog(Animals):
def gets_mad(self):
print 'When %s gets mad, %s bites'%(self.name, self.name)
def show_affection(self):
print '%s shows affection and wags his tail'% (self.name)
def fetch(self,thing):
print '%s chases and brings back the %s'% (self.name, thing)
class Cat(Animals):
def gets_mad(self):
print 'When %s gets mad, %s will scratch you'%(self.name, self.name)
def show_affection(self):
print '%s shows affection and purrs'% (self.name)
def fetch(self):
print "%s doesn't fetch anything" % (self.name)
r = Dog('Rover') #instantiating to Dog class
c = Cat('Fluffy')
c.show_affection()
r.show_affection()
c.fetch()
r.fetch('ball')
r.eat('dogfood')
c.eat('catfood')
c.gets_mad()
r.gets_mad()
rex = Dog('Ralph')
rex.eat('steak')
----------
import wx
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title='My File Organization')
book = wx.Notebook(self)
page = Settings(parent=book)
page2 = DailyTasks(parent=book)
book.AddPage(page, 'Settings')
book.AddPage(page2,'Daily Tasks')
self.SetSize(wx.Size(1024,768))
self.Centre()
class Settings(wx.Panel):
def __init__(self, parent):
self.parent = parent
wx.Panel.__init__(self, parent)
self.panel = wx.Panel(self, pos = (0,0),
size=(1024,768))
self.file_text = wx.StaticText(self.panel,
-1, "Daily Tasks Button 1 Path", (40,20))
self.file_text_box = wx.TextCtrl(self.panel,
-1,pos = (170,15), size = (240,20), style = 0, value = 'C:\Documents')
self.file_button_text = wx.StaticText(self.panel,
-1, "Button Name", (420,20))
self.file_button_text_box = wx.TextCtrl(self.panel,
-1,pos = (500,15), size = (140,20), style = 0, value = 'Zombies')
self.file_text2 = wx.StaticText(self.panel, -1,
"Daily Tasks Button 2 Path", (40,50))
self.file_text_box2 = wx.TextCtrl(self.panel,
-1,pos = (170,45), size = (240,20), style = 0, value = 'C:\\')
self.file_button_text2 = wx.StaticText(self.panel,
-1, "Button Name", (420,50))
self.file_button_text_box2 = wx.TextCtrl(self.panel,
-1,pos = (500,45), size = (140,20), style = 0, value = 'Angels')
class DailyTasks(Settings):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.parent = None
self.panel = wx.Panel(self, pos = (0,0), size=(1024,768))
def test(self):
print 'testing'
dt = DailyTasks() # This is where I have the question and need to create
# an instance of the DailyTasks class.
if __name__ == "__main__":
app = wx.App()
MyFrame().Show()
app.MainLoop()
wx should allow inheritance in the normal manner. Can you expand on what the actual behavior is when you assign dt to your instance of DailyTask()
From this question I found out how to ask the user for an input using wxPython. But how can I ask for multiple information at the same time?
Basicly I want to create a message box with three input fields under each other asking for "Name" "Surname" "Nickname". I also want to have the code really simple like:
name,surname,nick = askInfo("Name","Surname","Nickname")
It doesn't have to be wxPython but it MUST not be using Tkinter.
Build a wx.Dialog there are numerous examples out there.
import wx
class MyFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, -1, "Dialog Test",size=(500,400))
self.panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
self.log = wx.TextCtrl(self.panel, wx.ID_ANY, size=(400,300),style = wx.TE_MULTILINE|wx.TE_READONLY|wx.VSCROLL)
self.button = wx.Button(self.panel, label="Click me")
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)
def OnButton(self,event):
dlg = GetData(parent = self.panel)
dlg.ShowModal()
if dlg.result_name:
self.log.AppendText("Name: "+dlg.result_name+"\n")
self.log.AppendText("Surname: "+dlg.result_surname+"\n")
self.log.AppendText("Nickname: "+dlg.result_nickname+"\n")
else:
self.log.AppendText("No Input found\n")
dlg.Destroy()
class GetData(wx.Dialog):
def __init__(self, parent):
wx.Dialog.__init__(self, parent, wx.ID_ANY, "Name Input", size= (650,220))
self.panel = wx.Panel(self,wx.ID_ANY)
self.lblname = wx.StaticText(self.panel, label="Name", pos=(20,20))
self.name = wx.TextCtrl(self.panel, value="", pos=(110,20), size=(500,-1))
self.lblsur = wx.StaticText(self.panel, label="Surname", pos=(20,60))
self.surname = wx.TextCtrl(self.panel, value="", pos=(110,60), size=(500,-1))
self.lblnick = wx.StaticText(self.panel, label="Nickname", pos=(20,100))
self.nickname = wx.TextCtrl(self.panel, value="", pos=(110,100), size=(500,-1))
self.saveButton =wx.Button(self.panel, label="Save", pos=(110,160))
self.closeButton =wx.Button(self.panel, label="Cancel", pos=(210,160))
self.saveButton.Bind(wx.EVT_BUTTON, self.SaveConnString)
self.closeButton.Bind(wx.EVT_BUTTON, self.OnQuit)
self.Bind(wx.EVT_CLOSE, self.OnQuit)
self.Show()
def OnQuit(self, event):
self.result_name = None
self.Destroy()
def SaveConnString(self, event):
self.result_name = self.name.GetValue()
self.result_surname = self.surname.GetValue()
self.result_nickname = self.nickname.GetValue()
self.Destroy()
app = wx.App()
frame = MyFrame(None)
frame.Show()
app.MainLoop()
Pay special attention to the result items
I have a wxgrid inside a resizable scrollable panel. I dynamically add/hide/show rows in wxgrid. When I try to add/show more rows in wxgrid, it does not fit to the available space in panel but instead occupies a small area it had been occupying previously with a scrollbar for wxgrid.
Like this:
But after I resize the panel or frame, then it fits perfectly. Like this:
How can I make it to fit properly without needing to resize the panel?
I have tried all combinations of wx.EXPAND, wx.GROW, wx.ALL while adding grid to sizer and also tried gridobj.Layout() Nothing works. Any Ideas?
Iam using wx 3.0 with python 2.7 on windows 7
Edit:
Here's my code
controls.py
import wx
import wx.grid
import wx.combo
class SimpleGrid(wx.grid.Grid):
def __init__(self, parent):
wx.grid.Grid.__init__(self, parent, -1)
self.CreateGrid(10, 5)
for i in range(10):
self.SetRowLabelValue(i,str(i))
class ListCtrlComboPopup(wx.ListCtrl, wx.combo.ComboPopup):
def __init__(self,parent):
self.gfobj = parent
self.PostCreate(wx.PreListCtrl())
self.parent = parent
wx.combo.ComboPopup.__init__(self)
def AddItem(self, txt):
self.InsertStringItem(self.GetItemCount(), txt)
self.Select(0)
def GetSelectedItems(self):
del self.gfobj.selection[:]
current = -1
while True:
next = self.GetNextSelected(current)
if next == -1:
return
self.gfobj.selection.append(next)
current = next
def onItemSelected(self, event):
item = event.GetItem()
self.GetSelectedItems()
self.parent.draw_plot()
def onItemDeSelected(self, event):
self.GetSelectedItems()
self.parent.draw_plot()
def Init(self):
""" This is called immediately after construction finishes. You can
use self.GetCombo if needed to get to the ComboCtrl instance. """
self.value = -1
self.curitem = -1
def Create(self, parent):
""" Create the popup child control. Return True for success. """
wx.ListCtrl.Create(self, parent,
style=wx.LC_LIST|wx.SIMPLE_BORDER)
self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.onItemSelected)
self.Bind(wx.EVT_LIST_ITEM_DESELECTED, self.onItemDeSelected)
return True
def GetControl(self):
""" Return the widget that is to be used for the popup. """
return self
def SetStringValue(self, val):
""" Called just prior to displaying the popup, you can use it to
'select' the current item. """
idx = self.FindItem(-1, val)
if idx != wx.NOT_FOUND:
self.Select(idx)
def GetStringValue(self):
""" Return a string representation of the current item. """
a = self.GetItemText(self.value)
if self.value >= 0:
return a
return ""
def OnPopup(self):
""" Called immediately after the popup is shown. """
self.state = []
for i in range(self.GetItemCount()):
item = self.GetItem(itemId=i)
self.state.append(item.GetState())
#print self.state
wx.combo.ComboPopup.OnPopup(self)
def OnDismiss(self):
" Called when popup is dismissed. """
wx.combo.ComboPopup.OnDismiss(self)
main.py
import wx
import wx.lib.scrolledpanel
from controls import SimpleGrid
from controls import ListCtrlComboPopup
class GraphFrame(wx.Frame):
title = 'Demo: Data Trending Tool'
def __init__(self):
self.selection = []
self.displaySize = wx.DisplaySize()
wx.Frame.__init__(self, None, -1, self.title,
style = wx.DEFAULT_FRAME_STYLE,
size = (self.displaySize[0]/2, self.displaySize[1]/2))
self.containingpanel = wx.Panel(self, -1)
self.toppanel = wx.Panel(self, -1)
self.splittedwin = wx.SplitterWindow(self.containingpanel, wx.ID_ANY, style=wx.SP_3D | wx.SP_BORDER)
self.splittedwin.SetMinimumPaneSize(20)
self.gridpanel = wx.lib.scrolledpanel.ScrolledPanel(self.splittedwin,-1, style = wx.SUNKEN_BORDER)
self.panel = wx.lib.scrolledpanel.ScrolledPanel(self.splittedwin,-1, style = wx.SUNKEN_BORDER)
#### GRID
self.grid = SimpleGrid(self.gridpanel)
self.gridpanelsizer= wx.BoxSizer(wx.HORIZONTAL)
self.gridpanelsizer.Add(self.grid, wx.GROW)
self.gridpanel.SetSizer(self.gridpanelsizer)
self.gridpanelsizer.Fit(self)
#### COMBOBOX
self.cc = wx.combo.ComboCtrl(self.toppanel, style=wx.CB_READONLY, size=(200,-1), )
self.cc.SetPopupMaxHeight(140)
popup = ListCtrlComboPopup(self)
self.cc.SetPopupControl(popup)
self.cc.SetText("--select--")
# Add some items to the listctrl
for i in range(10):
popup.AddItem(str(i))
#### SIZER FOR COMBOBOX
self.cbpanelsizer= wx.BoxSizer(wx.HORIZONTAL)
self.cbpanelsizer.Add(self.cc, border = 5,flag = wx.LEFT)
self.toppanel.SetSizer(self.cbpanelsizer)
self.splittedwin.SplitHorizontally(self.gridpanel,self.panel,100)
##### SIZER FOR CONTAININGPANEL
self.cpsizer = wx.BoxSizer(wx.VERTICAL)
self.cpsizer.Add(self.splittedwin, 1, wx.EXPAND, 0)
self.containingpanel.SetSizer(self.cpsizer)
self.cpsizer.Fit(self.containingpanel)
mainsizer = wx.BoxSizer(wx.VERTICAL)
mainsizer.Add(self.toppanel, 0, wx.EXPAND)
mainsizer.Add(self.containingpanel, 1, wx.EXPAND)
self.SetSizerAndFit(mainsizer)
self.panel.SetAutoLayout(1)
self.panel.SetupScrolling()
self.gridpanel.SetAutoLayout(1)
self.gridpanel.SetupScrolling()
self.draw_plot()
def draw_plot(self):
for i in range(10):
if i in self.selection:
self.grid.ShowRow(i)
else:
self.grid.HideRow(i)
self.Layout()
#self.gridpanel.Layout()
if __name__ == "__main__":
app = wx.PySimpleApp()
app.frame = GraphFrame()
app.frame.Show()
app.MainLoop()
To simulate:
1. run main.py It displays a splitted window with a grid with single row in one panel.
Use the drop down to select more than one item (hold ctrl and select)
The wxgrid is cramped to one row space with a wxgrid scrollbar
Resize the panel using the splitter or resize the window. Now all the selected rows appear as required.
Finally found out!!!
With the help of This Answer, found that the problem was trying to redraw the gridpanel using gridpanel.Layout(). Instead redrawing the gridpanelsizer using gridpanelsizer.Layout() worked out!!
Updated main.py:
import wx
import wx.lib.scrolledpanel
from controls import SimpleGrid
from controls import ListCtrlComboPopup
class GraphFrame(wx.Frame):
title = 'Demo: Data Trending Tool'
def __init__(self):
self.selection = []
self.displaySize = wx.DisplaySize()
wx.Frame.__init__(self, None, -1, self.title,
style = wx.DEFAULT_FRAME_STYLE,
size = (self.displaySize[0]/2, self.displaySize[1]/2))
self.containingpanel = wx.Panel(self, -1)
self.toppanel = wx.Panel(self, -1)
self.splittedwin = wx.SplitterWindow(self.containingpanel, wx.ID_ANY, style=wx.SP_3D | wx.SP_BORDER)
self.splittedwin.SetMinimumPaneSize(20)
self.gridpanel = wx.lib.scrolledpanel.ScrolledPanel(self.splittedwin,-1, style = wx.SUNKEN_BORDER)
self.panel = wx.lib.scrolledpanel.ScrolledPanel(self.splittedwin,-1, style = wx.SUNKEN_BORDER)
#### GRID
self.grid = SimpleGrid(self.gridpanel)
self.gridpanelsizer= wx.BoxSizer(wx.HORIZONTAL)
self.gridpanelsizer.Add(self.grid, wx.GROW)
self.gridpanel.SetSizer(self.gridpanelsizer)
self.gridpanelsizer.Fit(self)
#### COMBOBOX
self.cc = wx.combo.ComboCtrl(self.toppanel, style=wx.CB_READONLY, size=(200,-1), )
self.cc.SetPopupMaxHeight(140)
popup = ListCtrlComboPopup(self)
self.cc.SetPopupControl(popup)
self.cc.SetText("--select--")
# Add some items to the listctrl
for i in range(10):
popup.AddItem(str(i))
#### SIZER FOR COMBOBOX
self.cbpanelsizer= wx.BoxSizer(wx.HORIZONTAL)
self.cbpanelsizer.Add(self.cc, border = 5,flag = wx.LEFT)
self.toppanel.SetSizer(self.cbpanelsizer)
self.splittedwin.SplitHorizontally(self.gridpanel,self.panel,100)
##### SIZER FOR CONTAININGPANEL
self.cpsizer = wx.BoxSizer(wx.VERTICAL)
self.cpsizer.Add(self.splittedwin, 1, wx.EXPAND, 0)
self.containingpanel.SetSizer(self.cpsizer)
self.cpsizer.Fit(self.containingpanel)
mainsizer = wx.BoxSizer(wx.VERTICAL)
mainsizer.Add(self.toppanel, 0, wx.EXPAND)
mainsizer.Add(self.containingpanel, 1, wx.EXPAND)
self.SetSizerAndFit(mainsizer)
self.panel.SetAutoLayout(1)
self.panel.SetupScrolling()
self.gridpanel.SetAutoLayout(1)
self.gridpanel.SetupScrolling()
self.draw_plot()
def draw_plot(self):
for i in range(10):
if i in self.selection:
self.grid.ShowRow(i)
else:
self.grid.HideRow(i)
#self.Layout()
self.gridpanelsizer.Layout()
if __name__ == "__main__":
app = wx.PySimpleApp()
app.frame = GraphFrame()
app.frame.Show()
app.MainLoop()
A great tool to debug this is the WIT (http://wiki.wxpython.org/Widget%20Inspection%20Tool)
With your corrected code I can get it to grow by forcing the sash position, not ideal, but it shows that the 'problem' is with the splitter.
import wx
import wx.lib.scrolledpanel
from controls import SimpleGrid
from controls import ListCtrlComboPopup
class GraphFrame(wx.Frame):
title = 'Demo: Data Trending Tool'
def __init__(self):
self.selection = []
self.displaySize = wx.DisplaySize()
wx.Frame.__init__(self, None, -1, self.title,
style = wx.DEFAULT_FRAME_STYLE,
size = (self.displaySize[0]/2, self.displaySize[1]/2))
self.containingpanel = wx.Panel(self, -1)
self.toppanel = wx.Panel(self, -1)
self.splittedwin = wx.SplitterWindow(self.containingpanel, wx.ID_ANY, style=wx.SP_3D | wx.SP_BORDER)
self.splittedwin.SetMinimumPaneSize(20)
self.gridpanel = wx.lib.scrolledpanel.ScrolledPanel(self.splittedwin,-1, style = wx.SUNKEN_BORDER)
self.panel = wx.lib.scrolledpanel.ScrolledPanel(self.splittedwin,-1, style = wx.SUNKEN_BORDER)
#### GRID
self.grid = SimpleGrid(self.gridpanel)
self.gridpanelsizer= wx.BoxSizer(wx.HORIZONTAL)
self.gridpanelsizer.Add(self.grid, wx.GROW)
self.gridpanel.SetSizer(self.gridpanelsizer)
self.gridpanelsizer.Fit(self)
#### COMBOBOX
self.cc = wx.combo.ComboCtrl(self.toppanel, style=wx.CB_READONLY, size=(200,-1), )
self.cc.SetPopupMaxHeight(140)
popup = ListCtrlComboPopup(self)
self.cc.SetPopupControl(popup)
self.cc.SetText("--select--")
# Add some items to the listctrl
for i in range(10):
popup.AddItem(str(i))
#### SIZER FOR COMBOBOX
self.cbpanelsizer= wx.BoxSizer(wx.HORIZONTAL)
self.cbpanelsizer.Add(self.cc, border = 5,flag = wx.LEFT)
self.toppanel.SetSizer(self.cbpanelsizer)
self.splittedwin.SplitHorizontally(self.gridpanel, self.panel, 50)
##### SIZER FOR CONTAININGPANEL
self.cpsizer = wx.BoxSizer(wx.VERTICAL)
self.cpsizer.Add(self.splittedwin, 1, wx.EXPAND, 0)
self.containingpanel.SetSizer(self.cpsizer)
mainsizer = wx.BoxSizer(wx.VERTICAL)
mainsizer.Add(self.toppanel, 0, wx.EXPAND)
mainsizer.Add(self.containingpanel, 1, wx.EXPAND)
self.SetSizer(mainsizer)
self.panel.SetupScrolling()
self.gridpanel.SetupScrolling()
self.draw_plot()
def draw_plot(self):
for i in range(10):
if i in self.selection:
self.grid.ShowRow(i)
else:
self.grid.HideRow(i)
s = self.grid.GetBestSize()
print(s)
self.splittedwin.SetSashPosition(s[1])
if __name__ == "__main__":
from wx.lib.mixins.inspection import InspectableApp
app = InspectableApp()
app.frame = GraphFrame()
app.frame.Show()
app.MainLoop()