Let's say that I have the following code:
root = Tk()
open = Button(self.root, text='Open', command=self.open, state='disabled')
open.pack()
close = Button(self.root, text='Close', command=self.close, state='disabled')
close.pack()
I only want to enable the buttons when some action is performed, so I need to access the buttons again later to edit the state variable. Rather than adding the buttons to a separate list and storing that, is there a way of accessing the buttons, or, for that matter, any set of objects that I have attached to the root (Menus, Drop down lists, or whatever), by calling a method on the root?
There is no definitive way to ask the root window for a list of all widgets. You can use pack_slaves or grid_slaves to get a list of widgets managed by a particular container, but depending on how you write your app that's no guarantee you'll get all the widgets.
You can also use winfo_children to get a list of all direct descendants of a widget. If you have a nested hierarchy of widgets (for example, by using frames as intermediary containers for organizational purpose) you may have to do some sort of looping to find a particular widget.
The best and simplest approach is to have your application be an instance of a class. You can then save references to the widgets as attributes of the class. I highly recommend this approach, there's simply no good reason to do it any other way.
For example:
class MyApp(Tk):
def __init__(self, *args, **kwargs):
...
self.open_button = Button(...)
self.close_button = Button(...)
...
def OnSomeEvent(self, event):
if ...:
self.open_button.configure(state="disabled")
else:
self.open_button.configure(state="normal")
Since you are using the pack method you can use the pack_slaves method to find items added. So to iterate over them you can do something like,
for item in root.pack_slaves():
item.do_stuff()
These will be in the root's children dictionary, but with a long int (the value of the pointer in the C layer I believe) as the key. (with newlines added in the dict value for readability)
> >>> from Tkinter import Tk, Button
> >>> root=Tk()
> >>> open=Button(root, text='Open')
> >>> root.__dict__
> {'_tclCommands': ['tkerror', 'exit', '3077241668Ldestroy'],
> 'master': None,
> 'children': {'3077328108L': <Tkinter.Button instance at 0xb76c4cec\>},
> '_tkloaded': 1,
> 'tk': <tkapp object at 0xb76bd560\>}
Related
Is this written technically ok?
Will it cause any problems. I cannot find any info about this way of constructing code for tkinter, however it works..
myButton = tkinter.Button(main_window)
myButton['text'] = "Click Me!"
myButton['state'] = "normal"
myButton['command'] = myClick
myButton['fg'] = "blue"
instead of:
myButton = tkinter.Button(main_window,text="Click Me!",state="normal", command=myClick,fg="blue")
If someone think why, just because code looks neater to me.
What you have written will work but if your don't like the presentation of the standard syntax, you could always do something like this:
myButton = tkinter.Button(main_window,
text="Click Me!",
state="normal",
command=myClick,
fg="blue")
According to the docs it is a legitim way to configure your widgets.
The reason why this works is because of the mapping protocol that is used by tkinter. As an example you can see here how it works and there is no danger in usage of it:
class Unpackable(object):
def __init__(self):
self.options=['hello','world']
def keys(self):
return self.options
def __getitem__(self, key):
return dict(zip(self.options,'first_word last_word'.split()))[key]
unp = Unpackable()
print(unp['hello'])
Output:
first_word
The offical python documentation says to setting options:
Options control things like the color and border width of a widget.
Options can be set in three ways:
At object creation time, using keyword arguments
fred = Button(self, fg="red", bg="blue")
After object creation, treating the option name like a dictionary
index
fred["fg"] = "red"
fred["bg"] = "blue"
Use the config() method to update multiple attrs subsequent to object
creation
fred.config(fg="red", bg="blue")
This is correct.i mean to say you have just defined variable of button then you are adding attributes of button
In following way all attributes are called in one statement only.
myButton = tkinter.Button(main_window,text="Click Me!",state="normal",command=myClick,fg="blue")
But you have called all attributes by variable.it just take more Lines.
And technically what you have written is fine
I have several widgets created in a loop, each given a sequential name, eg:
for item in itemlist:
myWidget=tk.Widget(root, name=item)
myWidget.pack()
Now I have widgets with pathnames like
.!mainapplication.!itemframe.item1
is it possible to use the pathname to destroy a widget?
I can't use myWidget.destroy() because then I can't target specific widgets.
If it's not possible I can create an array to hold the widgets and deal with them from there, but I was wondering if there was any way to do it this way.
If you save the return value of tk.Widget(...) to a list or dictionary, you can access any of the widgets without having to use the name. This is by far the most common and convenient way to manage widgets created in a loop.
Example:
widgets = []
for item in itemlist:
myWidget=tk.Widget(root, name=item)
myWidget.pack()
widgets.appen(widget)
...
for widget in widgets:
widget.destroy()
If you really need to convert a name to a widget, tkinter provides a method on every widget called nametowidget which accepts the name of a widget and returns the instance.
root.nametowidget('.!mainapplication.!itemframe.item1').destroy()
I have a very repetitive GUI to implement - think tens of label/text/button fields, for a data entry form. The sizes of each repeated section can be the same - in fact, everything can be the same except the text in the label and the variable that the data from the text field is assigned to upon completion.
I've worked with an engineer who used lambda functions to generate sub-functions in a very complex way which I almost followed, but not quite 100%. I was hoping, since this is a similar, mostly repetitive task, that there was some way to use a formulaic function to repeat the GUI creation work for me, rather than to have to type out each and every GUI item.
Is it possible to have repetitive GUI elements generated by a function, and if so, is that a lambda function? Or is there a different (or better) way to accomplish the same "not repeating myself"?
Is it possible to have repetitive GUI elements generated by a function, and if so, is that a lambda function?
Yes, it's possible to create gui elements with a function, and no, it's not a lambda function. Not only is it possible, it's arguably a best practice to create gui elements in a function, though you could also just use a simple loop or a conventional function.
When creating groups of widgets that are somehow tied together, it's even better to create a custom class that can encapsulate all of the behavior and provide a simple interface for the rest of the program.
Example
In the following example, we want to create a series of widgets with a label, an entry, and a submit button. It is going to be implemented as a class, since we are in effect creating an object that represents one form field.
Note: the following code assumes you imported tkinter with import tkinter as tk.
First, lets create a callback that sets a value in a dictionary, and also prints out the value for debugging purposes. The callback will be given the name of the field, and the value that the user entered:
data = {}
def handle_submit(name, value):
print("you submitted '%s' for %s" % (value, name))
data[name] = value
Next, the code to create 10 items would might look like this:
root = tk.Tk()
for i in range(1, 11):
field_name = "field %s" % i
row = CustomWidget(root, name=field_name, callback=handle_submit)
Finally, we need to create our class. We inherit from tk.Frame so that we can use it like any other tkinter widget. It needs to take parameters for its parent, the field name, and the function to call when the user presses submit:
class CustomWidget(tk.Frame):
def __init__(self, parent, name, callback):
tk.Frame.__init__(self, parent)
self.name = name
label_text = name.title() + ":"
self.callback = callback
self.label = tk.Label(self, text=label_text, anchor="e")
self.entry = tk.Entry(self)
self.button = tk.Button(self, text="Submit", command=self.submit)
self.button.pack(side="right")
self.label.pack(side="left")
self.entry.pack(side="left", fill="x", expand=True)
def submit(self):
self.callback(self.name, self.entry.get())
CODE: http://pastebin.com/W4uXmazw
I would like to memorize how to get values from any wx widget with event handling after clicking a wx.Button.
In my program i have two fields, the new filename and the contents.
What are the steps i have to take in order to get the values from each field?
From there, i can use pythons f.open and f.write methods to complete my application.
Thanks!
If you want to get value of a widget, then you need to make that widget accessible throughout the entire class. To do that, you need to make the variable for the widget into an instance variable. So instead of adding the text control directly to the sizer, you'll want to do something like this:
self.newfilename = wx.TextCtrl(panel,-1), 0, wx.TOP, 5)
self.contents = wx.TextCtrl(panel,-1,size=(390,150),style = wx.TE_MULTILINE|wx.TE_PROCESS_TAB)
Then in your button's event handler, you can just do something like this:
valueOne = self.newfilename.GetValue()
contents = self.contents.GetValue()
The other way to do it would be to use your panel. If you use "self.panel", then you could grab all its children via its GetChildren method and then iterate over the list and use Python's "isinstance" builtin to check what kind of widget you're accessing. If you have set the widget's name, you can check that too.
What is the approach to update widgets in a wxPanel based on events from other controls on same panel?
Scenario 1 is updating the list of a comboBox based on what has been selected from another comboBox , where both are in same panel.
Scenario 2 is showing a new control/widget in a panel based on an event.
Basically creating new controls is easy but I dont know how to refresh/update my panel so immedialtly shows them.
Scenario 1
To change the choices of a combobox self.cbx you can use any of the following methods:
self.cbx.SetItems(choices) where choices is the full list of choices.
self.cbx.SetString(n, string) that sets the string at position n.
InsertItems(items, pos) Inserts the list of strings in the items argument into the list box before the position in the pos argument.
Note that the method Set(choices) of listboxes does not exist for the list in comboboxes. You must use SetItems(choices) instead (this is not clearly indicated in some textbooks).
If you want these changes to occur as a result of a selection in another combobox self.cbx_1 , just get the event (self.Bind(wx.EVT_COMBOBOX, on_combo_1, self.cbx_1)) of the first combobox, process your data as you like in the corresponding self.on_combo method and use one of the above methods to modify the second combobox.
For example:
def on_combo_1(self, evt):
"append cbx_1 selection to cbx if not already in cbx"
selection = self.cbx_1.GetStringSelection()
cbx_choices = self.cbx.GetItems()
if selection not in cbx_choices:
cbx_choices.append(selection)
self.cbx.SetItems(cbx_choices)
The fact the comboboxes are in the same or different panel is irrelevant for that.
Scenario 2
Normally you put your widgets inside sizers. To hide or made visible elements on the sizer you call the methods Show, Hide or Layout:
Show(self, item, show=True, recursive=false)
Shows or hides an item managed by the sizer. To make a sizer item disappear or reappear, use Show followed by Layout. The item parameter can be either a window, a sizer, or the zero-based index of the item. Use the recursive parameter to show or hide an item in a subsizer. Returns True if the item was found.
Hide(self, item, recursive)
A convenience method for Show (item, False, recursive).
Layout(self)
This method will force the recalculation and layout of the items controlled by the sizer using the current space allocated to the sizer. Normally this is called automatically from the owning window's EVT_SIZE handler, but it is also useful to call it from user code when one of the items in a sizer change size, or items are added or removed.
References: wxPython in Action, Noel Rappin and Robin Dunn
For scenario one, you'd do something like the following (assuming the first combobox is bound to its EVT_COMBOBOX:
value = self.cboOne.GetValue()
if value == "something":
self.cboTwo.SetItems(someList)
For showing a new widget, you could create it and then use Show()/Hide() as necessary. If the widget is in a sizer, then use the Sizer's Append or Insert methods. It also has a Detach method that can be used to hide widgets or you just call Hide itself. See the documentation for more information: http://www.wxpython.org/docs/api/wx.Sizer-class.html