set name of many textctrl itering in range - python

please i want to put several textctrl in a panel and im trying to set the names and positions dynamically, with the following code,controls appears but i cant assing the names.
p=0
for i in range(20):
p += 25
indesc ="ingdesc"
indesc = indesc + str(i)
print indesc
self.HERE I WANT TO PUT indesc value = wx.TextCtrl(self.panel,pos=(280,190+p),size=(350,23),style=wx.TE_READONLY)
thanks

You can do it like this:
import wx
class MainFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title='Indesc')
self.panel = wx.Panel(self)
textctrls=[]
for i in range(20):
textctrls.append("self.indesc"+str(i))
p=0
for i in range(20):
p+=25
textctrls[i] = wx.TextCtrl(self.panel,pos=(10,10+p),size=(350,23),style=wx.TE_READONLY)
textctrls[0].SetValue("Item 0")
textctrls[11].SetValue("Item 11")
self.Show()
if __name__ == '__main__':
app = wx.App()
frame = MainFrame()
app.MainLoop()
But and it's a big BUT you are making life difficult for yourself because you can't access the items by the string name self.indesc11 for example you have to refer to it by the position in the list (as far as I can tell).
It is sensible and clearer for the future, if you are explicit when coding this sort of thing. It's only copy and paste after all.

Related

Python TypeError: CommandEvent.GetString(): too many arguments

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 need to slow down a loop in a python tkinter app

I am having a problem with a fairly simple app.
It performs properly, but I would like it to perform a little slower.
The idea is to randomly generate a name from a list, display it, then remove it fromthe list every time a button is clicked.
To make it a little more interesting, I want the program to display several names before
picking the last one. I use a simple for loop for this. However, the code executes so quickly, the only name that winds up displaying is the last one.
using time.sleep() merely delays the display of the last name. no other names are shown.
here is my code:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from tkinter import *
import random
import time
class Application(Frame):
def __init__(self, master):
""" Initialize the frame. """
super(Application, self).__init__(master)
self.grid()
self.name_list = ["Thorin","Tyler","Jose","Bryson","Joe"]
self.create_widget()
def create_widget(self):
self.lbl = Label(self)
self.lbl["text"] = "Click to spin"
self.lbl["font"] = ("Arial", 24)
self.lbl.grid()
self.bttn = Button(self)
self.bttn["text"]= "Spin"
self.bttn["command"] = self.spin
self.bttn.grid()
def spin(self):
if self.name_list:
for i in range(5):
index = random.randrange(len(self.name_list))
self.lbl["text"] = self.name_list[index]
self.lbl.grid()
self.name_list.pop(index)
else:
self.lbl["text"] = "No more names"
self.lbl.grid()
def main():
root = Tk()
root.title("Click Counter")
root.geometry("600x600")
app = Application(root)
root.mainloop()
if __name__ == '__main__':
main()
This is a pretty common class of problems related to GUI programming. The heart of the issue is the window drawing manager. As long as your function is executing, the drawing manager is frozen; updating the label's text will have no apparent effect until your function ends. So if you have a for loop with a sleep(1) command inside, all it will do is freeze everything for five seconds before updating with your final value when the function finally ends.
The solution is to use the after method, which tells Tkinter to call the specified function at some point in the future. Unlike sleep, this gives the drawing manager the breathing room it requires to update your window.
One possible way to do this is to register six events with after: five for the intermediate name label updates, and one for the final name change and pop.
def spin(self):
def change_name():
index = random.randrange(len(self.name_list))
self.lbl["text"] = self.name_list[index]
self.lbl.grid()
def finish_spinning():
index = random.randrange(len(self.name_list))
self.lbl["text"] = self.name_list[index]
self.lbl.grid()
self.name_list.pop(index)
if self.name_list:
name_changes = 5
for i in range(name_changes):
self.after(100*i, change_name)
self.after(100*name_changes, finish_spinning)
else:
self.lbl["text"] = "No more names"
self.lbl.grid()
(disclaimer: this is only a simple example of how you might use after, and may not be suitable for actual use. In particular, it may behave badly if you press the "spin" button repeatedly while the names are already spinning. Also, the code duplication between change_name and finish_spinning is rather ugly)
The code as it is can show the same item twice since it chooses a new random number each time and so will choose the same number part of the time. Note that you do not pop until after the loop which means that each time you run the program you will have one less name which may or may not be what you want. You can use a copy of the list if you want to keep it the same size, and/or random.shuffle on the list and display the shuffled list in order. Also you only have to grid() the label once,
class Application():
def __init__(self, master):
""" Initialize the frame. """
self.master=master
self.fr=Frame(master)
self.fr.grid()
self.name_list = ["Thorin","Tyler","Jose","Bryson","Joe"]
self.ctr=0
self.create_widget()
def create_widget(self):
self.lbl = Label(self.master width=30)
self.lbl["text"] = "Click to spin"
self.lbl["font"] = ("Arial", 24)
self.lbl.grid()
self.bttn = Button(self.master)
self.bttn["text"]= "Spin"
self.bttn["command"] = self.spin
self.bttn.grid()
def change_label(self):
self.lbl["text"] = self.name_list[self.ctr]
self.ctr += 1
if self.ctr < 5:
self.master.after(1000, self.change_label)
else:
self.ctr=0
def spin(self):
if self.name_list and 0==self.ctr: # not already running
random.shuffle(self.name_list)
self.change_label()
else:
self.lbl["text"] = "No more names"
if __name__ == '__main__':
root = Tk()
root.title("Click Counter")
root.geometry("600x600")
app = Application(root)
root.mainloop()

How do you get checkbox selections from a CustomTreeCtrl

I'm working with a CustomTreeCtrl with checkboxes and I can't figure out how to determine which checkboxes are selected. I looked at http://xoomer.virgilio.it/infinity77/wxPython/Widgets/wx.TreeCtrl.html#GetSelection and put this together:
import string
import os
import sys
import wx
import wx.lib.agw.customtreectrl as CT
class MyFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, -1, "CustomTreeCtrl Demo")
custom_tree = CT.CustomTreeCtrl(self, agwStyle=wx.TR_DEFAULT_STYLE)
root = custom_tree.AddRoot("The Root Item")
for y in range(5):
last = custom_tree.AppendItem(root, "item %d" % y)
for z in range(5):
item = custom_tree.AppendItem(last, "item %d" % z, ct_type=1)
self.Bind(CT.EVT_TREE_ITEM_CHECKED, self.ItemChecked)
def ItemChecked(self, event):
print("Somebody checked something")
print(event.GetSelections())
app = wx.PySimpleApp()
frame = MyFrame(None)
app.SetTopWindow(frame)
frame.Show()
app.MainLoop()
When I check a box, I get the Traceback: "AttributeError: 'TreeEvent' object has no attribute 'GetSelections'" Any suggestions on how to read which boxes are selected would be great!
The event object in question doesn't have a GetSelections method. It does have a GetSelection, which will tell you which item was selected at that event. If you want to get all of the selected items inside ItemChecked, rename custom_tree to self.custom_tree and then you're allowed to call self.custom_tree.GetSelections() inside ItemChecked.
If in future you want to know what kind of methods are available for some event object, you can put print(dir(event)) in your handler.
The custom tree control doesn't have a method to get the checked items. One thing that you could do is create a self.checked_items list in your frame, and maintain it in your ItemChecked method. This list could hold either the string values for the items or the items themselves. For instance,
class MyFrame(wx.Frame):
def __init__(self, parent):
# ....
self.checked_items = []
# ....
def ItemChecked(self, event):
if event.IsChecked():
self.checked_items.append(event.GetItem())
# or to store the item's text instead, you could do ...
# self.checked_items.append(self.custom_tree.GetItemText(event.GetItem()))
else:
self.checked_items.remove(event.GetItem())
# or ...
# self.checked_items.remove(self.custom_tree.GetItemText(event.GetItem()))

Wxpython - how to generate a series of text prompts without closing the app?

I have a text box in wxpython (see below) that stores the name of the value as a variable.
I am trying to do two things:
After the answer is entered, I want to display another question, and assign the new answer to another variable, using the same or an idential TextEntryDialog window.
Ideally, from a user standpoint, they just see a prompt, type an answer (or select from a list), and then after hitting OK, the prompt will change, and they will type in a new answer (which will be assigned to a new variable).
So why am I trying to do this? So that after the end of this Q & A session, I can write all of the variables to a database using pyodbc (which I dont need to know about right now).
So could you please tell me how I can automatically generate new prompts once an answer has been entered without closing the app and losing the variable data? And is there anyway to automatically backup this variable data while the user is answering in case the app crashes? My question list is about 250 questions long, and I dont want all those variables lost if my application crashes (which they tend to do)
Thanks!
import wx
class applicationName(wx.Frame):
def __init__(self, parent, id):
wx.Frame.__init__(self, parent, id, 'Title', size=(300,200))
#create panel and button
panel = wx.Panel(self)
test = wx.TextEntryDialog(None, "What's your name?", 'Title', 'Enter name')
if test.ShowModal() == wx.ID_OK:
apples = test.GetValue()
wx.StaticText(panel, -1, apples, (10,10))
if __name__ =='__main__':
app = wx.PySimpleApp()
frame = applicationName(parent=None, id=-1)
frame.Show()
app.MainLoop()
I don't recommend creating and destroying 250 dialogs like the other fellow did. I would probably create a list or dict at the beginning of my program that would get appended to whenever the user enters an answer. Also in that event handler, I would reset the StaticText control with a new question. You might need to refresh the screen if your questions vary a lot in length, but I think that would be a lot better than showing hundreds of dialogs in a row.
EDIT - Added some example code below:
import wx
class MyForm(wx.Frame):
#----------------------------------------------------------------------
def __init__(self):
wx.Frame.__init__(self, None, wx.ID_ANY, "Tutorial")
# Add a panel so it looks the correct on all platforms
panel = wx.Panel(self, wx.ID_ANY)
self.answers = {}
self.questions = ["What is your age?", "What is your weight?",
"Which of the following computer languages is the best ever: C++, PHP, Fortran, COBOL, Python?"]
self.nextQuestion = 0
self.question = wx.StaticText(panel, label="What is your name?")
self.answer = wx.TextCtrl(panel, value="")
submitBtn = wx.Button(panel, label="Submit")
submitBtn.Bind(wx.EVT_BUTTON, self.onSubmit)
sizer = wx.BoxSizer(wx.VERTICAL)
self.panelSizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.question, 0, wx.ALL, 5)
sizer.Add(self.answer, 0, wx.ALL|wx.EXPAND, 5)
sizer.Add(submitBtn, 0, wx.ALL|wx.CENTER, 5)
panel.SetSizer(sizer)
self.panelSizer.Add(panel, 1, wx.EXPAND)
self.SetSizer(self.panelSizer)
#----------------------------------------------------------------------
def onSubmit(self, event):
""""""
self.answers[self.question.GetLabel()] = self.answer.GetValue()
self.question.SetLabel(self.questions[self.nextQuestion])
self.answer.SetValue("")
self.nextQuestion += 1
print self.answers
self.panelSizer.Fit(self)
# Run the program
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm()
frame.Show()
app.MainLoop()
Write a function that an entry dialog and displays and returns the value the user entered
Put it in a for loop
You can even do something like:
answers = [getanswer(q) for q in questions]
getanswer could look like:
def getanswer(q):
test = wx.TextEntryDialog(None, *q)
if test.ShowModal() == wx.ID_OK:
return test.GetValue() # returns None the user didn't select OK.
questions can contain lists or tuples of the stuff you want to pass to the constructor of wx.TextEntryDialog.

wxPython: how to determine source of an event

I have a Panel with several images on it, each of which is bound to the same event handler. How can I determine which image is being clicked from the event handler? I tried using Event.GetEventObject() but it returns the parent panel instead of the image that was clicked.
Here's some sample code:
import math
import wx
class MyFrame(wx.Frame):
def __init__(self, parent, id=-1,title="",pos=wx.DefaultPosition,
size=wx.DefaultSize, style=wx.DEFAULT_FRAME_STYLE,
name="frame"):
wx.Frame.__init__(self,parent,id,title,pos,size,style,name)
self.panel = wx.ScrolledWindow(self,wx.ID_ANY)
self.panel.SetScrollbars(1,1,1,1)
num = 4
cols = 3
rows = int(math.ceil(num / 3.0))
sizer = wx.GridSizer(rows=rows,cols=cols)
filenames = []
for i in range(num):
filenames.append("img"+str(i)+".png")
for fn in filenames:
img = wx.Image(fn,wx.BITMAP_TYPE_ANY)
img2 = wx.BitmapFromImage(img)
img3 = wx.StaticBitmap(self.panel,wx.ID_ANY,img2)
sizer.Add(img3)
img3.Bind(wx.EVT_LEFT_DCLICK,self.OnDClick)
self.panel.SetSizer(sizer)
self.Fit()
def OnDClick(self, event):
print event.GetEventObject()
if __name__ == "__main__":
app = wx.PySimpleApp()
frame = MyFrame(None)
frame.Show()
app.MainLoop()
In your loop, give each StaticBitmap widget a unique name. One way to do this would be something like this:
wx.StaticBitmap(self, wx.ID_ANY,
wx.BitmapFromImage(img),
name="bitmap%s" % counter)
And then increment the counter at the end. Then in the event handler, do something like this:
widget = event.GetEventObject()
print widget.GetName()
That's always worked for me.
Call GetId() on your event in the handler and compare the id it returns to the ids of your staticBitmaps. If you need an example let me know and Ill update my answer
You can use GetId(), but make sure you keep it unique across your program. I am posting modified code to show how can you do it. Despite of using filenames as list.
def __init__(self, parent, id=-1,title="",pos=wx.DefaultPosition,
size=wx.DefaultSize, style=wx.DEFAULT_FRAME_STYLE,
name="frame"):
wx.Frame.__init__(self,parent,id,title,pos,size,style,name)
self.panel = wx.ScrolledWindow(self,wx.ID_ANY)
self.panel.SetScrollbars(1,1,1,1)
num = 4
cols = 3
rows = int(math.ceil(num / 3.0))
sizer = wx.GridSizer(rows=rows,cols=cols)
#you should use dict and map all id's to image files
filenames = []
for i in range(num):
filenames.append("img"+str(i)+".png")
for imgid,fn in enumerate(filenames):
img = wx.Image(fn,wx.BITMAP_TYPE_ANY)
img2 = wx.BitmapFromImage(img)
#pass the imgid here
img3 = wx.StaticBitmap(self.panel,imgid,img2)
sizer.Add(img3)
img3.Bind(wx.EVT_LEFT_DCLICK,self.OnDClick)
self.panel.SetSizer(sizer)
self.Fit()
def OnDClick(self, event):
print 'you clicked img%s'%(event.GetId() )
You can use dict and map every file name to id, by this way you will keep track of it all through your programe.

Categories

Resources