Accessing variable inside method, python tkinter - python

I'd like to preface by saying I've read the docs and I guess I'm not sure how to use the provided information, or I'm just not capable of seeing the difference between the provided code and my code. I've also searched on google for relevant issues to no avail. I'm currently following this tutorial
I've made a tkinter window for a simple program I'm working on, but now I'm trying to put it in a class and everything's going downhill. The basic structure of the code in question is as follows:
from tkinter import *
class WindowManager(Tk):
def __init__(self, parent):
Tk.__init__(self, parent)
self.parent = parent
# Place items for console log
self.initialize()
def initialize(self):
self.grid()
self.text_item = Text(self, bd=0, bg="black", fg="white", height="15", width="56", )
self.text_item.grid(column=0, row=0)
def access_text(app):
app.initialize.text_item.delete('1.0', END)
def main():
app = WindowManager(None)
app.title("Main Window")
app.geometry("400x250")
app.resizable(0, 0)
app.mainloop()
access_text(app)
if __name__ == "__main__":
main()
Now this is just the basic structure of the code in question, and everything that's relevant.
An error is being thrown when the window closes saying "'function' object has no attribute 'text_item'"
My best guess is that it's trying to do something with functions in general and not accessing the code found within the function in question, but I'm not sure what the proper way to access this variable is.
Any help or clarification would be much appreciated. Thank you for your time.

My issue was I was trying to access the variable from the method when it was a local variable in the class
Instead of app.initialize.text_item I should have just had app.text_item.
Credit to #Gribouillis

Related

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.

Redo Not Working on Windows for Tkinter Text Widget

I'm sorry if this is a stupid question, but I'm not sure what I am doing wrong here. The undo and redo worked perfectly fine on Linux but it will not work on Windows. I tried to create a binding manually (as seen below) but that also didn't solve the problem. I try this exact code below and the redo command does not work. I have researched the problem, but I only find the binding solution, which I have already tried.
import tkinter as tk
class TextThing():
def __init__(self, master):
self.text = tk.Text(undo=True, maxundo=-1, autoseparators=True)
self.bindings()
self.text.pack()
def bindings(self):
self.text.bind('<Control-Shift-z>', self.text.edit_redo)
if __name__ == '__main__':
master = tk.Tk()
text_thing = TextThing(master)
master.mainloop()
You need to bind "Control-Shift-Z" (uppercase "Z") because if the shift key is pressed you get an upper case "Z" not a lower case "z".

Python tkinter restarting the mainwindow while its running

I have a complex program where I run multiple tasks in my tkinter mainloop. My program is working however I am having one annoying problem, I do not have an ability to stop a task unless its finished. I have developed a simple tkinter program to show this problem:
Full code :
import tkinter as tk
from tkinter import Button,Entry,Canvas,Label,ttk
class Application(tk.Frame):
def __init__(self,master=None):
super().__init__(master)
self.master = master
def Create_canvas(self,canvas_width,canvas_height):
global canvas#described as global because used outside class
canvas = tk.Canvas(self.master,bg='papaya whip',width=canvas_width,height=canvas_height)
def Application_Intro(self):
print("starting new app")
restart_program_button = tk.Button(canvas, text="Restart_program",font='Helvetica 12 bold', width=20, height=2, command =self.Restart)
start_program_button = tk.Button(canvas, text="Start_program",font='Helvetica 12 bold', width=20, height=2, command =self.Start_program)
canvas.create_text(960,20,text="MY PROGRAM",font='Helvetica 16 bold')
canvas.create_window(710,300,window = restart_program_button)
canvas.create_window(710,500,window = start_program_button)
canvas.pack()
self.master.mainloop()
def Start_program(self):
print("Program start")
self.master.after(1000,self.Start_program)
def Restart(self):
#self.master.destroy()
#self.master.quit()
print("HERE I WANT TO INTERRUPT START PROGRAM AND RETURN TO IDLE STATE")
#WHAT TO DO IN THIS FUNCTION TO GO BACK TO INITIAL MAINLOOP STATE??
return
master = tk.Tk()
app = Application(master=master)
app.Create_canvas(1920,1080)
app.Application_Intro()
The program above will create a simple GUI with 2 buttons. Initially, the program will IDLE and wait for user to start an Application. When application is started, it will call itself recursively ( checking various states and doing other complex operations in my real program), however, I Want to be able to interrupt this operation and stop it at any time. That is why I have created a button "RESTART" which should restart the program back to its initial state where I am waiting for user to start and application again. How can I return out of the Start_program function after the button is pressed?
I cant really find relevant information on the internet apart from master.destroy() or master.quit().
master.destroy() destroys my mainloop completely which is not what I want, and calling master.quit() does not even seem to do anything.
Adding an image url so you can understand it easier:
https://ibb.co/djMd5TW
It depends how much of the program you are willing to rewrite. IMO, the "best-case scenario" would be not deleting the main window and using as few TopLevels as possible. I would recommend instead just creating a series of frames so you can add and remove the entire content of the window very easily. These frames would be your own classes with their parent being tkinter.Frame and contain all the logic for that screen.
The reason I am suggesting this is because it appears you have ~700+ lines of methods which (I know from experience ;-) is a real pain to maintain (any more than a few hundred lines and I will start to use the method above). If you want an example of this method, please see the code at the bottom of this answer.
A slightly smaller change, I would recommend you replace your from tkinter import * with import tkinter as tk as the former creates a very cluttered namespace:
from tkinter import *: This imports the Tkinter library into the global namespace. This isn't best practice, because it fills your namespace with a lot of classes, which you might accidentally overwrite, but it's okay for very small scripts. [Creating a Tkinter Hello World - Python GUI programming with Tkinter]
Another small change I would recommend is to stop creating your own message windows (like that declaring "Login Success") and instead look at the tkinter.messagebox module (in that situation, you could use showinfo).
If you have lots of time on your hands, take a look at the IDLE source as it is a tkinter program that is (generally speaking) very well written (I believe Thonny also uses tkinter).
#/usr/bin/python3
import tkinter as tk
class Login(tk.Frame):
def __init__(self, *args, **kw):
super().__init__(*args, **kw)
self.create_widgets()
self.pack(expand=True, fill="both")
def create_widgets(self):
# your login screen widgets ...
# I use this method to stop __init__ becoming too big and also to
# give me the option to seperate widget creating from
# instantiation if I wish
tk.Button(self, text="Login", command=self.check_creds).pack()
def check_creds(self):
# mysql and other logic
# assuming everything is good to go:
self.destroy()
Welcome(self.master)
class Welcome(tk.Frame):
def __init__(self, *args, **kw):
super().__init__(*args, **kw)
self.create_widgets()
self.pack(expand=True, fill="both")
def create_widgets(self):
# again, your widgets are created here...
tk.Button(self, text="Log out", command=self.log_out).pack()
def log_out(self):
self.destroy()
Login(self.master)
root = tk.Tk()
Login(root)
root.mainloop()

Defined a window as a class, but unable to call mainloop function

I am so trash at coding that I have no idea how to utilise classes to make cool stuff :( --- I'm really new to GUI development, and I'm trying to make a simple maze game with a level selector. I have the maze program squared away, but I am somehow hopeless at Tkinter apparently, since I've been trying constantly for the last hour to find a solution online. As you might have noticed, this is my first post here.
I'm running this in PyCharm, using my decent computer on Windows 10. I'm especially trash at this IDE since I, for some reason, cannot install any libraries/ use any libraries that I see clearly installed in my list of libraries... but that's for another post. As I've mentioned, I've been trying to figure out a simple program for the past hour, but nothing seems to be working.
Nothing I find online is particularly useful, either, and the ones that might be are so hopelessly complex that I cannot understand what they are trying to achieve. I'm looking for a simple solution to a simple problem, and hopefully, this great community can help me out.
import tkinter as tk
class Window():
def __init__(self):
self = tk.Tk()
self.geometry("%dx%d+0+0" % (1920,1080))
root = Window()
root.mainloop()
Expected: Window appears
Observed: Program abruptly ends
Error:
Traceback (most recent call last):
File "C:/Users/(GD) ShadowPlague/PycharmProjects/GameDesign/Main.py", line 12, in <module>
root.mainloop()
AttributeError: 'Window' object has no attribute 'mainloop'
You create class in wrong way. You can't assign Tk() to self to create correctly class. External root will have nothing to do with internal self. First you create instance Window() and assign to variable root but later you create instance Tk() and assign to self but it will not change instance assigned to root.
First method: create Tk() inside class as self.root and then you use win.root
import tkinter as tk
class Window():
def __init__(self):
self.root = tk.Tk()
self.root.geometry("%dx%d+0+0" % (1920,1080))
win = Window()
win.root.mainloop()
Second method: inherit from Tk(). It needs Window(tk.Tk) and super().__init__
import tkinter as tk
class Window(tk.Tk):
def __init__(self):
super().__init__()
self.geometry("%dx%d+0+0" % (1920,1080))
root = Window()
root.mainloop()

Setting font in tkinter returns errors

I'm in the middle of rewriting the code for my first tkinter application, in which I'd avoided using classes. That was a dead end and I have to finally learn class programming in python. I've encountered a very weird error and I have no idea how to fix it. I've tried, but to no effect. What I'm trying to do is specify a font for two labels in my app. It worked well in my previous, class-free code but now it gives me an error:
(...) line 56, in create_widgets
TimeFont = font.Font(family='Palatino', size=88, weight='bold')
File "/Library/Frameworks/Python.framework/Versions/3.3/lib/python3.3/tkinter/font.py", line 71, in __init__
root = tkinter._default_root
AttributeError: 'module' object has no attribute '_default_root'
Here's the function I'm using for creating widgets:
def create_widgets(self):
self.set_timer = ttk.Entry(self, textvariable=self.timer)
self.start = ttk.Button(self, text='Start', command=self.start)
TimeFont = font.Font(family='Palatino', size=88, weight='bold') #the infamous line 56
self.display1 = ttk.Label(self, textvariable=self.player1, font=TimeFont)
self.display2 = ttk.Label(self, textvariable=self.player2, font=TimeFont)
And some more code "from above" in case its relevant:
from decimal import *
from tkinter import *
from tkinter import ttk
from tkinter import font
import time, _thread, subprocess
class Chclock(ttk.Frame):
#classmethod
def main(cls):
NoDefaultRoot()
root = Tk()
app = cls(root)
app.grid(sticky=NSEW)
root.grid_columnconfigure(0, weight=1)
root.grid_rowconfigure(0, weight=1)
root.resizable(True, False)
root.mainloop()
def __init__(self, root):
super().__init__(root)
root.bind('q', self.player1move)
root.bind('p', self.player2move)
root.bind('b', self.pause)
root.bind('a', self.undo)
root.bind('l', self.undo)
self.create_variables()
self.create_widgets() #here I call the function containing the error
self.grid_widgets()
self.grid_columnconfigure(0, weight=1)
It's probably something silly but I just can't understand what's causing this problem. It used to work fine...
Thanks!
Perhaps the code "NoDefaultRoot()" and the error message "object has no attribute '_default_root'" might have something to do with each other? Notice a correlation? First rule of debugging is to assume the error message is telling you something useful.
The problem is that you are creating a font object without telling that object what window it belongs to. Since you aren't telling it, it chooses to use the default root window. However, you've explicitly requested no default root window.
This is a somewhat strange way to structure your Tkinter program. I recommend reading the answers in the question Python Tkinter Program Structure
Well I've managed to find it. Since some kind person gave this question an upvote I'll post the solution: I've deleted the NoDefaultRoot() line. I'm not sure why it didn't work, and why it works now, but it does... Can someone explain what's happened in the comments? I'm really new to this stuff and the line I'd deleted came with a template.
Sorry if I made a mess.

Categories

Resources