Why put frames and widgets in classes? - python

As the question states, I can't seem to fully grasp the point of using classes with tkinter.
I have read through a decent number of different sites but I keep getting search results on how to create and use classes, but none so far have been able to get through to me. I've even scoured through the suggested questions while asking this one. The closest I've come to understanding is Bryan's explanation on this answer to the question Why use classes when programming a tkinter gui?
But still, I feel like I'm almost there, just not quite over the edge of understanding.
In his example in the link, he creates an unconventional program, and then a better, more conventional program that does the same thing. I know that it represents a much smaller scale than the thousand-line programs that could really benefit from an object oriented approach.
Does every widget need to be in its own separate frame that's maybe part of an even bigger frame?
Can classes have methods that create and place a frame? In addition, can those same classes have methods than can create, modify, and place a widget within the previously made frame?
I also have some code that allows me to create, modify, and place a widget. Although I know it's not conventional, so I would greatly appreciate some input on this as well. Any suggestions on what you would do with this code to make it better?
import tkinter as tk
def layout(self, row=0, column=0, columnspan=None, row_weight=None, column_weight=None, color=None, sticky=None, ipadx=None, padx=None, ipady=None, pady=None):
self.grid(row=row, column=column, columnspan=columnspan, sticky=sticky, ipadx=ipadx, padx=padx, ipady=ipady, pady=pady)
self.grid_rowconfigure(row, weight=row_weight)
self.grid_columnconfigure(column, weight=column_weight)
self.config(bg=color)
class MyLabels(tk.Label):
def __init__(self, parent, text, **kwargs):
tk.Label.__init__(self, parent, text=text)
layout(self, **kwargs)
class MyButtons(tk.Button):
def __init__(self, parent, text, command, **kwargs):
tk.Button.__init__(self, parent, text=text, command=command)
layout(self, **kwargs)
window = tk.Tk()
test_button = MyButtons(window, "hi", None, color="pink")
window.mainloop()
Edited after comments:
So I've been working many hours since yesterday trying to incorporate the ideas you've had for me. This is what I came up with:
import tkinter as tk
window = tk.Tk()
class MyWidgets(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.layout()
def layout(self):
self.grid(row=0, column=0)
self.config(bg="blue")
class MyButtons(MyWidgets):
def __init__(self, parent, text):
MyWidgets.__init__(self, parent)
tk.Button(parent, text=text)
self.layout()
frme = MyWidgets(window)
btn = MyButtons(frme, text="Test")
window.mainloop()
I've tried moving things around and rewriting lots of areas on this little side program, and even though I was able to prove that btn is infact accessing the self.config(bg="blue") attribute, the button doesn't appear to change. As a matter of fact I can't find a way to make the button appear without needing to put self.grid() in the child class just after the button is created.
Still, even if I did add the self.grid() the button still won't turn blue. Is it something with self?
Why won't the button appear when the child class creates it, and the parent class places it?
Note: I've purposefully omitted the entire layout function and replaced it with just a simple config method. I figure if I can understand this, I can then find a way to incorporate the whole function back into the code.

Does every widget need to be in its own separate frame that's maybe part of an even bigger frame?
That's a bit like asking if every part of a mathematical expression needs parenthesis. Strictly speaking, the answer is "no". However, using frames to organize groups of widgets is a tool designed to make writing and understanding the code easier, much like parenthesis in a complex math equation makes writing and understanding the equation easier.
Can classes have methods that create and place a frame? In addition, can those same classes have methods than can create, modify, and place a widget within the previously made frame?
Yes, and yes. Methods in a class don't have any limitations on what they can and cannot do. Methods of a class can do anything that a normal function can do.
I also have some code that allows me to create, modify, and place a widget. Although I know it's not conventional, so I would greatly appreciate some input on this as well. Any suggestions on what you would do with this code to make it better?
"Better" is highly subjective. What is better for 20 lines of code might not be better for 200, 2,000, or 20,000. What is better for a function used exactly twice might not be better for a function used hundreds or thousands of times (or visa versa).
That being said, you're doing one thing that is very unconventional and which leads to making your code harder to understand: you're using self as a parameter for a function that is not a method of a class. self means something very specific to python programmers; using it outside of the context of a method is very confusing.
You should do one of two things for the method layout:
Rename self to be widget or any other term other than self
Create a base class that defines layout, and then have your classes inherit from the base class. In that case, self is the proper first argument.
This part of the answer refers to code which was added after I wrote my original answer.
The base class I was referring to needs to be a separate class. For example:
class Base():
def layout(self):
self.grid(row=0, column=0)
self.config(bg="blue")
class MyLabels(Base, tk.Label):
def __init__(self, parent, text, **kwargs):
tk.Label.__init__(self, parent, text=text)
self.layout(self, **kwargs)
class MyButtons(Base, tk.Button):
def __init__(self, parent, text, command, **kwargs):
tk.Button.__init__(self, parent, text=text, command=command)
self.layout(self, **kwargs)
This type of class is sometimes called a mixin because it's not designed to be instantiated as a standalone object. Rather, it "mixes in" some additional behavior to other classes. A mixin will typically have methods, but won't have its own __init__.

Related

Calling textvariable from function within a class Python 3 tkinter

Thankful for any assistance in my question.
I am using Python 3 with tkinter where I have setup multiple pages, however I am trying to call a specific textvariable which is in a class, within a definition and wondering if it's possible to do with the way I built my script.
It's quite a big script so I don't want to paste all the code here, I hope this will be enough.
Basically the class is called POIIN(tk.frame) and I am trying to call
class POPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.entryboxes()
def entryboxes(self):
# Entry boxes + variables
global voucher
voucher = IntVar()
entry_box3 = tk.Entry(self, textvariable=voucher, width=32, bg="lightgreen")
entry_box3.place(x=435, y=151)
I am trying to call the textvariable by doing the following outside of the class:
def do_mssql():
try:
if int(POPage.entryboxes(voucher).get()) > int(maxN):
voucherchktext()
elif int(voucher.get()) < int(minN):
voucherchktext()
else:
sql()
except:
vouchermissing()
However it seems to be calling the IntVar() and I don't get any error message, (quite new with Python)
Basically I can pull the voucher from another class (page) with just doing the int(voucher.get()).
I could of course rename the textvariable and call it that way (which I don't mind), but it would be great if there is any way around this.
Greatful for any help!
BR,
Thanks for posting your entire code on pastebin. By looking at your code I can see that none of your classes actually take ownership of the tkinter widgets they create. In otherwords, they don't have any instance variables. I would suggest a radical redesign and getting rid of all global variables and objects - in this case it's a major code smell and suggests that there is a flaw in your design - and that's fine, you did say you were a beginner after all. I see a lot of good things in your code, too! Not to be one-sided...
Basically, the change I'm recommending is this: Each frame class should take ownership of the widgets they create. That includes things like tk.Labels, tk.Buttons, tk.Entrys and tk.IntVars among others.
The major benefit this provides, is that any instance of your frame class will have its own tk.IntVar (or whatever entry widget variable, like tk.StringVar) which it does not need to share with any of the other classes (you called it a textvariable). This makes sense if you think about it: Every tk.Entry widget is coupled with one variable object that keeps track of the user's entered data for that entry widget - if you create just one global entry variable and share it with all entry widgets in all frames, you could easily lose a user's entered data - there's really no reason to do that.
Try running/playing around with the following code snippet:
import tkinter as tk
class MyFrame(tk.Frame):
def __init__(self, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
self.label = tk.Label(self, text="Some Text")
self.label.pack()
self.entry_var = tk.IntVar()
self.entry = tk.Entry(self, textvariable=self.entry_var, width=32)
self.entry.pack()
self.button = tk.Button(self, text="Print this frame's 'entry_var'", command=lambda: print(self.entry_var.get()))
self.button.pack()
class Application(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.title("Title")
self.geometry("256x256")
self.resizable(width=False, height=False)
self.frames = []
for frame in MyFrame(), MyFrame():
self.frames.append(frame)
frame.pack()
def main():
application = Application()
application.mainloop()
return 0
if __name__ == "__main__":
import sys
sys.exit(main())
You have a main Application (you call it Ghost in your example), which has multiple frames (my example only has one frame class, but the Application has two instances of that frame class). Each frame instance has an entry widget and a unique, completely separate and distinct corresponding entry variable object. There is no reason for frames to share the same entry variable object. Making the widgets and entry variable objects instance variables of their classes should eliminate your use of global variables entirely.
In my example, if I needed to get access to the values in the entry widgets from outside the classes - like in the main function for example, you would say application.frames[0].entry_var.get() to get the value in the first frame's entry widget.

How can I make an interface work with casting

So, I am programming my Python Program with the MVC-Architecture, and I want everything nice and seperated from each other. I don't want the View of my GUI having to work with the Controllers instance and so on. So I made an 'IController' abstract class which is the parent of 'Controller', which has all of the functions. In 'IController' I have the functions my Model and View need to access. The Controller looks somewhat like this:
class IController:
def method(self):
pass
class Controller(IController):
self.x = 'Hello'
def method(self):
print('self.x)
So where I previously had
class Frame(tk.Frame):
def __init__ (self, controller):
self.controller = controller
button = tk.Button(self, command=lambda: self.controller.method()
I now want to turn this into
class Frame(tk.Frame):
def __init__ (self, controller):
self._controller = type(controller)
button = tk.Button(self, command=lambda: self._controller.method()
The problem here is, that when I do this, I can't keep the instance of my 'Controller' Class. I need this, since the instance has values and methods I need to work with here. I also can't save the instance of 'Controlle'r in 'IController' since it is an abstract class, so I won't instance it and can't save anything in it.
I expected it to just work, but I am not sure if this is possible now. I read that casting is not possible in python, but I think there must be another way for me to fix this. When I ran it, it told me that I am lacking 'self'. I can't send the instance of the Controller with it, then it would not be capsulated. Is there a way around this?
For other people who might try the same: It is not possible to do this like you would do it in C# or others. I solved it by re-writing my program in and learning C#. That and some ugly workarounds are the only possibilities.

tkinter: Best way to tag an arbitrary widget for retrieval

While building my own widgets based on the stock ones, I've been adding a "name" field to every widget class of mine for easier access later, like:
class MyFrame(tk.Frame):
def __init__(self, master, name, *args, *kwargs):
super().__init__(master, *args, *kwargs)
self.name = name
After setting up the entire window layout, I could do something like:
mywidget = allWidgets['myWidgetName']
But I couldn't help but wonder if there is a better approach by using a widget's built-in attributes, without adding a new tag. I know that winfo_children() helps traverse the widget tree, but I do need random access.
Based on #BryanOakley 's tip, I finally discovered that all Tkinter widgets have a ._name attribute, whose default value is !<classname>, all lowercase even if the classname uses mixed cases.
I could then assign any name to ._name after instantiating my widget class.
It's understandable that this detail falls out of the official doc because this is not part of the public interface, but knowing this instantly saved memory for my own work.
However, I guess that it'd be my own responsibility to maintain the uniqueness of widget names if I started playing around with it.
I have to thank Bryan a million for his resourcefulness when it comes to Tkinter.

__init__ method implemented on application called inside the main method

This code is part of the main calculator application mentioned here: https://pastebin.com/ECA2AQzY
I am new to python and from my understanding, "self" is the first argument being passed automatically for each instance of a class. Here, I am confused whether "app" would be self? and Frame is some superclass?
I do not understand why init is called on Frame within a main init method?
Also, why is lambda needed? Can it just not be command = self.appendToDisplay("7"))
What was the need of calling grid method on "app" object when the grid is called in the class itself?
calculator = Tk()
calculator.title("Calculator")
calculator.resizable(0, 0)
#================================================
class Application(Frame):
def __init__(self, master, *args, **kwargs):
Frame.__init__(self, master, *args, **kwargs)
self.createWidgets()
#================================================
def createWidgets(self):
self.sevenButton = Button(self, font=("Helvetica", 11), text="7",
borderwidth=0, command=lambda: self.appendToDisplay("7"))
self.sevenButton.grid(row=1, column=0, sticky="NWNESWSE")
#================================================
app = Application(calculator).grid()
calculator.mainloop()
Let's try to answer your questions in order:
Application intherrits from Frame, that is all methods implemented in Frame are also avaliable in Application. In addition Application can implement its own methods as well as override implementations in Frame. In the latter case, the implementations in Application replace the implementations in Frame. This is the case for the __init__() method. The method in Application replaces the one in Frame. Thus, the Frame() __init__() method is never called and that is typically not good, since there may be a lot of the functionallity in Frame() that needs explicit initialization. Therefore, Application.__init__() must explicitly call Frame.__init__() to get it to initialize.
The difference between self.appendToDisplay("7") and lambda: self.appendToDisplay("7") is that in the first case the method appendToDisplay() is directly called and the result is returned. When using lambda instead of calling the function, a new function is defined. Thus, when using the lambda construct the result will not be directly available. Instead the result of the calculation will be delayed to a later time, when the command is to be executed. Thus, we want to display 7 on the display when the button is pressed and not now.
The grid call in Application.createWidgets() refers to the button, to place that in the calculator Application. The Application.grid() call on the other hand, is placing the whole calculator itself. Thus, it is a hierachical thing, where the parts of the application are first placed and then the application itself.
I hope the above answers help you understand the concepts a bit better. It may be quite a bit to digest and you should try to find a few tutorials that explains the concepts in a bit more detail.

Keeping classes loosely coupled and sharing data

I've been working in python on a project where I have a GUI which I split up a bunch of the work between classes. I don't know a lot of the best practices for passing data around between classes, and I've frequently run into the issue, where I have to implement something, or change something for work, and I've resorted to making a lot of the classes objects of another class in order to give it the data I need.
Any ideas or suggests would be greatly appreciated on how to keep my classes independent for later modification and still pass the relevant data around without affecting interfaces too much?
As an example
class Window():
def __init__(self, parent=None):
self.parent = parent
def doStuff(self):
#do work here
class ParseMyWork(Window):
def __init__(self, parent=None):
self.parent=parent
I often find myself doing stuff like the above giving objects to class Window
or simply inheriting everything from them as in ParseMyWork
There must be better and cleaner ways of passing data around without making my classes utterly dependent on eachother, where one little change creates a cascade effect that forces me to make changes in a bunch of other classes.
Any answers to the question don't necessarily have to be in python, but it will be helpful if they are
If I'm understanding your question correctly, I would say that inheritance is not necessary in your case. Why not give ParseMyWork a function for dealing with a specific Window task?
class Window():
def __init__(self, parent=None):
self.parent = parent
def doStuff(self):
#do work here
class ParseMyWork():
def __init__(self, parent=None):
self.parent=parent`
def doWindowActivity(self, window):
window.doStuff
Then you can use the function like this
work_parser = ParseMyWork()
window = Window()
work_parser.doWindowActivity(window);
That way you can use your work_parse instance with any window instance.
Apologies in advance for my Python, it's been a while so if you see any rookie mistakes, do point them out.
Keep it simple.py:
def doStuff(window):
#do work here
return window
def parseStuff(stuff):
pass
really.py:
from simple import doStuff, parseStuff
def really_simple(window):
okay = doStuff(window)
return parseStuff(okay)
don't complicate the class:
from really import really_simple
really_simple(window)
imo: classes are overly complicated objects, and in a lot of cases more confusing than they need to be, plus they hold references and modify stuff, and can be difficult to decouple once they have been tied to other classes. if there isn't a clear reason why a class needs to be used, then it probably doesn't need to be used.
Classes are super powerful, so it's good you're getting started with em.
Discalimer: Haven't worked in python for a while now, so things might not be exact. The general idea still applies though.
Getting into your question now:
I would say the best way to achieve what you want is to create an instance of the first object where you will extract information from.
Now when creating a class, it's vital that you have attributes within them that you will want to be stored within it that you would like to retrieve once the class is instantiated.
For example, using your Window class example above, let's say that you have an attribute called resolution. It would look something like this:
class Window():
def __init__(self, parent = None):
self.parent = None
self.resolution = '40x80'
Now the resolution information associated with your Window class is forever part of any Window class instance. Now, the next step would be to create a get method for resolution. This should be done as follow:
class Window():
def __init__(self, parent = None):
self.parent = None
self.resolution = '40x80'
def getResoultion():
return self.resolution
Now, the reason we created this get method is because we can now set a variable to the information that is returned with it.
So let's say that you have everything associated with your Window class in its own file (let's say the file name is called Window.py). In a separate file (let's call it main.py), you can do the following:
import Window
windowInstance = Window()
windowResolution = windowInstance.getResolution()
If you print out the variable windowResolution, you should get that 40x80 printed out.
Now, as a side note, I do believe it is possible to get the information associated with an attribute with an instance of a class by simply doing something like
windowResolution = windowInstance.resolution
but that is bad practice in general. The reason, in a nutshell, is because you are now exposing attribute names of your class which you do not want to do because it makes it easy for a person outside of your code to learn the name where that information is held and change it. This can then lead to a myriad of other problems when it comes to making an overall program work. That is why it is best practice to use getters and setters. I already showed what getters are. Simply a get method for attributes. Setters, as you can probably assume, allow for one to set the information of an attribute to something else. Now you might say "Gabe, if we can create setter methods, what's the point of it if they just change it". My answer to that is to not give a setter method to all attributes. For attributes you don't mind for a person to change, give it a setter method, but for attributes you do not want any outside users to touch, simply don't create a setter method for it. Same goes with getter methods too. Users don't need to see all of the information of all attributes that makes your program work. Here's a better explanation: https://en.wikipedia.org/wiki/Mutator_method
Now, back to your example. Now let's say you have your ParseMyWork class in its own file like we did with your Window class, and let's say that ParseMyWork needs the resolution info from Window class. You can do the following :
import Window
import ParseMyWork
windowInstance = Window()
windowResolution = windowInstance.getResolution()
parseInstance = ParseMyWork(windowResolution)
This will only pass the window resolution information associated with your Window class. Hope this helps.

Categories