TKinter syntax to creat widget - python

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

Related

Python - Can I use a lambda function to generate a repetitive GUI with tkinter? (labels/text/buttons)

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())

Referring to dynamically determined class variables (self.[name])

new to python and have been googling around. Am trying out python class with a simple calculator.
Am currently using tkinter.
self.grid()
self.customfont = font.Font(family="Arial", size=14)
self.entryVariable=tkinter.StringVar()
dictFields = {"S0":"Enter initial price, S0, here.",
"K":"Enter strike price, K, here.",
"T":"Enter time to maturity, T, here.",
"r":"Enter risk-free rate, r, here.",
"sigma":"Enter volatility, sigma, here.",
"q":"Enter dividend yield, q, here."
}
i = 2
for key in dictFields.keys():
self.entryVariable = tkinter.StringVar()
## Unsure of this but I googled that I can create dynamically named class variables on the fly in this manner - would like to check if this is correct and if it is the best way to do it?
setattr(self, key, 0)
## another difficulty is that subsequently i have to set the properties of this variables - self.key seems to return be the literal self.key rather than self.S0, self.K etc.
self.key = tkinter.Entry(self, textvariable=self.entryVariable, justify="center")
self.key.grid(row=i, column=1, sticky="EW")
self.key.bind("<Return>", self.OnPressEnter)
self.entryVariable.set(dictFields[key])
i+=2
Any help would be appreciated!
EDIT:
I am unsure if the downs are because my questions wasn't clear (as they are embedded within comments in the code) or if the question itself is generally a bad one. Would appreciate if someone could kindly advise.
Questions:
How can I create class variables dynamically? I have googled a few methods that involved setattr(self, key, 0) or "{0}".format(key). However, I cant seem to get them to work as I am trying to assign a tkinter.Entry object to this dynamically created class variable name.
Thanks again.
self is great, if you are in something that can call itself a self.
For this you will need a class so you can put these variables in one place and make sense of this thing you are building.
how about:
class calculator(<you_might_need_another_class_here>):
""" just a normal calculator """
Then your next line would define how you want to start the calculator
def __init__(self): #here is where self shows up
pass
#replace "pass" with anything calculators do automatically
After this point your use of self will work inside this class.
yes you can dynamically create attributes with setattr. Make sure all your keys are strings.
But the last question is No. You are accessing "self.key" instead of self.S0
so a line like
self.key = tkinter.Entry(self, textvariable=self.entryVariable, justify="center")
can become
setattr(self,key,tkinter.Entry(self, textvariable=self.entryVariable, justify="center")
but quite honestly I don't know what to do with this line.
self.key.grid(row=i, column=1, sticky="EW")
you can use getattr(self,key) instead to pull the dynamically created self.keys

Python: Configuring Tkinter widget with argument to configure as a string

I'm using Python and Tkinter and was wondering whether there is anyway I could do something like this because currently it gives me an error. I would like to be able to configure a widget with user input as a string to decide what to configure, hence why the variable 'string_variable' needs to be a string.
tk_widget.config(string_variable = variable)
At the moment, I get an error saying "TclError: unknown option -'string_of_stringvar_here'" Please help me! Thank you for your replies in advance.
- Ed
You can treat a widget like a dictionary, with attributes being keys. For example:
label = tk.Label(root)
...
some_attribute = "background"
some_value = "red"
label[some_attribute] = some_value
You can also build up a dictionary of attributes and values, and pass that to the config method:
values = {some_attribute: some_value}
label.config(values)

GTK+3: How to render INSENSITIVE widgets in the style of NORMAL widgets

Sorry if this is a repost, I just can't find the answer anywhere. I am developing an application in Python and GTK+3 which has a TreeView which is set to be INSENSITIVE. This prohibits our users from making direct selections on a table of constants. The default behaviour of INSENSITIVE GTK+3 widgets however is to shade an insensitive object. In most cases this is a good behaviour, but in my case I need my table to remain easily legible.
What I would like to do is to be able to override the rendering of this particular INSENSITIVE object to match the rendering of NORMAL objects. Then if the user changes GTK themes, this particular INSENSITIVE widget will be rendered just like the normal widgets.
I am attaching some simple code to illustrate my point ...
import gi.repository.Gtk as Gtk
import gi.repository.Gdk as Gdk
class Example(Gtk.Window):
def __init__(self):
"""A minimal example of the rendering of a INSENSITIVE widget"""
# Use Gtk.Window __init__ method
Gtk.Window.__init__(self)
# Add a box
self.set_title("Example1")
self.box = Gtk.Box()
self.add( self.box )
# Entry widget
self.entry = Gtk.Entry()
self.entry.set_text("Can't touch this")
self.entry.set_sensitive( False )
self.box.pack_start(self.entry, True, True, 0)
class Example2(Example):
def __init__(self):
"""Forced recoloring of the INSENSITIVE widget. How do I do
this so that it matches the GTK 3+ style for normal text?
"""
# Use Example __init__ method
Example.__init__(self)
self.set_title("Example2")
# Hack the color
self.entry.override_color(
Gtk.StateFlags.INSENSITIVE,
Gdk.RGBA(0,0,0,1)
)
self.entry.override_background_color(
Gtk.StateFlags.INSENSITIVE,
Gdk.RGBA(1,1,1,1)
)
if __name__ == "__main__":
# Example 1
Window1 = Example()
Window1.connect("delete-event", Gtk.main_quit)
Window1.show_all()
# Example 2
Window2 = Example2()
Window2.show_all()
Gtk.main()
Here I overwrote the colouring of Example2. How can I overwrite it in a way that will always match the GTK theme?
You can access a widget's style properties from it's GtkStyleContext, which you can obtain by calling the get_style_context method of any widget. With the instance of the context you can access the different style properties using the corresponding GtkStateFlag.
Here is an example of how to get the background color of the widget when it's on NORMAL state:
from gi.repository import Gtk
def get_background_color(widget):
context = widget.get_style_context()
color = context.get_background_color(Gtk.StateFlags.NORMAL)
return color # the result is a GdkRGBA instance
I'm not completely sure what you should do to completly overwrite the INSENSITIVE state's styles, but I'm guessing you should use a GtkStyleProvider.
Why don't you try another approach, such as setting treeview.props.reorderable = False and setting all the cell renderer modes to INERT?

Accessing objects added to the Tkinter root

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\>}

Categories