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
Related
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()
import wx
class FrontMenu(wx.Frame):
def __init__(self, parent, title):
super(FrontMenu, self).__init__(parent, title=title, size=(400, 300))
self.InitUI()
self.Centre()
self.Show()
def InitUI(self):
menubar = wx.MenuBar()
fileMenu = wx.Menu()
menubar.Append(fileMenu, '&File')
self.SetMenuBar(menubar)
vbox = wx.BoxSizer(wx.VERTICAL)
gs = wx.GridSizer(4, 1, 0, 0)
gs.AddMany([
(wx.Button(self, label='Create New Audit.'), 0, wx.EXPAND),
(wx.Button(self, label='View Previous Audits.'), 0, wx.EXPAND),
(wx.Button(self, label='Add New Engineer.'), 0, wx.EXPAND),
(wx.Button(self, label='Close Application.'), 0, wx.EXPAND)
])
When using the AddMany method how do I capture button events?
I can achieve this when I add the buttons 1 at a time.
Thanks
Paul
Assign ids to your buttons:
wx.Button(self, id=SOME_INTEGER, label=...
Add an event handler:
self.Bind(wx.EVT_BUTTON, self.on_button)
and in the handler check the id of the event source:
def on_button(self, evt):
evt.Skip()
if SOME_INTEGER == evt.GetId():
# do something.
That pointed me in the right direction. Below is the code that now works.
Thanks
class Frame(wx.Frame):
def __init__(self, title):
wx.Frame.__init__(self, None, title=title, pos=(150,150), size=(400,300))
self.Bind(wx.EVT_CLOSE, self.OnClose)
menuBar = wx.MenuBar()
menu = wx.Menu()
m_exit = menu.Append(wx.ID_EXIT, "E&xit\tAlt-X", "Close window and exit program.")
self.Bind(wx.EVT_MENU, self.OnClose)
self.Bind(wx.EVT_MENU, self.OnClose, m_exit)
menuBar.Append(menu, "&File")
menu = wx.Menu()
m_about = menu.Append(wx.ID_ABOUT, "&About", "Information about this program")
self.Bind(wx.EVT_MENU, self.OnAbout, m_about)
menuBar.Append(menu, "&Help")
self.SetMenuBar(menuBar)
self.statusbar = self.CreateStatusBar()
panel = wx.Panel(self)
box = wx.GridSizer(2, 2)
m_new_audit = wx.Button(panel, wx.NewId(), "New Audit.")
m_new_audit.Bind(wx.EVT_BUTTON, self.NewAudit)
m_view_audit = wx.Button(panel, wx.NewId(), "View Previous Audits.")
m_view_audit.Bind(wx.EVT_BUTTON, self.ViewAudit)
m_add_engineer = wx.Button(panel, wx.NewId(), "Add New Engineer.")
m_add_engineer.Bind(wx.EVT_BUTTON, self.AddEngineer)
m_close = wx.Button(panel, wx.NewId(), "Close.")
m_close.Bind(wx.EVT_BUTTON, self.OnClose)
box.Add(m_new_audit, 10, wx.EXPAND, 10)
box.Add(m_view_audit, 10, wx.EXPAND, 10)
box.Add(m_add_engineer, 10, wx.EXPAND, 10)
box.Add(m_close, 10, wx.EXPAND, 10)
panel.SetSizer(box)
panel.Layout()
def OnClose(self, event):
dlg = wx.MessageDialog(self,
"Do you really want to close this application?",
"Confirm Exit", wx.OK|wx.CANCEL|wx.ICON_QUESTION)
result = dlg.ShowModal()
dlg.Destroy()
if result == wx.ID_OK:
self.Destroy()
def NewAudit(self, event):
dlg = wx.MessageDialog(self,
"Create New Audit?",
"Confirm?", wx.OK|wx.CANCEL|wx.ICON_QUESTION)
result = dlg.ShowModal()
if result == wx.ID_OK:
event = "New Audit"
print event
dlg.Destroy()
def ViewAudit(self, event):
dlg = wx.MessageDialog(self,
"View Previous Audit?",
"Confirm?", wx.OK|wx.CANCEL|wx.ICON_QUESTION)
result = dlg.ShowModal()
if result == wx.ID_OK:
event = "Previous Audit"
print event
dlg.Destroy()
def AddEngineer(self, event):
dlg = wx.MessageDialog(self,
"Add New Engineer?",
"Confirm?", wx.OK|wx.CANCEL|wx.ICON_QUESTION)
result = dlg.ShowModal()
if result == wx.ID_OK:
event = "New Engineer"
print event
dlg.Destroy()
def OnAbout(self, event):
dlg = AboutBox()
dlg.ShowModal()
dlg.Destroy()
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()
I am coding with Python 2.7, and I am trying to change the background bitmap image based on a user's radio button selection from a drop down menu in a different window. In the code attached, the Bitmap0 image is the default. By selecting "Photo" menu, then the "Change Photo" menu item, causes a radio box to appear. I want to be able to select Bitmap1 and have the bitmap image change to the Bitmap1 image.
I have unsuccessfully tried pubsub, and I was never confident with how the pubsub module fit into Python. Isn't this possible through accessing the right variables in the right way?
My simplified code is as follows:
import wx
IPaa0 = 0
bgphoto = "bitmap" + str(IPaa0) + ".bmp"
print bgphoto
_ID_Item0 = wx.NewId()
_ID_Item1 = wx.NewId()
_ID_Item2 = wx.NewId()
_ID_Item3 = wx.NewId()
_ID_Item4 = wx.NewId()
_ID_Item5 = wx.NewId()
class cPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
def TogFot(self, fotnum):
print "TogFot Entered"
class aFotoDir(wx.App):
def OnInit(self):
iMainFrame = cMainFrame("Top Frame", (0, 0), (1200, 900))
iMainFrame.Show()
self.SetTopWindow(iMainFrame)
return True
class cMainFrame(wx.Frame):
def __init__(self, title, pos, size):
wx.Frame.__init__(self, None, -1, title, pos, size)
iPanel = cPanel(self)
MenuBar = wx.MenuBar()
mFile = wx.Menu()
MenuBar.Append(mFile, "File")
mFoto = wx.Menu()
MenuBar.Append(mFoto, "Photo")
self.SetMenuBar(MenuBar)
MenuItem0 = wx.Menu()
mFile.Append(_ID_Item0, "Open")
MenuItem1 = wx.Menu()
mFile.Append(_ID_Item1, "Save")
MenuItem2 = wx.Menu()
mFile.Append(_ID_Item2, "Save As")
MenuItem3 = wx.Menu()
mFile.Append(_ID_Item3, "Close")
MenuItem4 = wx.Menu()
mFile.Append(_ID_Item4, "Exit")
MenuItem5 = wx.Menu()
mFoto.Append(_ID_Item5, "Change Photo")
self.Bind(wx.EVT_MENU, self.ClickChFoto, id = _ID_Item5)
img0 = wx.Image(bgphoto, wx.BITMAP_TYPE_ANY)
img1 = wx.StaticBitmap(iPanel, -1, wx.BitmapFromImage(img0))
def ClickChFoto(self, event):
iSecondFrame = cSecondFrame("Input", (50, 0), (400, 300))
iSecondFrame.Show()
return True
class cSecondFrame(cMainFrame):
def __init__(self, title, pos, size):
wx.Frame.__init__(self, None, -1, title, pos, size)
iSecondPanel = wx.Panel(self, -1)
iBut0 = wx.Button(iSecondPanel, -1, "OK", pos = ( 75, 200))
iBut1 = wx.Button(iSecondPanel, -1, "CANCEL", pos = (225, 200))
Plist = ["Bitmap0", "Bitmap1"]
self.iWid0 = wx.RadioBox(iSecondPanel, -1, "Photo Selection", (50, 50),
(200, 100), Plist, 1, wx.RA_SPECIFY_COLS)
self.Bind(wx.EVT_BUTTON, self.ClickOK, iBut0)
self.Bind(wx.EVT_BUTTON, self.ClickCANCEL, iBut1)
def ClickOK(self, event):
print "OK Clicked"
global IPaa0
IPaa0 = self.iWid0.GetSelection()
print IPaa0
global bgphoto
bgphoto = "bitmap" + str(IPaa0) + ".bmp"
print bgphoto
self.Close(True)
def ClickCANCEL(self, event):
print "CANCEL Clicked"
self.Close(True)
#-------------------------------------------------------------------------------
if __name__ == "__main__":
app = aFotoDir(False)
app.MainLoop()
The image files are just two files with the titles "Bitmap0" and "Bitmap1". I didn't know the protocol for uploading images, and they were each over 6 MB in size, so I didn't upload.
I would appreciate any guidance and instruction.
You need to pass a reference of cFrame to cSecondFrame as a parameter to the __init__ of cSecondFrame. Then from within its def ClickOK load the new bitmap as before and set it in img1 and call the Layout() function of cFrame.