I am using Python WX to make a large GUI containing maybe 100 CheckBoxes. I want to read the value of each checkbox and append these values to a list. I can do this with 100 lines of code but prefer to use a loop. In the loop, how can I identify or select the specific checkbox I want to get the value from?
self.Box1 = wx.CheckBox(self.panel, id = 1, label='first box', pos=(10, 25), size=(30,22))
self.Box2 = wx.CheckBox(self.panel, id = 2, label='second box', pos=(20, 25), size=(30,22))
.
.
.
self.Box100 = wx.CheckBox(self.panel, id = 100, label='100th box', pos=(100, 25), size=(30,22))
Looking for something like:
MyList = []
for N in range (1, 101):
MyList.append(self.Box + N.Value)
The more generic question here is "how to select an object name in a loop"
I have searched all day with no luck. I am not a programming expert and hope this is worthy of someone's answer.
Rather than having 100 almost-identical lines of code, which is error-prone, inefficient and unattractive, actually build the CheckBoxes in a loop and hold them in a list:
self.boxes = []
for i in range(1, 101):
self.boxes.append(wx.CheckBox(self.panel, id=i,
label="Box {0}".format(i)
pos=(10, 25), size=(30,22)))
Then getting all of the values is similarly simple:
for i, box in enumerate(self.boxes, 1):
...
as is accessing a single one:
box = self.boxes[i-1]
If you really want "first", "second", "100th" write a helper function to process i into a string representation.
I personally like to use widget names. For example:
import wx
########################################################################
class MyPanel(wx.Panel):
""""""
#----------------------------------------------------------------------
def __init__(self, parent):
"""Constructor"""
wx.Panel.__init__(self, parent)
self.sizer = wx.BoxSizer(wx.VERTICAL)
for i in range(5):
txt = "Checkbox #%s" % i
chk = wx.CheckBox(self, label=txt, name=txt)
self.sizer.Add(chk, 0, wx.ALL|wx.CENTER, 5)
button = wx.Button(self, label="Get check")
button.Bind(wx.EVT_BUTTON, self.onButton)
self.sizer.Add(button, 0, wx.ALL|wx.CENTER, 5)
self.SetSizer(self.sizer)
#----------------------------------------------------------------------
def onButton(self, event):
""""""
widget = self.FindWindowByName("Checkbox #0")
print widget
print widget.GetValue()
########################################################################
class MyFrame(wx.Frame):
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="Checkboxes")
panel = MyPanel(self)
self.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
frame = MyFrame()
app.MainLoop()
This code will create a set of 5 wx.Checkboxes that each have a unique name. Then you can look them up by name using wx.FindWindowByName.
You could also create a dictionary using the names as the keys and the values as the CheckBox widgets which gives the advantage of being a faster lookup.
Related
I am currently making a UI in python using wx. I am trying to have multiple tabs active at the same time, but at the same time I am trying to keep either a list or int value active and showing in the bottom right corner on any tab at all times. Unfortunately I seem to have run into a road block. When I run the code below I get the error:
i = parent.GetString(Points.a)
TypeError: CommandEvent.GetString(): too many arguments
Honestly I am only a year into coding practice and I don't really know what this error means. If possible, could someone please explain it to me and possible give me some tips on how to solve the issue?
Thanks in advance.
import wx
class Points:
c = 14
a = [14]
b = "{}\n" .format(a)
class MOC(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.poi = wx.StaticText(self, label=Points.b, pos=(400, 400))
select = wx.Button(self, wx.ID_ANY, '+1', size=(90, 30))
select.Bind(wx.EVT_BUTTON, self.add)
def add(self, parent):
i = parent.GetString(Points.a)
Points.a.remove(Points.c)
Points.c += 1
Points.a.append(Points.c)
self.poi.SetLabel(i)
class TOS(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
wx.StaticText(self, label=Points.b, pos=(400, 400))
class UIFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, size = (500,500), title = "Mind")
p = wx.Panel(self)
nb = wx.Notebook(p)
page1 = MOC(nb)
page2 = TOS(nb)
nb.AddPage(page1, "Means")
nb.AddPage(page2, "Types")
sizer = wx.BoxSizer()
sizer.Add(nb, 1, wx.EXPAND)
p.SetSizer(sizer)
if __name__ == "__main__":
app = wx.App()
UIFrame().Show()
app.MainLoop()
In the line i = parent.GetString(Points.a) you are passing the argument Points.a but GetString has no arguments because it is used to get the string from an item i.e. self.Item.GetString().
Points.a is not a wx defined item, it is in fact a python list, to access that you should change the offending line above with
i = str(Points.a[0]) or
i = Points.a[0] or
i = Points.a or
i = str(Points.a) depending on your requirements.
Depending on which access method you choose, you may have to alter the
self.poi.SetLabel(i) as well, as i could be a list or an int rather than the required str
Running with the first option works without further changes
I am trying to use wx.Choice component to archieve a simple application.
The idea is, a number of items are listed in the wx.Choice, and each of them will trigger an unique function.
My first version of code was:
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, 0, 'wxPython pull-down choice', size = (400, 300))
panel_select_model= wx.Panel(self, -1)
model_list = ['Test_A', 'Test_B']
self._model_type = None
self._stat_tex = wx.StaticText(panel_select_model, 1, "Select Model Type:", (15, 20))
self._droplist = wx.Choice(panel_select_model, 2, (150, 18), choices = model_list)
""" Bind A Panel """
self._droplist.SetSelection(0)
self._droplist.Bind(wx.EVT_CHOICE, self.Test_A_click)
But it turns out that the two items in the dropdown list will trigger the same function (I expect that the Test_A will trigger that function and Test_B simply do nothing). So I try to bind Test_B to another method Test_B_click
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, 0, 'wxPython pull-down choice', size = (400, 300))
panel_select_model= wx.Panel(self, -1)
model_list = ['Test_A', 'Test_B']
self._model_type = None
self._stat_tex = wx.StaticText(panel_select_model, 1, "Select Model Type:", (15, 20))
self._droplist = wx.Choice(panel_select_model, 2, (150, 18), choices = model_list)
""" Bind A Panel """
self._droplist.SetSelection(0)
self._droplist.Bind(wx.EVT_CHOICE, self.Test_A_click)
""" Bind B Panel """
self._droplist.SetSelection(1)
self._droplist.Bind(wx.EVT_CHOICE, self.Test_B_click)
The above code display a main frame, a drop list in it and two items in the drop list. However, when I click either of the two items, no function is triggered anymore.
So, how can I archieve my goal: Bind different functions to each of the items in a wx.Choice component, and make them trigger functions correctly.
Thanks.
Your methodology is probably the most common way to do what you want and it's pretty easy to tell what's going on as well. However if you do this with a little dash of meta-programming that might be a little cleaner if you set it up correctly:
import wx
########################################################################
class MyFrame(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
wx.Frame.__init__(self, None, title="Callbacks")
panel = wx.Panel(self)
self.choice = wx.Choice(panel, choices=["test_A", "test_B"])
self.choice.Bind(wx.EVT_CHOICE, self.onChoice)
self.Show()
#----------------------------------------------------------------------
def onChoice(self, event):
choice = self.choice.GetStringSelection()
method = getattr(self, "on_%s" % choice)
method()
#----------------------------------------------------------------------
def on_test_A(self):
print "You chose test_A!"
#----------------------------------------------------------------------
def on_test_B(self):
print "You chose test_B!"
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
frame = MyFrame()
app.MainLoop()
Here we use Python's getattr function to determine which method to actually call. This allows us to skip the use of the if statement, but assumes that we have all the methods set up for all the choices in the control.
I am coming back to ask my own question. I don't think this is the perfect solution, but it managed my way out.
Here is the code:
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, 0, 'wxPython pull-down choice', size = (400, 300))
panel_select_model= wx.Panel(self, -1)
model_list = ['Test_A', 'Test_B']
self._model_type = None
self._stat_tex = wx.StaticText(panel_select_model, 1, "Select Model Type:", (15, 20))
self._droplist = wx.Choice(panel_select_model, 2, (150, 18), choices = model_list)
""" Bind A Panel """
self._droplist.SetSelection(0)
self._droplist.Bind(wx.EVT_CHOICE, self.choice_click)
""" Bind B Panel """
self._droplist.SetSelection(1)
self._droplist.Bind(wx.EVT_CHOICE, self.choice_click)
def choice_click(self, event):
if self._droplist.GetStringSelection() == "Test_A":
self.Test_A__click()
elif self._droplist.GetStringSelection() == "Test_B":
self.Test_B_click()
In above code. I put another layer between the wx.Choice component and the functions that I would like to trigger. Once we have a match, its corresponding function will be triggered.
I am afraid this is not an effective way. So I would be extremely appreciate if anyone can bring up a good solution. Thanks a lot.
One of the columns in my ObjectListView contains data that is too long to display completely, so it's automatically truncated. I would like a way to display all the data in the cell. I've already implemented the suggestion in this tutorial, but I'm not very happy with the result because the tooltip is for the entire list, not just that list item. Ideally, I'd like to have exactly what's pictured in the first image of this other question.
That post mentions that the expansion-on-hover behavior happens automatically, but I don't see any way of recreating that behavior. Perhaps it only happens on Windows? I'm running on GTK.
You could always implement an on right click pop-up window that you can populate with whatever you would like - see the wxPython Demo for Pop-up windows as a starting point.
Alternatively you could switch from ObjectListView to wx.lib.agw.UltimateListCtrl or DataViewControl - both of these seem to support tooltips that are aware of which row and column you are hovering over.
I messed around with the code in my tutorial using the answer you had mentioned and came up with the following:
import wx
from ObjectListView import ObjectListView, ColumnDefn
########################################################################
class Book(object):
"""
Model of the Book object
Contains the following attributes:
'ISBN', 'Author', 'Manufacturer', 'Title'
"""
#----------------------------------------------------------------------
def __init__(self, title, author, isbn, mfg):
self.isbn = isbn
self.author = author
self.mfg = mfg
self.title = title
########################################################################
class MainPanel(wx.Panel):
#----------------------------------------------------------------------
def __init__(self, parent):
wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY)
self.products = [Book("wxPython in Action", "Robin Dunn",
"1932394621", "Manning"),
Book("Hello World", "Warren and Carter Sande",
"1933988495", "Manning")
]
self.dataOlv = ObjectListView(self, wx.ID_ANY,
style=wx.LC_REPORT|wx.SUNKEN_BORDER)
self.dataOlv.Bind(wx.EVT_MOTION, self.updateTooltip)
self.setBooks()
# Allow the cell values to be edited when double-clicked
self.dataOlv.cellEditMode = ObjectListView.CELLEDIT_SINGLECLICK
# create an update button
updateBtn = wx.Button(self, wx.ID_ANY, "Update OLV")
updateBtn.Bind(wx.EVT_BUTTON, self.updateControl)
# Create some sizers
mainSizer = wx.BoxSizer(wx.VERTICAL)
mainSizer.Add(self.dataOlv, 1, wx.ALL|wx.EXPAND, 5)
mainSizer.Add(updateBtn, 0, wx.ALL|wx.CENTER, 5)
self.SetSizer(mainSizer)
#----------------------------------------------------------------------
def updateControl(self, event):
"""
Update the control
"""
print "updating..."
#product_dict = [{"title":"Core Python Programming", "author":"Wesley Chun",
#"isbn":"0132269937", "mfg":"Prentice Hall"},
#{"title":"Python Programming for the Absolute Beginner",
#"author":"Michael Dawson", "isbn":"1598631128",
#"mfg":"Course Technology"},
#{"title":"Learning Python", "author":"Mark Lutz",
#"isbn":"0596513984", "mfg":"O'Reilly"}
#]
product_list = [Book("Core Python Programming", "Wesley Chun",
"0132269937", "Prentice Hall"),
Book("Python Programming for the Absolute Beginner",
"Michael Dawson", "1598631128", "Course Technology"),
Book("Learning Python", "Mark Lutz", "0596513984",
"O'Reilly")
]
data = self.products + product_list
self.dataOlv.SetObjects(data)
#----------------------------------------------------------------------
def setBooks(self, data=None):
"""
Sets the book data for the OLV object
"""
self.dataOlv.SetColumns([
ColumnDefn("Title", "left", 220, "title"),
ColumnDefn("Author", "left", 200, "author"),
ColumnDefn("ISBN", "right", 100, "isbn"),
ColumnDefn("Mfg", "left", 180, "mfg")
])
self.dataOlv.SetObjects(self.products)
#----------------------------------------------------------------------
def updateTooltip(self, event):
"""
"""
pos = wx.GetMousePosition()
mouse_pos = self.dataOlv.ScreenToClient(pos)
item_index, flag = self.dataOlv.HitTest(mouse_pos)
print flag
if flag == wx.LIST_HITTEST_ONITEMLABEL:
msg = "%s is a good book!" % self.dataOlv.GetItemText(item_index)
self.dataOlv.SetToolTipString(msg)
else:
self.dataOlv.SetToolTipString("")
event.Skip()
########################################################################
class MainFrame(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, parent=None, id=wx.ID_ANY,
title="ObjectListView Demo", size=(800,600))
panel = MainPanel(self)
#----------------------------------------------------------------------
def main():
"""
Run the demo
"""
app = wx.App(False)
frame = MainFrame()
frame.Show()
app.MainLoop()
#----------------------------------------------------------------------
if __name__ == "__main__":
main()
I also updated my tutorial to include the code above. Please note that the tooltip only updates when you mouse through the cells in the first column. I haven't found a good way around that.
If you run this code, you will get default values from a combo box on Panel One, but after changing the values on Panel 2, the values being called from the Panel 1 button do not reflect the change in values on Panel 2.
import wx
class PanelOne(wx.Panel):
def __init__(self, parent, id):
wx.Panel.__init__(self, parent, id)
self.button1 = wx.Button(self, -1, label="Panel 1 Test",
pos=wx.Point(100, 100))
self.button1.Bind(wx.EVT_BUTTON, self.button1Click, self.button1)
def button1Click(self,event):
p2=PanelTwo(self, 0)
print "Panel 1: %s" % p2.C1
print "Panel 1: %s" % p2.C2
class PanelTwo(wx.Panel):
def __init__(self, parent, id):
wx.Panel.__init__(self, parent, id)
# Default Declarations
self.C1 = List1[0]
self.C2 = List2[-1]
self.combo1 = wx.ComboBox(self, -1, value=List1[0], pos=wx.Point(100, 30),
size=wx.Size(100, 150), choices=List1, style = wx.CB_READONLY)
self.Bind(wx.EVT_COMBOBOX, self.EvtCmb1, self.combo1)
self.combo2 = wx.ComboBox(self, -1, value=List2[-1], pos=wx.Point(300, 30),
size=wx.Size(100, 150), choices=List2, style = wx.CB_READONLY)
self.Bind(wx.EVT_COMBOBOX, self.EvtCmb2, self.combo2)
self.button1 = wx.Button(self, -1, label="Panel 2 Test",
pos=wx.Point(100, 100))
self.button1.Bind(wx.EVT_BUTTON, self.button1Click, self.button1)
def button1Click(self,event):
print "Panel 2: %s" % self.C1
print "Panel 2: %s" % self.C2
def EvtCmb1(self, event):
combo1 = (event.GetString())
self.C1 = combo1
def EvtCmb2(self, event):
combo2 = (event.GetString())
self.C2 = combo2
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title="Testing 123", size = (500, 400) )
p = wx.Panel(self)
nb = wx.Notebook(p)
panel1 = PanelOne(nb,-1)
panel2 = PanelTwo(nb,-1)
nb.AddPage(panel1, "Page One")
nb.AddPage(panel2, "Page Two")
sizer = wx.BoxSizer()
sizer.Add(nb, 1, wx.EXPAND)
p.SetSizer(sizer)
#Combo1
List1 = ['Apple', 'Banana']
#Combo2
List2 = ["1", "2", "3", "4"]
app = wx.App(redirect=False)
frame = MainFrame()
frame.Show(True)
app.MainLoop()
I find your code confusing, but I think I found the problem. Consider your PanelOne.button1Click() function:
def button1Click(self,event):
p2=PanelTwo(self, 0)
print "Panel 1: %s" % p2.C1
print "Panel 1: %s" % p2.C2
You're creating a new instance of PanelTwo every time, meaning it will always have the default values. Think of classes like makes and models of cars. I can have a Toyota Camry and you can have a Toyota Camry. They are the same type (class) of car, but they are seperate cars (different instances). Any change I make to my Camry obviously won't affect yours since they are different objects entirely. So following this analogy, everytime PanelOne.button1Click() is called, it buys a new Camry (creates a new instance); it doesn't get an already existing one. That new car comes fresh from the factory with all the default options regardless of how other owners modify their cars.
So basically, your panel1 instance and your panel2 instance aren't talking to each other at all, so panel1 can't know anything about the state of panel2. You have a few ways you can solve this. The easiest way is to pass panel2 as an init argument for panel1. Like this:
class PanelOne(wx.Panel):
def __init__(self, parent, id, p2):
wx.Panel.__init__(self, parent, id)
self.p2 = p2 #store panel2 so we can check it later
#do the rest of your setup
def button1Click(self,event):
print "Panel 1: %s" % self.p2.C1
print "Panel 1: %s" % self.p2.C2
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title="Testing 123", size = (500, 400) )
panel2 = PanelTwo(nb,-1)
panel1 = PanelOne(nb,-1, panel2) #panel1 needs to be able to read panel2's settings
#do the rest of your setup
This still isn't a great solution. Generally, I'd try to come up with something using PubSub to pass the information between classes without causing a dependency (i.e. note how PanelOne can never exist wthout PanelTwo, even in your original code), but I think that's overly complicated for your purposes.
I need a ListBox to show a phone book.Then i need to show name in top and number in bottom in each list item like phone.how to bind the datas into listbox.
now i made a listbox with singleline as shown below
cur.execute("select fname from tblsample1 order by fname")
names = [str(item[0]) for item in cur.fetchall()]
lvnames=wx.ListBox(panel,-1,(10,40),(210,180),names, wx.LB_SINGLE)
how to bind sqlite3 cursor with two columns to the listview
i need a wx.ListBox mouse click event(not EVT_LISTBOX
because i need only mouse click event)
Use the HtmlListBox, here is a little example to get you started.
import wx
class PhoneNumbers(wx.HtmlListBox):
def __init__(self, parent):
wx.HtmlListBox.__init__(self, parent)
self.data = [
("Foo", "3452-453"),
("Bar", "5672-346"),
]
self.SetItemCount(len(self.data))
def OnGetItem(self, n):
return "<b>%s</b><br>%s" % self.data[n]
def add_number(self, name, number):
self.data.append((name, number))
self.SetItemCount(len(self.data))
self.Refresh()
class Frame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, size=(200, 400))
self.numbers = PhoneNumbers(self)
self.contact_name = wx.TextCtrl(self)
self.contact_number = wx.TextCtrl(self)
self.add_btn = wx.Button(self, label="Add contact")
self.Sizer = wx.BoxSizer(wx.VERTICAL)
self.Sizer.Add(self.numbers, 1, wx.EXPAND)
self.Sizer.Add(wx.SearchCtrl(self), 0, wx.EXPAND)
self.Sizer.Add(wx.StaticText(self, label="Name"), 0, wx.TOP, 10)
self.Sizer.Add(self.contact_name)
self.Sizer.Add(wx.StaticText(self, label="Number"), 0, wx.TOP, 5)
self.Sizer.Add(self.contact_number)
self.Sizer.Add(self.add_btn, 0, wx.ALL, 10)
self.numbers.Bind(wx.EVT_LISTBOX, self.OnSelectNumber)
self.add_btn.Bind(wx.EVT_BUTTON, self.OnAddNumber)
def OnSelectNumber(self, event):
name, number = self.numbers.data[event.Selection]
self.contact_name.Value = name
self.contact_number.Value = number
def OnAddNumber(self, event):
self.numbers.add_number(
self.contact_name.Value,
self.contact_number.Value
)
app = wx.PySimpleApp()
app.TopWindow = f = Frame()
f.Show()
app.MainLoop()
You should rephrase your question, I don't know if I got this right.
If you only need to display the two lines in your ListBox, you could simply use a \n:
cur.execute("select fname,number from tblsample1 order by fname")
entries = [str(item[0])+'\n'+str(item[1]) for item in cur.fetchall()]
To get a 'click' Event, you cant set the style of your wx.ListBox to wx.LC_SINGLE_SEL and catch the selection event wx.EVT_LIST_ITEM_SELECTED