Unable to dynamically create parent-child tkinter widgets in python - python

I am playing around with tkinter by creating a mortgage calculator. I am trying to create widgets dynamically from a separate file which contains lists with the relevant attributes.
The main file is:
import tkinter as tk
import tkinter.ttk as ttk
import tkinterwidgetinfo as twi
root=tk.Tk()
root.title('Tkinter Practice')
class Application(ttk.Frame):
def __init__(self, master=None):
super().__init__()
self.pack()
self.createApplication()
def createApplication(self):
## create widgets dynamically from twi
for i in twi.progWidgets:
a = i[1]+i[2]+'**'+str(i[3])+')'
i[0] = eval(a)
i[0].pack()
app = Application(root)
app.mainloop()
The list containing the widget information is in a separate file and imported. The information is as follows:
progWidgets = [
['inputFrame', 'ttk.LabelFrame(', '', {'text': "User Input",
'labelanchor': "nw"}],
['principalLabel', 'ttk.Label(', 'inputFrame,', {'text' : "Principal(£)"}],
['principalEntry', 'ttk.Entry(', 'inputFrame,', {}],
['termLabel', 'ttk.Label(', 'inputFrame,', {'text' : "Mortgage Term (Years)"}],
['termEntry', 'ttk.Entry(', 'inputFrame,', {}]
]
When I run this code, the first widget (the labelframe), isn't created. However, when I create the labelframe outside the loop, as follows:
import tkinter as tk
import tkinter.ttk as ttk
import tkinterwidgetinfo as twi
root=tk.Tk()
root.title('Tkinter Practice')
class Application(ttk.Frame):
def __init__(self, master=None):
super().__init__()
self.pack()
self.createApplication()
def createApplication(self):
inputFrame = ttk.Labelframe(text = "User Input",
labelanchor = "nw")
inputFrame.pack()
## create widgets dynamically from twi
for i in twi.progWidgets:
a = i[1]+i[2]+'**'+str(i[3])+')'
i[0] = eval(a)
i[0].pack()
app = Application(root)
app.mainloop()
The program behaves perfectly. How can I include the labelframe in the loop?

You use 'inputFrame' as a parent name but it is not defined. When you run your second piece of code inputFrame is defined and everything works.
I agree with commenters that using eval is a terrible idea (security reasons, flexibility), but you still can do this:
for i in twi.progWidgets:
a = i[0]+'='+i[1]+i[2]+'**'+str(i[3])+')'
eval(a)
a = i[0]+'.pack()'
eval(a)

To anyone finding this question, as you can tell from the content I was very new to programming at the time. From what I can remember I was under the naïve impression that organising the code in a very abstract manner was somehow saving processing time or somehow easier to read. I didn't have a grasp of the underlying mechanisms which dictate the operation of computers. With this in mind akarilimano's answer is correct and works but I would support the comments beneath the original question which criticise the use of such a bizarre layout.
From PEP 20
Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Readability counts.
If the implementation is hard to explain, it's a bad idea.

Related

Python, Tkinter - some general questions about the code

I have some general questions regarding working code below:
tkinter is library for graphic interface as I understand I can use it interchangeably with for example Kivy?
Would it be better to learn Kivy instead or other?
Lines import tkinter as tk and from tkinter import * do exactly the same, in the first one I have alias though?
In the code below, why do I have to use ttk in ttk.Progressbar?
I imported whole library with import tkinter as tk so why do i have to reimport ttk just for progress bar? (otherwise it is not working). I would expect to work sth. like tk.Progressbar
In the line btnProg = tk.Button(self.root, text = 'update', command=self.fnUpdateProgress), why method "fnUpdateProgress" can't have any variables? Whenever I add any, the button stop working? -> for example btnProg = tk.Button(self.root, text = 'update', command=self.fnUpdateProgress(24)) (ofc then some changes in def of the method itself)
I created progress bar (pb) as attribute of the class Test, but wolud it be better to define it as regular variable (without self)? To be honest, code works exactly the same.
Code:
import tkinter as tk
from tkinter import *
from tkinter import ttk
from CreateProgramMain import main
import GlobalVariables
class Test():
####################################################################################
def __init__(self):
self.Progress=0
self.root = tk.Tk()
self.root.title(GlobalVariables.strAppName)
self.root.geometry('400x200')
lbl = Label(self.root, text="Please choose environment.",font=("Arial Bold", 12))
lbl.grid(column=2, row=0,sticky='e')
def btnTestClicked():
main("TEST",self)
btnTest=tk.Button(self.root, text="Test Environment", command=btnTestClicked)
btnTest.grid(column=2, row=15)
#Place progress bar
pb = ttk.Progressbar(self.root,orient='horizontal',mode='determinate',length=200)
pb.grid(column=1, row=65, columnspan=2, padx=10, pady=20)
pb["value"]=self.Progress
pb["maximum"]=100
btnProg = tk.Button(self.root, text = 'update', command=self.fnUpdateProgress)
btnProg.grid(column=2, row=75)
self.root.mainloop()
def fnUpdateProgress(self): #why i cant insert variable inside?
pb["value"]=self.Progress
self.Progress=self.Progress+5
pb.update()
app = Test()
Thank you
it is upto you. However, tkinter and kivy both have their own syntaxes, commands, and their own usages. It might be a little difficult to convert tkinter code to kivy.
it is upto you
Yes. In the first, you have imported tkinter as tk. In the second one. You have done a wild card import. You have imported everything
Tkinter is a folder containing various modules. It contains a file called ttk.py which you have to import to access ttk.
All other classes like Label, Entry, Tk is present in __init__.py
you have to use lambda for it. If you call the function, it will be executed wlright away and the returned value is kept as the command.
Doing command=self.fnUpdateProgress(24)) will execute the function right away. Then, the returned value is kept as a command. Here, it returns None. So the command is nothing or the button is useless.
Use a lambda expression command=lambda: self.fnUpdateProgress(24))
if you don't add self it will be local to the function only. To access ot outside, it would have to be declared global, which is the point to avoid while using classes

Python: tkinter window presented in an OO manner

I have been trying to represent tkinter window in an OO manner. There are few answers here, and I have managed to produce a working code, but I simply do not understand why it's working.
import tkinter as tk
class CurrConv:
def __init__(self, window, date):
self.window = window
window.title("Currency Converter")
window.geometry("350x150+300+300")
self.label = tk.Label(text="Date: {}\n".format(date))
self.label.pack()
self.text_box = tk.Text()
self.text_box.insert("2.0", "100 is: {}\n".format(100))
self.text_box.insert("3.0", "24 is: {}".format(24))
self.text_box.pack()
self.button = tk.Button(text="Quit", width=8, command=window.quit)
self.button.place(relx=0.5, rely=0.9, anchor="center",)
def main():
window = tk.Tk()
app = CurrConv(window, 1234)
window.mainloop()
if __name__ == "__main__":
main()
The thing I don't understand is the usage of "app" object. It is used nowhere, and usually when we create an object (in any programing language), we invoke certain actions on it. However here we do nothing with the app object. Class encapsulation appears to indirectly modify "window", which is confusing, to say the least.
Next, I don't understand how labels and text boxes are added to "window", when in the code I nowhere create those in "window", I create them on "self", which would be "app", which is no longer used.
Bottom line is, for the reasons above, I do not understand how the above code works.
Thanks in advance.
I hope I was clear enough.
Class encapsulation appears to indirectly modify "window", which is confusing, to say the least.
Yes, that is what the code is doing, and it's incorrect IMHO. The code inside of CurrConv.__init__ at least arguably should not be directly modifying the root window for some of the very reasons you put in your question.
Next, I don't understand how labels and text boxes are added to "window", when in the code I nowhere create those in "window", I create them on "self", which would be "app", which is no longer used.
Your terminology is a bit incorrect. "I nowhere create those in 'window'" isn't true. By creating the widgets without an explicit master they default to being created in the root window. The reference to the widget is created in the class (self.label, etc) but the widget itself is created in the root window. IMHO this is also not the proper way to write a tkinter application. Explicit is better than implicit.
For me, the proper way to write this class would be for CurrConv to inherit from a tkinter Frame (or some other widget). Everything it creates should go inside itself, and then the code that creates the instance of CurrConv is responsible for adding it to a window. This solves your problem of not being able to create multiple windows.
Also, the widgets inside of CurrConv should explicitly set the master of any child widgets it creates rather than relying on a default master.
Example:
class CurrConv(tk.Frame):
def __init__(self, window, date):
super().__init__(window)
self.label = tk.Label(self, ...)
self.text_box = tk.Text(self, ...)
self.button = tk.Button(self, ...)
...
...
With that, everything is nicely encapsulated inside the class, and you can create multiple instances inside multiple windows very easily:
root = tk.Tk()
conv1 = CurrConv(root)
conv1.pack(fill="both", expand=True)
top = tk.Toplevel(root)
conv2 = CurrConv(top)
conv2.pack(fill="both", expand=True)
In the above case, it's perfectly fine for the class to modify the window because it's part of the code that creates the window in the first place.
You can still use a class for the application as a whole which you can use to hold some global state. To further prove the usefulness of creating CurrConv as a subclass of Frame, the following example adds currency converters to a notebook instead of separate windows.
class CurrConvApp():
def __init__(self):
self.root = tk.Tk()
self.root.title("Currency Converter")
self.root.geometry("350x150+300+300")
self.notebook = ttk.Notebook(self.root)
cc1 = CurrConv(self.notebook)
cc2 = CurrConv(self.notebook)
self.notebook.add(cc1, text="USD to EUR")
self.notebook.add(cc2, text="EUR to USD")
...
def start(self):
self.root.mainloop()
if __name__ == "__main__":
app = CurrConvApp()
app.start()
For the first question, yes app is an object. But it is an instance of the class CurrConv. When you initialize a class, you call the class's __init__ method, and this case, by doing so, you execute the statements in that method: modifying the window (which you passed as a parameter whin you created app) and adding widgets to it. So although app is not directly used, it had the side effect of doing those things when it was created. And for that reason, since you only need the initialization method, assigning to a variable is not necessary, you can just make it like CurrConv(window, 1234).
For the second, yes, you didn't mention window when you created the widgets, but when the master of a new Tkinter widget is not specified, it takes the main master (the root, created using tk.Tk()) as it's master.

Adding Tix Widget to Tkinter Container

I'm working with Tkinter in Python 2.7 on Windows 7, and found the need to create a popup box with a tree-style list of checkboxes. I could not find this in Tkinter, or ttk. I did, however, find it in Tix in the CheckList widget. I got a working standalone example using Tix, but I cannot figure out how to add my Tix.CheckList to my ttk.Frame that controls my main program.
Surely I am not forced to use Tix framework from the ground up?
import Tix
import pandas as pd
import Tkinter as tk
class TreeCheckList(object):
def __init__(self, root):
self.root = root
self.cl = Tix.CheckList(self.root)
self.cl.pack(fill=Tix.BOTH, expand=Tix.YES)
self.cl.hlist.config(bg='white', bd=0, selectmode='none', selectbackground='white', selectforeground='black', drawbranch=True, pady=5)
self.cl.hlist.add('ALL', text='All Messages')
self.cl.hlist.add('ALL.First', text='First')
self.cl.setstatus('ALL.First', "off")
self.cl.hlist.add('ALL.Second', text='Second')
self.cl.setstatus('ALL.Second', "off")
self.cl.autosetmode()
def main():
root = Tix.Tk()
top = Tix.Toplevel(root)
checklist = TreeCheckList(top)
root.update()
top.tkraise()
root.mainloop()
if __name__ == '__main__':
main()
The above code works in a standalone program using all Tix widgets. However, when I try to implement this into my larger program, I receive a TclError: invalid command name "tixCheckList"
To simulate this in the standalone, I changed the lines:
root = Tix.Tk()
top = Tix.Toplevel(root)
to
root = tk.Tk()
top = tk.Toplevel(root)
I was hoping I could just implement a Tix.Toplevel, placing it on a tk.Tk() root, but same issue.
Am I only allowed to use Tix frames when using a Tix widget, or am I misunderstanding something? If anyone has good Tix documentation, I would LOVE whatever I can get. It seems good docs on it are few and far between. Or is this same functionality included in ttk and I've just overlooked it? It seems to be one of the only things left out.
I have just learned that apparently only root needs to be a Tix class. Since Tk, and therefore ttk, classes appear to be added to the Tix root just fine (since most of them extend the Tkinter classes anyway), this appears to be a "fix". So my problem may have been solved by changing just
root = tk.Tk()
to
root = Tix.Tk()
This did require that I pull Tix into a part of my program I wasn't wanting for encapsulation purposes, but I guess there's no other way.

TypeError in 2 classes tkinter application

For a beginner in Tkinter, and just average in Python, it's hard to find proper stuff on tkinter. Here is the problem I met (and begin to solve). I think Problem came from python version.
I'm trying to do a GUI, in OOP, and I got difficulty in combining different classes.
Let say I have a "small box" (for example, a menu bar), and in want to put it in a "big box". Working from this tutorial (http://sebsauvage.net/python/gui/index.html), I'm trying the following code
#!usr/bin/env python3.5
# coding: utf-8
import tkinter as tki
class SmallBox(tki.Tk):
def __init__(self,parent):
tki.Tk.__init__(self,parent)
self.parent = parent
self.grid()
self.box = tki.LabelFrame(self,text="small box")
self.box.grid()
self.graphicalStuff = tki.Entry(self.box) # something graphical
self.graphicalStuff.grid()
class BigBox(tki.Tk):
def __init__(self,parent):
tki.Tk.__init__(self,parent)
self.parent = parent
self.grid()
self.box = tki.LabelFrame(self,text='big box containing the small one')
self.graphStuff = tki.Entry(self.box) # something graphical
self.sbox = SmallBox(self)
self.graphStuff.grid()
self.box.grid()
self.sbox.grid()
But I got the following error.
File "/usr/lib/python3.5/tkinter/__init__.py", line 1871, in __init__
self.tk = _tkinter.create(screenName, baseName, className, interactive, wantobjects, useTk, sync, use)
TypeError: create() argument 1 must be str or None, not BigBox
The tutorial you are using has an incorrect example. The Tk class doesn't have a parent.
Also, you must only create a single instance of Tk (or subclass of Tk). Tkinter widgets exist in a tree-like hierarchy with a single root. This root widget is Tk(). You cannot have more than one root.
The code looks quite similar at this one : Best way to structure a tkinter application
But there is one slight difference, we're not working on Frame here. And the error asks for a problem in screenName, etc. which, intuitively, looks more like a Frame.
In fact, I would say that in Python3 you can't use anymore the version of the first tutorial and you have to use Frame, and code something like that :
#!usr/bin/env python3.5
# coding: utf-8
import tkinter as tki
class SmallBox(tki.Frame):
def __init__(self,parent):
tki.Frame.__init__(self,parent)
self.parent = parent
self.grid()
self.box = tki.LabelFrame(self,text="small box")
self.box.grid()
self.graphicalStuff = tki.Entry(self.box) # something graphical
self.graphicalStuff.grid()
class BigBox(tki.Frame):
def __init__(self,parent):
tki.Frame.__init__(self,parent)
self.parent = parent
self.grid()
self.box = tki.LabelFrame(self,text='big box containing the small one')
self.graphStuff = tki.Entry(self.box) # something graphical
self.sbox = SmallBox(self)
self.graphStuff.grid()
self.box.grid()
self.sbox.grid()
if __name__ == '__main__':
tg = BigBox(None)
tg.mainloop()
We don't find (especially for French people, or maybe people not "natural" in english) many examples and docs, and the one I use is quite common, so maybe it will be useful to someone.

Stopping label replacement

I am creating a revision program for myself however whenever the fun1 function is called it prints out underneath the previously executed function. e.g the label will print out underneath the last one instead of replacing it, any ideas? Any help would be appreciated!!
#Imports moduals used
from tkinter import *
import time
import random
#Sets GUI
gui = Tk()
gui.geometry("500x500")
gui.maxsize(width=500, height=500)
gui.minsize(width=500, height=500)
#Sets list of facts
def t():
print("hi")
facts = ['fact one','true', 'fact two','abc']
#Defines random fact generator
def fun1():
r = random.randrange(len(facts))
lbl = Label(gui,text=facts[r]).pack()
btnt = Button(text="True", command=t).pack()
btnf = Button(text="False", command=t).pack()
gui.after(5000, fun1)
gui.after(5000, fun1)
mainloop()
Overview
The best way to write this sort of program is to create the label or button only once, and then use the configure method to change the text of the the label.
Using a procedural style
Here's an example based off of your original code:
#Imports moduals used
from tkinter import *
import time
import random
#Sets GUI
gui = Tk()
gui.geometry("500x500")
gui.maxsize(width=500, height=500)
gui.minsize(width=500, height=500)
def t():
print("hi")
#Defines random fact generator
def fun1():
r = random.randrange(len(facts))
lbl.configure(text=facts[r])
gui.after(5000, fun1)
#Sets list of facts
facts = ['fact one','true', 'fact two','abc']
# create the widgets
lbl = Label(gui,text="")
btnt = Button(text="True", command=t)
btnf = Button(text="False", command=t)
# lay out the widgets
lbl.pack(side="top", fill="x", expand=True)
btnt.pack(side="left", fill="x")
btnf.pack(side="right", fill="y")
# show the first fact; it will cause other
# facts to show up every N seconds
fun1()
mainloop()
Using an object-oriented style
Since you're just getting started, let me suggest a better way to organize your code. Python is object-oriented in nature, so it makes sense to make your application an object. Even though you may not be familiar with classes an objects, if you start with this pattern and follow a couple of simple rules, you can get all the benefits of object orientation without much effort.
The only thing you need to remember is that in your main class, all functions need to have self as the first parameter, and when calling a function you use self.function and omit self as an argument (python does that for you). Other than that, you can pretty much code as normal inside a class.
Here's an example:
import tkinter as tk
import random
class Example(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
self.facts = ['fact one','true', 'fact two','abc', 'a long fact\non two lines']
self.label = tk.Label(self,text="", width = 40, height=4)
self.true_button = tk.Button(self, text="True", command=self.t)
self.false_button = tk.Button(self, text="False", command=self.t)
self.label.pack(side="top", fill="both", expand=True, pady=40)
self.true_button.pack(side="left", fill="x")
self.false_button.pack(side="right", fill="x")
self.fun1()
def t(self):
print("hi")
def fun1(self):
r = random.randrange(len(self.facts))
self.label.configure(text=self.facts[r])
self.after(5000, self.fun1)
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()
Here are a few things to notice:
import tkinter as tk this requires a tiny bit more typing, but it makes your program more robust. The side effect of this (which is a good side effect IMO) is that all tkinter commands now need to be prefixed with tk (eg: tk.Button). In my opinion this is a much better way than just blindly importing everything from tkinter as a bunch of global variables and functions. I know must tutorials show from tkinter import *, but they are misguided.
The main part of your logic is a class. This makes it easy to avoid using global variables. As a rule of thumb when programming, you should avoid global variables.
Notice how self is an argument to t, fun1 and __init__ -- this is a requirement for python classes. It's just how you do it. Also notice we call them like self.fun1 rather than just fun1. This lets python know that you want to call the function associated with the current object. Once your program gets bigger, this makes it easier to know where fun1 is defined.
I removed the code that forces the size of the GUI. Tkinter is really good at calculating what the size should be, so let it do it's job. Also, if you take care when laying out your widgets, they will grow and shrink properly when the window is resized. This means you don't need to force a min or max size to a window. Forcing a size gives a bad user experience -- users should always be able to resize windows to fit their needs.
I separated the creation of the widgets from the layout of the widgets. For one, you have to separate them if you want to keep references to widgets. This is because this: lbl=label(...).pack() sets lbl to None. That's just how python works -- the last function is what gets saved to a variable, and both pack and grid always return none. The second reason is simply that it makes your code easier to write and maintain. All of the code that organizes your widgets is in one place, making it easier to see the big picture.

Categories

Resources