Manipulating ComboBoxes in wxPython - python

I'm using Python and wxPython to create an UI that lets the user select a XML file in the first combobox and all the components (i.e. buttons) in the XML appears as choices of another combobox below. It's clearly reading correctly as it prints out the right thing in the console as I go through all the XMLs, but I just can't seem to link it back to the combobox I'm looking for.
Here's the code:
import wx
import os
import xml.dom.minidom
from xml.dom.minidom import parse
# get all xmls
path = "C:\Users\William\Desktop\RES\Param"
files = os.listdir(path)
class Panel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.xmlList = files
self.xmlPickerTitle = wx.StaticText(self, label="XML Picker", pos=(20, 30))
self.xmlPicker = wx.ComboBox(self, pos=(100, 30), size=(500, -1), choices=self.xmlList, style=wx.CB_DROPDOWN)
self.elementsTitle = wx.StaticText(self, label="Elements Found", pos=(20, 100))
# labels
self.buttonsPickerTitle = wx.StaticText(self, pos=(20,120), label="Buttons")
self.buttonList = []
self.buttonsPicker = wx.ComboBox(self, pos=(100, 120), size=(250, -1), choices=buttonList, style=wx.CB_DROPDOWN)
self.Bind(wx.EVT_COMBOBOX, self.XMLSelect,)
def XMLSelect(self, event):
xmlPicked = self.xmlList[event.GetSelection()]
DOMTree = xml.dom.minidom.parse(xmlPicked)
collection = DOMTree.documentElement
buttons = DOMTree.getElementsByTagName("Button")
for button in buttons:
if button.hasAttribute("name"):
buttonList.append(button.getAttribute("name"))
print button.getAttribute("name")
app = wx.App(False)
frame = wx.Frame(None, title = "Auto", size = (800, 600))
panel = Panel(frame)
frame.Show()
app.MainLoop()
Any ideas?
Thanks in advance!

I had an issue with the file name not containing the path so I have had to join them to pass into xmlPicked but that might be a difference between linux and Windows.
The key point is to Clear() and Append() to the ComboBox
Also, Bind to a specific ComboBox because you have 2.
Finally, set the selection for the ComboBox so that it is obvious that you have data available.
import wx
import os
import xml.dom.minidom
from xml.dom.minidom import parse
# get all xmls
path = "/home/whatever"
files = os.listdir(path)
class Panel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.xmlList = files
self.xmlPickerTitle = wx.StaticText(self, label="XML Picker", pos=(20, 30))
self.xmlPicker = wx.ComboBox(self, pos=(100, 30), size=(500, -1), choices=self.xmlList, style=wx.CB_DROPDOWN)
self.elementsTitle = wx.StaticText(self, label="Elements Found", pos=(20, 100))
# labels
self.buttonsPickerTitle = wx.StaticText(self, pos=(20,120), label="Buttons")
self.buttonList = []
self.buttonsPicker = wx.ComboBox(self, pos=(100, 120), size=(250, -1), choices=self.buttonList, style=wx.CB_DROPDOWN)
self.xmlPicker.Bind(wx.EVT_COMBOBOX, self.XMLSelect,)
def XMLSelect(self, event):
self.buttonsPicker.Clear()
xmlPicked = self.xmlList[event.GetSelection()]
xmlPicked = os.path.join(path,xmlPicked)
DOMTree = xml.dom.minidom.parse(xmlPicked)
collection = DOMTree.documentElement
buttons = DOMTree.getElementsByTagName("Button")
for button in buttons:
if button.hasAttribute("name"):
button_name = str(button.getAttribute("name"))
self.buttonsPicker.Append(button_name)
print button_name
self.buttonsPicker.SetSelection(0)
app = wx.App(False)
frame = wx.Frame(None, title = "Auto", size = (800, 600))
panel = Panel(frame)
frame.Show()
app.MainLoop()

Related

Dropdown of wxPython combobox doesn't work on popup window

When I put a combo box on wxPython popup window, dropdown function doesn't work.
My example code is this.
import wx
class TestPopup(wx.PopupWindow):
def __init__(self, parent):
"""Constructor"""
wx.PopupWindow.__init__(self, parent = parent)
self.popUp = wx.Panel(self, size = (200,200))
self.popUp.SetBackgroundColour("white")
self.st = wx.StaticText(self.popUp, -1, " Select Comport", pos=(10,10))
self.selCom = wx.ComboBox(self.popUp, -1, pos=(85, 50), choices=["Com1", "Com2"])
self.mySizer = wx.BoxSizer(wx.VERTICAL)
self.mySizer.Add(self.popUp)
self.SetSizerAndFit(self.mySizer)
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, parent = None, title= "ComboBox Test", size = (300,200))
self.panel = wx.Panel(self)
self.selComButton = wx.Button(self.panel, -1, "Select Comport")
self.selComButton.Bind(wx.EVT_BUTTON, self.selectPopUp)
self.selCom = wx.ComboBox(self.panel, -1, pos = (85, 50),choices=["Com1", "Com2"])
def selectPopUp(self, event):
win = TestPopup(self.GetTopLevelParent())
btn = event.GetEventObject()
pos = btn.ClientToScreen((0, 0))
sz = btn.GetSize()
win.Position(pos, (0, sz[1]))
win.Show(True)
if __name__ == "__main__":
app = wx.App()
frame = MainFrame()
frame.Show()
app.MainLoop()
In the code, combo box in main frame works well. But, in the popup window, which is shown when 'select Comport' button is clicked, combobox doesn't work.
What's wrong with this?
It works well.
It doesn't work.
The ComboBox certainly works in a popup window under Linux, so it's difficult to address your question directly. However, I would suggest that in this case, you might well be better served, if you were to use a Dialog rather than a PopUpWindow, as it will do the heavy lifting for you.
For example:
import wx
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, parent = None, title= "Communication Port", size = (300,200))
self.panel = wx.Panel(self)
self.selComButton = wx.Button(self.panel, -1, "Select Comport")
self.selComButton.SetToolTip("Select Comport")
self.selComButton.Bind(wx.EVT_BUTTON, self.selectPopUp)
def selectPopUp(self, event):
dlg = wx.SingleChoiceDialog(None,"Pick a com port", "Com ports",["Com1","Com2","Com3","Com4"],wx.CHOICEDLG_STYLE)
if dlg.ShowModal() == wx.ID_OK:
res = dlg.GetStringSelection()
self.selComButton.SetLabel(res)
dlg.Destroy()
if __name__ == "__main__":
app = wx.App()
frame = MainFrame()
frame.Show()
app.MainLoop()

Calling wx.frame class method for handling an events from a button on frame's panel

I have the following class hierarchy:
wx.Frame derived class; within that frame I have a splitter window, and a wx.Panel based class linked to that splitter window. In the panel I have a button. I am binding an event handler to the button to do some actions. The problem is that most of the actions are supposed to be done within the frame class. So, somehow I have to call a method from the frame class in the button even handler. How can I do that? Relevant part of the code is below.
Cheers
class EPSPanel(wx.Panel):
def __init__(self, parent):
super().__init__(parent)
lbl_SGCR = wx.StaticText(self, label="SGCR", pos=(20, 20))
self.ent_SGCR = wx.TextCtrl(self, value="", pos=(100,20), size=(200,-1))
lbl_SWCR = wx.StaticText(self, label="SWCR", pos=(20, 60))
self.ent_SWCR = wx.TextCtrl(self, value="", pos=(100,60), size=(200,-1))
lbl_SWU = wx.StaticText(self, label="SWU", pos=(20, 100))
self.ent_SWU = wx.TextCtrl(self, value="", pos=(100,100), size=(200,-1))
lbl_SGU = wx.StaticText(self, label="SGU", pos=(20, 140))
self.ent_SGU = wx.TextCtrl(self, value="", pos=(100,140), size=(200,-1))
lbl_SWL = wx.StaticText(self, label="SWL", pos=(20, 180))
self.ent_SWL = wx.TextCtrl(self, value="", pos=(100,180), size=(200,-1))
lbl_SGL = wx.StaticText(self, label="SGL", pos=(20, 220))
self.ent_SGL = wx.TextCtrl(self, value="", pos=(100,220), size=(200,-1))
calc_button = wx.Button(self, label="Calculate", pos=(110,260))
calc_button.Bind(wx.EVT_BUTTON, self.on_btn)
def on_btn(self, event):
self.set_SGCR = float(self.ent_SGCR.GetValue())
self.set_SWCR = float(self.ent_SWCR.GetValue())
self.set_SGU = float(self.ent_SGU.GetValue())
self.set_SWU = float(self.ent_SWU.GetValue())
# these four values in this method I need to pass to the KrFrame class
# instance to process in one of its methods. I also need somehow
# let the frame class know that that button was pressed. How can I do it?
class KrFrame(wx.Frame):
def __init__(self):
super().__init__(parent=None,
title='Gas Relative Permeability Editor', size=(900, 800))
self.sp = wx.SplitterWindow(self)
self.rightSplitter = wx.SplitterWindow(self.sp) #Another splitter to split right panel into two vertical ones
self.leftSplitter = wx.SplitterWindow(self.sp)
self.panel01 = KrPanel(self.leftSplitter)
self.panel02 = PlotPanel(self.rightSplitter)
self.panel03 = EPSPanel(self.rightSplitter) #Third panel for scaled end point entry
self.panel04 = KrPanel(self.leftSplitter)
self.rightSplitter.SplitHorizontally(self.panel02, self.panel03, 400) #Splitting right panel into two horizontally
self.leftSplitter.SplitHorizontally(self.panel01, self.panel04, 400)
self.sp.SplitVertically(self.leftSplitter, self.rightSplitter, 450)
self.create_menu()
self.Show()
if __name__ == '__main__':
app = wx.App(False)
frame = KrFrame()
app.MainLoop()
del app
Thanks Rolf. The issue is with sharing the code is that it already flourished over a number of modules and sharing all of them will result in several metres of scrolling. I found a workaround as below. My understanding is that events are propagated among all parent classes from the child widget. Hence if the button is clicked, wxFrame gets the event too. So, I am doing the bind to an instance of the button class under the constructor of my WxFrame. In the binding I am defining event handling logic. This seems to work:
class KrFrame(wx.Frame):
def __init__(self):
super().__init__(parent=None,
title='Gas Relative Permeability Editor', size=(900, 800))
self.sp = wx.SplitterWindow(self)
self.rightSplitter = wx.SplitterWindow(self.sp) #Another splitter to split right panel into two vertical ones
self.leftSplitter = wx.SplitterWindow(self.sp)
self.panel01 = KrPanel(self.leftSplitter)
self.panel02 = PlotPanel(self.rightSplitter)
self.panel03 = EPSPanel(self.rightSplitter) #Third panel for scaled end point entry
This is where I am binding an handler to the button click event
self.panel03.calc_button.Bind(wx.EVT_BUTTON, self.on_btn)
self.panel04 = KrPanel(self.leftSplitter)
self.rightSplitter.SplitHorizontally(self.panel02, self.panel03, 400) #Splitting right panel into two horizontally
self.leftSplitter.SplitHorizontally(self.panel01, self.panel04, 400)
self.sp.SplitVertically(self.leftSplitter, self.rightSplitter, 450)
self.create_menu()
self.Show()
def on_btn(self, event):
self.set_SGCR = float(self.panel03.ent_SGCR.GetValue())
self.set_SWCR = float(self.panel03.ent_SWCR.GetValue())
self.set_SGU = float(self.panel03.ent_SGU.GetValue())
self.set_SWU = float(self.panel03.ent_SWU.GetValue())
print(self.set_SGCR, self.set_SWCR, self.set_SGU, self.set_SWU)
You can call a function defined within the frame, although to do something useful with the data, is made more difficult because you have defined a splitter window, within a splitter window, within a frame.
For future reference, it's best if you can provide a running version of sample code. Many people who could answer the question, do not want to have to re-hash the code to make it run.
This should give you food for thought.
import wx
class EPSPanel(wx.Panel):
def __init__(self, parent):
super().__init__(parent)
#track the real parent for updating purposes
#
self.parent = parent.GrandParent
#
lbl_SGCR = wx.StaticText(self, label="SGCR", pos=(20, 20))
self.ent_SGCR = wx.TextCtrl(self, value="", pos=(100,20), size=(200,-1))
lbl_SWCR = wx.StaticText(self, label="SWCR", pos=(20, 60))
self.ent_SWCR = wx.TextCtrl(self, value="", pos=(100,60), size=(200,-1))
lbl_SWU = wx.StaticText(self, label="SWU", pos=(20, 100))
self.ent_SWU = wx.TextCtrl(self, value="", pos=(100,100), size=(200,-1))
lbl_SGU = wx.StaticText(self, label="SGU", pos=(20, 140))
self.ent_SGU = wx.TextCtrl(self, value="", pos=(100,140), size=(200,-1))
lbl_SWL = wx.StaticText(self, label="SWL", pos=(20, 180))
self.ent_SWL = wx.TextCtrl(self, value="", pos=(100,180), size=(200,-1))
lbl_SGL = wx.StaticText(self, label="SGL", pos=(20, 220))
self.ent_SGL = wx.TextCtrl(self, value="", pos=(100,220), size=(200,-1))
calc_button = wx.Button(self, label="Calculate", pos=(110,260))
calc_button.Bind(wx.EVT_BUTTON, self.on_btn)
def on_btn(self, event):
self.set_SGCR = float(self.ent_SGCR.GetValue())
self.set_SWCR = float(self.ent_SWCR.GetValue())
self.set_SGU = float(self.ent_SGU.GetValue())
self.set_SWU = float(self.ent_SWU.GetValue())
# these four values in this method I need to pass to the KrFrame class
# instance to process in one of its methods. I also need somehow
# let the frame class know that that button was pressed. How can I do it?
KrFrame.mycalculation(self.parent,self.set_SGCR,self.set_SWCR,self.set_SGU,self.set_SWU)
class KrFrame(wx.Frame):
def __init__(self):
super().__init__(parent=None,
title='Gas Relative Permeability Editor', size=(900, 800))
self.sp = wx.SplitterWindow(self)
self.rightSplitter = wx.SplitterWindow(self.sp) #Another splitter to split right panel into two vertical ones
self.leftSplitter = wx.SplitterWindow(self.sp)
self.panel01 = wx.Panel(self.leftSplitter)
self.Total = wx.TextCtrl(self.panel01)
self.panel02 = wx.Panel(self.rightSplitter)
self.panel03 = EPSPanel(self.rightSplitter) #Third panel for scaled end point entry
self.panel04 = wx.Panel(self.leftSplitter)
self.rightSplitter.SplitHorizontally(self.panel02, self.panel03, 400) #Splitting right panel into two horizontally
self.leftSplitter.SplitHorizontally(self.panel01, self.panel04, 400)
self.sp.SplitVertically(self.leftSplitter, self.rightSplitter, 450)
# self.create_menu()
self.Total.SetValue("00000.00")
self.Show()
def mycalculation(parent,SGCR=0,SWCR=0,SGU=0,SWU=0):
print ("Total of values:",SGCR,SWCR,SGU,SWU,"=",SGCR+SWCR+SGU+SWU)
parent.Total.SetValue(str(SGCR+SWCR+SGU+SWU))
if __name__ == '__main__':
app = wx.App(False)
frame = KrFrame()
app.MainLoop()

How to dynamically position elements in wxPython

I've not been able to find an answer in the documentation. Say I have the following:
import wx
from wx import *
import sys
app = wx.App()
def quitProgram(*args):
sys.exit()
def restart(*args):
app.MainLoop()
xSize = 500
ySize = 300
window = wx.Frame(None, title = "My GUI", size = (xSize,ySize))
panel = wx.Panel(window)
# generic label
labelLeft = wx.StaticText(panel, label = 'some text', pos = (2,30))
# exit button
exit = wx.Button(panel, -1, label="Exit", pos = (1, 1), size=(-1,-1))
exit.Bind(wx.EVT_BUTTON, quitProgram)
# reset button
reset = wx.Button(panel, -1, label="Refresh", pos = (100,1), size=(-1,-1))
reset.Bind(wx.EVT_BUTTON, restart)
window.Show(True)
app.MainLoop()
How can I position the objects- buttons and labels- based on the size of the main window? I'd like the objects to reposition based on resizing the window.
The code posted is not clean wxPython code.
You must instantiate your Frame as a Class and use Sizers to automate positioning of your widgets.
A minimal code that reproduces your frame using Sizers is this:
import wx
class MyFrame(wx.Frame):
def __init__(self, *args, **kwds):
wx.Frame.__init__(self, *args, **kwds)
self.SetSize((500, 300))
self.bt_exit = wx.Button(self, wx.ID_ANY, "exit")
self.bt_refresh = wx.Button(self, wx.ID_ANY, "refresh")
self.text_ctrl = wx.TextCtrl(self, wx.ID_ANY, "some text", style=wx.TE_MULTILINE)
self.SetTitle("My GUI")
self.bt_exit.Bind(wx.EVT_BUTTON, self.on_exit)
self.bt_refresh.Bind(wx.EVT_BUTTON, self.on_refresh)
sizer_1 = wx.BoxSizer(wx.VERTICAL)
sizer_2 = wx.BoxSizer(wx.HORIZONTAL)
sizer_2.Add(self.bt_exit, 1, 0, 0)
sizer_2.Add(self.bt_refresh, 1, 0, 0)
sizer_1.Add(sizer_2, 0, wx.EXPAND, 0)
sizer_1.Add(self.text_ctrl, 1, wx.EXPAND, 0)
self.SetSizer(sizer_1)
self.Layout()
def on_exit(self, evt):
self.Close()
def on_refresh(self, evt):
self.text_ctrl.Clear()
if __name__ == "__main__":
app = wx.App()
frame = MyFrame(None)
frame.Show()
app.MainLoop()
This is well explained in wxPython/Phoenyx docs. Check for example these tutorials

WxPython using Listbox and other UserInput with a Button

I am trying to create a web crawler based on specific user input. For example, the User Input I am trying to receive is from a ListBox and a text field. Once I have that information, I would like the user to click a button to start the search with the information collected.
This is where I have been getting problems. The EVT function doesn't recognize the listbox since its been linked to the Button evt. Is there a way to solve the problem? Can EVT information be shared with other EVTs?
Here is what I have so far:
import wx # for gui
class MyFrame(wx.Frame):
def __init__(self, parent, id):
wx.Frame.__init__(self, parent, id, 'Title', size=(300,200))
tournLevel = ['$10,000', '$15,000', '$20,000', '$50,000','$75,000','$100,000']
levelBox = wx.ListBox(panel, -1, (40, 50), (90, 90), tournLevel)
levelBox.SetSelection(1) # default selection
checkButton = wx.Button(panel, label= "Check Now", pos = (150, 50), size = (90, 40))
self.Bind(wx.EVT_BUTTON, self.OnClick, checkButton)
def OnClick(self, event):
currLevel = event.GetSelection()
print(currLevel) # to test if GetSelection is working
if __name__ == '__main__':
app = wx.App()
frame = MyFrame(parent=None, id=-1)
frame.Show()
app.MainLoop()
I would be very happy if I could just get the button to recognize the ListBox results.
Thank you for your time!
You can just grab it from the listbox, you don't need it from the event. See below:
import wx # for gui
class MyFrame(wx.Frame):
def __init__(self, parent, id):
wx.Frame.__init__(self, parent, id, 'Title', size=(300,200))
tournLevel = ['$10,000', '$15,000', '$20,000', '$50,000','$75,000','$100,000']
self.levelBox = wx.ListBox(panel, -1, (40, 50), (90, 90), tournLevel)
self.levelBox.SetSelection(1) # default selection
self.checkButton = wx.Button(panel, label= "Check Now", pos = (150, 50), size = (90, 40))
self.Bind(wx.EVT_BUTTON, self.OnClick, self.checkButton)
def OnClick(self, event):
currLevel = self.levelBox.GetSelection()
print(currLevel) # to test if GetSelection is working
if __name__ == '__main__':
app = wx.App()
frame = MyFrame(parent=None, id=-1)
frame.Show()
app.MainLoop()
More specifically, if you store levelBox as self.levelBox, it will be accessible inside the OnClick method as a MyFrame attribute. You can then use the GetSelection method for this object (not the event), which will get the current selection.
You can make levelBox into a property of the class by turning it into self.levelBox and accessing it that way as #brettb mentioned. However you can get a bit sneakier and do it using a lambda for your callback to pass the Listbox widget to the event handler:
import wx # for gui
class MyFrame(wx.Frame):
def __init__(self, parent, id):
wx.Frame.__init__(self, parent, id, 'Title', size=(300,200))
panel = wx.Panel(self)
tournLevel = ['$10,000', '$15,000', '$20,000', '$50,000','$75,000','$100,000']
levelBox = wx.ListBox(panel, -1, (40, 50), (90, 90), tournLevel)
levelBox.SetSelection(1) # default selection
checkButton = wx.Button(panel, label= "Check Now", pos = (150, 50), size = (90, 40))
evt = lambda caller, widget=levelBox: self.OnClick(caller, widget)
checkButton.Bind(wx.EVT_BUTTON, evt)
def OnClick(self, event, widget):
currLevel = widget.GetSelection()
print(currLevel) # to test if GetSelection is working
print widget.GetString(currLevel)
if __name__ == '__main__':
app = wx.App()
frame = MyFrame(parent=None, id=-1)
frame.Show()
app.MainLoop()
Also note that you didn't have panel defined, so your original code doesn't work. See the following for more information:
http://wiki.wxpython.org/Passing%20Arguments%20to%20Callbacks

wxpython textctrl line and column

I am writing a text editor using wxpython. I want to display the line & column numbers based on the position of the text pointer(when changed using keyboard or mouse), in the statusbar.
I tried to do it using the below code:
import wx
class TextFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, 'Text Editor', size=(300, 250))
self.panel = wx.Panel(self, -1)
self.multiText = wx.TextCtrl(self.panel, -1,"",size=(200, 100), style=wx.TE_MULTILINE|wx.EXPAND)
sizer = wx.BoxSizer()
sizer.Add(self.multiText, proportion=1, flag=wx.CENTER|wx.EXPAND)
self.panel.SetSizer(sizer)
self.CreateStatusBar()
self.multiText.Bind(wx.EVT_KEY_DOWN, self.updateLineCol)
self.multiText.Bind(wx.EVT_LEFT_DOWN, self.updateLineCol)
def updateLineCol(self, event):
#lineNum = len(self.multiText.GetRange( 0, self.multiText.GetInsertionPoint() ).split("\n"))
l,c = self.multiText.PositionToXY(self.multiText.GetInsertionPoint())
self.StatusBar.SetStatusText(str(l)+","+str(c), number=0)
event.Skip()
app = wx.App(False)
frame = TextFrame()
frame.Show()
app.MainLoop()
I tried the below 2 ways to achieve that:
1.) lineNum = len(self.multiText.GetRange( 0, self.multiText.GetInsertionPoint() ).split("\n"))
2.) l,c = self.multiText.PositionToXY(self.multiText.GetInsertionPoint())
But, both, always give the line & column values of previous position of the pointer instead of current.
Is there a way to display the current line & column values?
I think you just need to bind to EVT_KEY_UP instead of EVT_KEY_DOWN. That way the event handler isn't called until after you have finished typing. I also updated the content written to the statusbar to make it a bit clearer which value was which:
import wx
class TextFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, 'Text Editor', size=(300, 250))
self.panel = wx.Panel(self, -1)
self.multiText = wx.TextCtrl(self.panel, -1,"",size=(200, 100), style=wx.TE_MULTILINE|wx.EXPAND)
sizer = wx.BoxSizer()
sizer.Add(self.multiText, proportion=1, flag=wx.CENTER|wx.EXPAND)
self.panel.SetSizer(sizer)
self.CreateStatusBar()
self.multiText.Bind(wx.EVT_KEY_UP, self.updateLineCol)
self.multiText.Bind(wx.EVT_LEFT_DOWN, self.updateLineCol)
def updateLineCol(self, event):
#lineNum = len(self.multiText.GetRange( 0, self.multiText.GetInsertionPoint() ).split("\n"))
l,c = self.multiText.PositionToXY(self.multiText.GetInsertionPoint())
stat = "col=%s, row=%s" % (l,c)
self.StatusBar.SetStatusText(stat, number=0)
event.Skip()
app = wx.App(False)
frame = TextFrame()
frame.Show()
app.MainLoop()

Categories