I am trying to make a tkinter desktop application (Notepad) using classes but I found an Attribute Error in my code. I made three files "menu_option.py", "textarea.py" and "window_frame.py". Run the "menu_option.py" file so that you found the error. I am using python (3.9). Also is there any another way to connect "new_file" function to menu item.
Here is my code below:
menu_option.py
from tkinter import *
from textarea import TextArea
from window_frame import Window
class Menu_Option(TextArea):
file = None
def __init__(self, master, newfile=None):
super().__init__(root)
self.Menubar = 'MenuBar'
self.Filemenu = 'FileMenu'
self.root = self.root
self.master = self.master
self.newfile = newfile
def launch(self):
self.Menubar = Menu(self.root)
self.Filemenu = Menu(master=self.Menubar, tearoff=0)
self.Filemenu.add_command(label='New File...', command=self.newfile)
self.Menubar.add_cascade(label='File', menu=self.Filemenu)
self.root.config(menu=self.Menubar)
class Features(Menu_Option):
def __init__(self, master):
super().__init__(master, newfile=self.new_file)
def new_file(self):
global file
self.root.title(self.title)
file = None
self.textarea.delete(1.0, END)
if __name__ == '__main__':
root = Tk()
Window(root).launch()
TextArea(root).launch()
Menu_Option(root).launch()
Features(root).launch()
root.mainloop()
textarea.py
from tkinter import *
from window_frame import Window
from tkinter.scrolledtext import ScrolledText
class TextArea(Window):
def __init__(self, name):
super().__init__(name)
self.name = self.root
self.master = 'root'
self.textarea = 'text_area'
self.font = 'courier 14 normal'
def launch(self):
self.textarea = ScrolledText(self.root, font=self.font)
self.textarea.pack(expand=True, fill=BOTH)
if __name__ == '__main__':
root = Tk()
Window(root).launch()
TextArea(root).launch()
root.mainloop()
window_frame.py
from tkinter import *
class Window:
def __init__(self, root):
self.root = root
self.geometry = '1000x550+100+100'
self.title = 'Untitled - ProBook'
def launch(self):
self.root.geometry(self.geometry)
self.root.title(self.title)
if __name__ == '__main__':
root = Tk()
Window(root).launch()
root.mainloop()
Error:
Exception in Tkinter callback
Traceback (most recent call last):
File "D:\Installed Programs\Python\lib\tkinter\__init__.py", line 1892, in __call__
return self.func(*args)
File "C:\Users\vaish\OneDrive\ProBook\menu_option.py", line 35, in new_file
self.textarea.delete(1.0, END)
AttributeError: 'str' object has no attribute 'delete'
Since Menu_Option() has override launch() function, therefore TextArea.launch() will not be executed and so instance variable textarea is still a string.
If child class wants to inherit parent class launch() functionality, you need to call the parent class launch() in its launch() function:
textarea.py
class TextArea(Window):
...
def launch(self):
super().launch() # call parent class launch()
self.textarea = ScrolledText(self.root, font=self.font)
self.textarea.pack(expand=True, fill=BOTH)
menu_option.py
class Menu_Option(TextArea):
...
def launch(self):
super().launch() # execute parent class launch()
...
...
if __name__ == "__main__":
root = Tk()
#Window(root).launch() # <- no need to execute
#TextArea(root).launch() # <- no need to execute
#Menu_Option(root).launch() # <- no need to execute
Features(root).launch()
root.mainloop()
Note that Window(root).launch(), TextArea(root).launch() and Menu_Option(root).launch() are not required.
You have initialized self.textarea="text_area" in textarea.py . But when you import it in menu_option.py, you are overwriting the function launch, which is supposed to set the value of self.textarea to a ScrolledText and pack it. To solve this you have to include the code in launch function of textarea.py in the function launch of Menu_Option class.
from tkinter import *
from tkinter.scrolledtext import ScrolledText
from textarea import TextArea
from window_frame import Window
class Menu_Option(TextArea):
file = None
def __init__(self, master, newfile=None):
super().__init__(root)
self.Menubar = 'MenuBar'
self.Filemenu = 'FileMenu'
self.root = self.root
self.master = self.master
self.newfile = newfile
def launch(self):
self.Menubar = Menu(self.root)
#You have to include these 2 lines of code which were overwritten
self.textarea = ScrolledText(self.root, font=self.font)
self.textarea.pack(expand=True, fill=BOTH)
self.Filemenu = Menu(master=self.Menubar, tearoff=0)
self.Filemenu.add_command(label='New File...', command=self.newfile)
self.Menubar.add_cascade(label='File', menu=self.Filemenu)
self.root.config(menu=self.Menubar)
Related
In file a there is a button that calls the execution of a function from file b and it is removed. Then another button is created that should reset gui to its original state.
Unfortunately, I don't know how to properly configure it to make it work.
In trying to fix it, you ended up with the following errors:
ImportError: cannot import name 'GUI' from partially initialized module 'b' (most likely due to a circular import) (c:\Users\user\Desktop\english_app\b.py)
TypeError: back() missing 1 required positional argument: 'root'
file a:
from tkinter import *
from b import dest
from functools import partial
class Gui:
def __init__(self, root):
self.b = Button(root, text='destroy', command=partial(dest, root))
self.b.pack()
if __name__ == "__main__":
root = Tk()
Gui(root)
mainloop()
file b:
from tkinter import *
class dest:
def __init__(self, root):
for widget in root.winfo_children():
widget.destroy()
self.b_back = Button(root, text="Back", command=self.back)
self.b_back.pack()
def back(self, root):
for widget in root.winfo_children():
widget.destroy()
Gui(root)
Need to use import statement without the from to avoid circular imports.
For example:
file a.py:
from tkinter import *
import b
from functools import partial
class Gui:
def __init__(self, root):
self.b = Button(root, text='destroy', command=partial(b.dest, root))
self.b.pack()
if __name__ == "__main__":
root = Tk()
Gui(root)
mainloop()
file b.py:
from tkinter import *
import a
class dest:
def __init__(self, root):
for widget in root.winfo_children():
widget.destroy()
self.b_back = Button(root, text="Back", command=self.back(root))
self.b_back.pack()
def back(self, root):
for widget in root.winfo_children():
widget.destroy()
a.Gui(root)
I wonder how to pass return value from one class to another class in tkinter.
In my program I have DataChosenForm class where I want to choose option in Combobox and pass this result to another class ReturnData to set a variable in a Label.
import tkinter as tk
from tkinter import ttk
class DataChosenForm(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
chosen = tk.LabelFrame(self, text="wybór")
chosen.grid(row=0)
self.combo = ttk.Combobox(chosen)
self.combo['values'] = ('wizz', 'ryanair', 'lot')
self.combo.grid(row=0, column=2, padx=80, pady=10)
self.combo.bind("<<ComboboxSelected>>", self.callback)
def callback(self, event=None):
if event.widget.get() == 'wizz':
print('wizz')
return 'wizz'
elif event.widget.get() == 'ryanair':
print('ryanair')
return 'ryanair'
elif event.widget.get() == 'lot':
print('lot')
return 'lot'
class ReturnData(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
var = tk.StringVar()
message_box = tk.LabelFrame(self, text="wynik")
message_box.grid(row=1)
mb = tk.Label(message_box, textvariable=var,anchor='nw')
mb.pack(padx=120, pady=30)
class Application(tk.Tk):
def __init__(self):
super().__init__()
self.title("program do wyszukiwania cen lotów")
self.geometry('300x200')
self.resizable(width=False, height=False)
DataChosenForm(self).grid(row=0, column=0)
ReturnData(self).grid(row=1)
if __name__ == "__main__":
app = Application()
app.mainloop()
You could first display the combobox DataChosenForm(self).grid(row=0, column=0) without calling the ReturnData in the Application class.
Then, in the callback() method collect the choice choice = event.widget.get() and pass it to ReturnData. This would mean, however, that the LabelFrame is displayed only after a choice is made.
import tkinter as tk
from tkinter import ttk
class DataChosenForm(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
chosen = tk.LabelFrame(self, text="wybór")
chosen.grid(row=0)
self.combo = ttk.Combobox(chosen)
self.combo['values'] = ('wizz', 'ryanair', 'lot')
self.combo.grid(row=0, column=2, padx=80, pady=10)
self.combo.bind("<<ComboboxSelected>>", self.callback)
def callback(self, event=None):
choice = event.widget.get()
print(choice)
ReturnData(self, choice).grid(row=1)
class ReturnData(tk.Frame):
def __init__(self, parent, choice):
super().__init__(parent)
message_box = tk.LabelFrame(self, text="wynik")
message_box.grid(row=1)
mb = tk.Label(message_box, text=choice, anchor='nw')
mb.pack(padx=120, pady=30)
class Application(tk.Tk):
def __init__(self):
super().__init__()
self.title("program do wyszukiwania cen lotów")
self.geometry('300x200')
self.resizable(width=False, height=False)
DataChosenForm(self).grid(row=0, column=0)
if __name__ == "__main__":
app = Application()
app.mainloop()
I think #Julia has the basically the right architecture for your tkinter application, but her answer could be improved by using a tkinter Variable — because all widgets associated with one will automatically update their displayed value whenever the Variable is changed (by one of them or something else).
Here's a little documentation on Variable Classes. Note that since ttk.Combobox with have all the methods of a tk.Entry widgets, here's a bit of documentation about them (which also happens to illustrate the "pattern" of using of a StringVar in conjunction with one so it also applies to a ttk.Comboboxs).
Generally, you can tell a widget to use a tkinter Variable by specifying an instance of one as the option textvariable= keyword argument when the widget is created. You can also set the option using the partial dictionary interface most widgets support, so assignments like widget['textvariable'] = variable are another way to make use of them — the code below makes use of both of these ways.
Here's Julia's code modified to use a tk.StringVar. Note the Combobox doesn't need a callback function to bind the <<ComboboxSelected>> event to it, so all that complexity has been eliminated.
import tkinter as tk
from tkinter import ttk
class DataChosenForm(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
choice = tk.LabelFrame(self, text="wybór")
choice.grid(row=0)
self.combo = ttk.Combobox(choice)
self.combo['textvariable'] = parent.var # Use shared variable.
self.combo['values'] = ('wizzair', 'ryanair', 'lot')
self.combo.grid(row=0, column=2, padx=80, pady=10)
class ReturnData(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
message_box = tk.LabelFrame(self, text="wynik")
message_box.grid(row=1)
mb = tk.Label(message_box, textvariable=parent.var, # Use shared variable.
anchor='nw', width=20)
mb.pack(padx=120, pady=30)
class Application(tk.Tk):
def __init__(self):
super().__init__()
self.title("program do wyszukiwania cen lotów")
self.geometry('300x200')
self.resizable(width=False, height=False)
self.var = tk.StringVar(value='Dokonać wyboru') # Create shared variable.
DataChosenForm(self).grid(row=0, column=0)
ReturnData(self).grid(row=1)
if __name__ == "__main__":
app = Application()
app.mainloop()
You can just pass other class or it's field to __init__ of the DataChosenForm, and to callback function from there, where then you can change class/field directly. Here's what I mean, but I use TreeView:
import tkinter as tk
from tkinter import ttk
class ConnectedClass:
def __init__(self):
self.received = "will be changed"
class TreeViewWrapper(ttk.Treeview):
def __init__(self, master, connected_class, **kw):
super().__init__(master, **kw)
parents = []
for i in range(10):
parent = self.insert("", "end", text="Item %s" % i, tags=str(i))
for i in range(3):
self.insert(parent, "end", text="Item %s" % i, tags=str(i))
self.bind("<Control-r>", lambda e: self.pass_to_other(e, connected_class))
def pass_to_other(self, _, connected_class):
items = self.selection()
connected_class.received = items
class App:
def __init__(self):
self.root = tk.Tk()
self.con_class = ConnectedClass()
self.tree = TreeViewWrapper(self.root,self.con_class)
self.tree.pack()
self.root.bind("<Control-p>",lambda e:print(self.con_class.received))
self.root.mainloop()
if __name__ == "__main__":
app = App()
I need to subclass a Label widget, so that the browser can open a link while the mouse clicks on it.
Here is the code snippet of what I've done so far.
from tkinter import *
import webbrowser
class HyperLinkLabel(Label):
def __init__(self, link, *args, **kwargs):
Label.__init__(self, *args, **kwargs)
self.link = link
self.bind("<Button-1>", self.click_callback)
def click_callback(self):
webbrowser.open_new(self.link)
if __name__ == '__main__':
master = Tk()
root = Frame(master)
label1 = HyperLinkLabel(root, link='https://www.google.com')
label1.config(text='hello')
label1.pack()
root.master.minsize(100, 50)
root.mainloop()
You should declare master (or parent) parameter and pass it to Label constructor. (root from the perspect of caller)
event handle should have event parameter even though you don't use it. Otherwise TypeError exception is thrown.
Pack frame so that widgets inside it is visible.
from tkinter import *
import webbrowser
class HyperLinkLabel(Label):
def __init__(self, master, link, *args, **kwargs): # <-- pass master parameter
Label.__init__(self, master, *args, **kwargs) # <-- pass master parameter
self.link = link
self.bind("<Button-1>", self.click_callback)
def click_callback(self, event): # <--- missed event parameter
webbrowser.open_new(self.link)
if __name__ == '__main__':
master = Tk()
root = Frame(master)
label1 = HyperLinkLabel(root, link='https://www.google.com')
label1.config(text='hello')
label1.pack()
root.pack() # <-- should pack frame; otherwise link widget is not visible
root.master.minsize(100, 50)
root.mainloop()
Well, i want to add menubar, but something is going wrong.
It says: AttributeError: 'NoneType' object has no attribute 'config'
My code:
from tkinter import *
class ApplicationWindow(Tk):
def __init__(self, master=None):
Tk.__init__(self, master)
self.master = master
self.geometry('800x400')
self.f_app = Frame(self).pack()
menubar = Menu(self.master)
self.master.config(menu=menubar)
fileMenu = Menu(menubar)
fileMenu.add_command(label="Exit", command=self.onExit)
menubar.add_cascade(label="File", menu=fileMenu)
self.b_log = Button(self, width=10, text="Войти", command=self.func).pack()
def onExit(self):
self.quit()
def func(self):
print("hello")
def main():
# root = tk
app = ApplicationWindow()
app.mainloop()
if __name__ == '__main__':
main()
You're initializing your ApplicationWindow class without passing any arguments in, like this app = ApplicationWindow(). In your init method, you give master a None default, and when you try to use master.config it says
'NoneType' object has no attribute 'config'
Try passing an argument in when you initialize the instance of ApplicationWindow. Whatever it is that you want master to be (just not a None object).
I have updated your code (below) and it runs. The button works, and the exit function closes the window. There was a lot to fix, but it runs without error. Take it from here:
import tkinter
class ApplicationWindow(tkinter.Tk):
def __init__(self, master=None):
# Tk.__init__(self, master)
self.master = master
self.master.geometry('800x400')
self.master.f_app = tkinter.Frame(self.master).pack()
menubar = tkinter.Menu(self.master)
self.master.config(menu=menubar)
fileMenu = tkinter.Menu(menubar)
fileMenu.add_command(label="Exit", command=self.onExit)
menubar.add_cascade(label="File", menu=fileMenu)
self.b_log = tkinter.Button(self.master, width=10, text="Войти", command=self.func).pack()
def onExit(self):
self.master.destroy()
def func(self):
print("hello")
def main():
root = tkinter.Tk()
app = ApplicationWindow(root)
root.mainloop()
if __name__ == '__main__':
main()
You have an argument named master=None defaults to None. So when you create an instance of ApplicationWindow() without parameter your master argument gets None, and here you are calling config() method but your master is none and it doesnt have a method named config.
class ApplicationWindow(Tk):
def __init__(self, master=None):
...
self.master.config(menu=menubar) # Error accurred here
def main():
# root = tk
app = ApplicationWindow() # pass an argument
I want to open about.py when 'about' link is pressed from the file menu of the main.py. Once again I apologize for asking the kid question. Thanks in advance.
suppose this is main.py:
from Tkinter import *
import tkFileDialog
import about
class Example(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.initUI()
def initUI(self):
self.parent.title("File dialog")
self.pack(fill=BOTH, expand=1)
menubar = Menu(self.parent)
self.parent.config(menu=menubar)
fileMenu = Menu(menubar)
fileMenu.add_command(label="About", command=self.onAbout)
menubar.add_cascade(label="File", menu=fileMenu)
self.txt = Text(self)
self.txt.pack(fill=BOTH, expand=1)
def onAbout(self):
pass #how can I call about.py here..?
def main():
root = Tk()
ex = Example(root)
root.geometry("300x250+300+300")
root.mainloop()
if __name__ == '__main__':
main()
And about.py look some thing like this:
from Tkinter import *
class About(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.initUI()
def initUI(self):
pass
def main():
root = Tk()
ex = About(root)
root.geometry("630x400+200+200")
root.mainloop()
if __name__ == '__main__':
main()
This is how you do it:
def onAbout(self):
new_root = Tk()
new_root.geometry("630x400+200+200")
About(root)