Okay, i've been trying to create a small text editor in Tkinter. I've stubble across a problem and I can't seem to find the answer. If anyone could just help me, I'd be very happy.
First of all, here is my code :
import tkinter as tk
import tkinter.filedialog as tkfile
class PyTedi(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
# Instantiate Menu
main_menu = tk.Menu(self)
menu_bar = PyTediMenu(main_menu)
main_menu.add_cascade(label='File', menu=menu_bar)
self.config(menu=main_menu)
# Instantiate Text Area
text_area = PyTediTextArea(self)
text_area.pack(side=tk.BOTTOM)
# Instantiate Tool Bar
tool_bar = PyTediToolBar(self)
tool_bar.pack(side=tk.TOP)
class PyTediMenu(tk.Menu):
def __init__(self, parent):
tk.Menu.__init__(self, parent)
self.add_command(label='New', command=None)
self.add_command(label='Open', command=None)
self.add_command(label='Save', command=tkfile.asksaveasfile)
self.add_separator()
self.add_command(label='Exit', command=self.quit)
class PyTediToolBar(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent, height=30)
class PyTediTextArea(tk.Text):
def __init__(self, parent):
tk.Text.__init__(self, parent)
if __name__ == '__main__':
app = PyTedi()
app.mainloop()
Basically, I've found out, (From another stack question) That it is a good idea to create class based components... My problem is, let's say I want to create a command -> Save File. So I create a method inside my Menu and link to the save function. BUT, how do I grab the text area content and write it to a file ? They are not even part of the same class. Is it a bad design implementation or it's just me ?
Thanks !
while it is a good idea to use class based programming, i would like to point out that unless you are modifying the widget in some way, subclassing it is completely unnecessary, when you create the class PyTediTextArea you aren't actually modifying the original text class in any way, so it would be simpler for you to simply change
text_area = PyTediTextArea(self)
to
self.text_area = tk.Text(self)
that way you save yourself subclassing at the bottom and from anywhere in your main class you can simply call
self.text_area.get(0, "end")
to get all of the text in the widget
James
Related
So I am trying to import my own custom style methods into my main app to then use as a custom style in ttk.Label(), by calling the class method, but I'm having trouble finding a way to call it. Below is the example code of the main app.
import tkinter as tk
from tkinter import ttk
from custom_styles import customStyle
class MainApp:
def __init__(self, master):
self.master = master
**initialization code****
#----style methods-----#
self.styled = customStyle(self.master)
#title label
self.title_label = ttk.Label(self.master, text="test", style=self.styled.test())
self.title_label.pack()
And below is the class I am calling the methods above from, which is different file.
from tkinter import ttk
import tkinter as tk
class customStyle:
def __init__(self, master) -> None:
self.master = master
def test(self):
style = ttk.Style()
style.configure("test.TLabel",
foreground="white",
background="black",
padding=[10, 10, 10, 10])
I've tried to call just the name of the style method like this
self.title_label = ttk.Label(self.master, text="test", style='test.TLabel')
I've also tried to call the method by calling the class then method like this
self.title_label = ttk.Label(self.master, text="test", style=self.styled.test())
I knew this wouldn't work, but I still tried it
self.title_label = ttk.Label(self.master, text="test", style=self.styled.test('test.TLabel'))
I also tried not making an object out of the methods, so I took away the class and just made a list of functions, but that didn't work either. Of course, I looked on the internet and searched stack for questions, but to no avail. Maybe this structure I am trying to maintain is not efficient?
I'm honestly just looking to understand a way to call the methods w/o putting them in the same file, but I just don't know how to.
The style option requires the name of a style as a string. Since your function test returns None, it's the same as if you did ttk.Label(..., style=None)
One solution is to have your test function return the style name:
def test(self):
...
return "test.TLabel"
Of course, that means you can only use it for that one specific style. Another solution is that you leave it as-is and return nothing. In that case you can just hard-code the style. You must ensure that you call the test function, however, so that the style is initialized.
self.styled.test()
self.title_label = ttk.Label(self.master, text="test", style="test.TLabel")
Arguably, a better option would be to add attributes to the class, and initialize the styles when you instantiate the class. It might look something like this:
class customStyle:
def __init__(self, master) -> None:
self.master = master
style = ttk.Style()
style.configure("test.TLabel",...)
...
self.label = "test.TLabel"
self.button = "test.TButton"
self.scrollbar = "test.TScrollbar"
...
class MainApp:
def __init__(self, master):
self.master = master
self.styled = customStyle(self.master)
self.title_label = ttk.Label(..., style=self.styled.label)
...
There are probably even better ways to do this. The point is, you need to pass a valid style name as a string to the style parameter of a ttk widget.
Let me start this off by saying that I have scoured stack overflow and I have been unable to find an answer to my issue. I have found something that is very close, but I still am unable to resolve my issue. If you are aware of a link that would be helpful, please include it in the comments.
I am relatively new to programming in Python so please provide constructive criticism if you see any incorrect programming practices or if anything can be programmed in a more pythonic style.
I'm having a problem with getting a variable from a radio button in one class and printing it out in a label on a different frame in a different class. FYI: As of right now, all classes are in the same file but I plan to change that after I complete the program.
Here is my controller class:
import tkinter as tk
from tkinter import ttk
class MainWindow(tk.Tk):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.emotion_list = ["angry", "sad", "happy", "fearful", "surprised", "curious",
"anxious", "chill"]
self.title("Application")
self.frames = {}
inner_container = ttk.Frame(self)
inner_container.grid(sticky='ew')
for FrameClass in (ChooseFeelings, ChooseState):
frame = FrameClass(inner_container, self, self.pages)
self.frames[FrameClass] = frame
frame.grid(row=0, column=0, sticky='nsew')
self.show_frame(ChooseFeelings)
def show_frame(self, inner_container):
frame = self.frames[inner_container]
frame.tkraise()
def get_frame(self, classname):
"""Returns an instance of a frame given its class name as a string"""
for page in self.frames.values():
if str(page.__class__.__name__) == classname:
return page
return None
As you can see, I have included a method at the bottom of my controller class that can be called to create an instance of any class when needed.
I have also not included all classes in the self.frames dictionary because they don't pertain to the issue at hand.
Here I have the class that displays the radio buttons to the user. I am well aware that as it looks right now, they aren't organized on the frame. There are other widgets on the frame that I am omitting from the class because they don't pertain to the issue.
class ChooseFeelings(ttk.Frame):
def __init__(self, container, controller, **kwargs):
super().__init__(container, **kwargs)
emotion_container = ttk.Frame(self, padding='100 15 30 15')
emotion_container.grid(sticky='ew')
self.controller = controller
self.selected_emotion = tk.StringVar()
move_on_button = ttk.Button(emotion_container, text="Click me when you are ready to move on",
command=lambda: [controller.show_frame(ChooseState), print(selected_emotion.get())])
move_on_button.pack(side='top')
for emotions in self.controller.emotion_list:
ttk.Radiobutton(emotion_container, text=emotions, variable=self.selected_emotion,
value=emotions).pack(side='bottom')
After clicking the move_on_button, I print out to the console the variable that I hope to get in the label on the next frame.
Here is the frame where the variable should print out to the user.
class ChooseState(ttk.Frame):
def __init__(self, container, controller, **kwargs):
super().__init__(container, **kwargs)
self.controller = controller
state_container = ttk.Frame(self, padding='100 15 30 15')
state_container.grid(sticky='ew')
self.emotion_label = ttk.Label(state_container, text=f"You chose {self.get_feels()} as your emotion.")
self.emotion_label.grid(row=0, column=0, sticky='w')
def get_feels(self):
feelings_frame = self.controller.get_frame("ChooseFeelings")
user_emotion = feelings_frame.selected_emotion.get()
return user_emotion
root = MainWindow()
root.mainloop()
As you can see, the get_feels method is used to create an instance of the ChooseFeelings class, then the variable 'user_emotion' is supposed to hold the selected radio button variable. Once that happens, the 'user_emotion' variable is returned and should be printed out to the screen. Or so I thought....
In the ChooseState class, I should be printing out in 'self.emotion_label', the chosen radio button variable from the previous class. Instead all I get is "You chose as your emotion". All I would like to do is pass the radio button variable from the previous class to this class without any errors.
If anyone has any insight on how to fix this problem it would be greatly appreciated. Thank You!
I have an auto generated code which generates a GUI that has various widgets in it. One of the widget is a ScrolledListBox. A part of the code is shown below:
class New_Toplevel_1:
def __init__(self, top=None):
self.Scrolledlistbox4.configure(background="white")
self.Scrolledlistbox4.configure(font="TkFixedFont")
self.Scrolledlistbox4.configure(highlightcolor="#d9d9d9")
self.Scrolledlistbox4.configure(selectbackground="#c4c4c4")
self.Scrolledlistbox4.configure(width=10)
I want to access the Scrolledlistbox4 from outside this class. So for example, I would like to write to write a function that updates the ScrolledListBox whenever I call it. I am relatively new to python and would like to know how can I accomplish this.
You need to first create a Scrolledlistbox4 object as an attribute:
self.scrolled_listbox = Scrolledlistbox4(...)
then you can do all configures in outermost scope like:
a = New_Toplevel_1()
a.scrolled_listbox.configure(background='white')
...
In below example "Outside Button" changes the text option of a class' button from the outside:
import tkinter as tk
class FrameWithButton(tk.Frame):
def __init__(self, master):
super().__init__(master)
self.btn = tk.Button(root, text="Button")
self.btn.pack()
root = tk.Tk()
an_instance = FrameWithButton(root)
an_instance.pack()
def update_button():
global an_instance
an_instance.btn['text'] = "Button Text Updated!"
tk.Button(root, text="Outside Button", command=update_button).pack()
root.mainloop()
I have a question. I thought that a class can be based on an object or previously define class. When I change it into class Application(object): it doesn't work. Can you tell me why it didn't work and why did the code below works or why did class Application(Frame) works? Frame is not a previously define object and not object. Sorry for my English. Here is my code:
# Lazy Buttons
# Demonstrates using a class with tkinter
from tkinter import *
class Application(Frame): #
""" A GUI application with three buttons. """
def __init__(self, master):
super(Application, self).__init__(master)
self.grid()
self.create_widgets()
def create_widgets(self):
""" Create three buttons that do nothing. """
# create the first Button
self.bttn1 = Button(self, text= "I do nothing.")
self.bttn1.grid()
# create second button
self.bttn2 = Button(self)
self.bttn2.grid()
self.bttn2.configure(text="Me too!")
# create third button
self.bttn3 = Button(self)
self.bttn3.grid()
self.bttn3["text"] = "Same here!"
# main
root= Tk()
root.title("Lazy Buttons")
root.geometry("200x85")
app = Application(root)
root.mainloop()
Frame is a previously defined class. It's part of tkinter, which you imported on your first line.
Your Application class extends Frame, which means that it gets methods from Frame and can do everything that a Tk Frame can do, like show widgets. If you don't extend Frame, and only extend object instead, this will not work.
It might be clearer to replace...
from tkinter import *
with...
import tkinter as tk
and fix your references to Tk's classes (they would become tk.Button, tk.Frame and tk.Tk).
I am trying to add a custom title to a window but I am having troubles with it. I know my code isn't right but when I run it, it creates 2 windows instead, one with just the title tk and another bigger window with "Simple Prog". How do I make it so that the tk window has the title "Simple Prog" instead of having a new additional window. I dont think I'm suppose to have the Tk() part because when i have that in my complete code, there's an error
from tkinter import Tk, Button, Frame, Entry, END
class ABC(Frame):
def __init__(self,parent=None):
Frame.__init__(self,parent)
self.parent = parent
self.pack()
ABC.make_widgets(self)
def make_widgets(self):
self.root = Tk()
self.root.title("Simple Prog")
If you don't create a root window, Tkinter will create one for you when you try to create any other widget. Thus, in your __init__, because you haven't yet created a root window when you initialize the frame, Tkinter will create one for you. Then, you call make_widgets which creates a second root window. That is why you are seeing two windows.
A well-written Tkinter program should always explicitly create a root window before creating any other widgets.
When you modify your code to explicitly create the root window, you'll end up with one window with the expected title.
Example:
from tkinter import Tk, Button, Frame, Entry, END
class ABC(Frame):
def __init__(self,parent=None):
Frame.__init__(self,parent)
self.parent = parent
self.pack()
self.make_widgets()
def make_widgets(self):
# don't assume that self.parent is a root window.
# instead, call `winfo_toplevel to get the root window
self.winfo_toplevel().title("Simple Prog")
# this adds something to the frame, otherwise the default
# size of the window will be very small
label = Entry(self)
label.pack(side="top", fill="x")
root = Tk()
abc = ABC(root)
root.mainloop()
Also note the use of self.make_widgets() rather than ABC.make_widgets(self). While both end up doing the same thing, the former is the proper way to call the function.
Here it is nice and simple.
root = tkinter.Tk()
root.title('My Title')
root is the window you create and root.title() sets the title of that window.
Try something like:
from tkinter import Tk, Button, Frame, Entry, END
class ABC(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.pack()
root = Tk()
app = ABC(master=root)
app.master.title("Simple Prog")
app.mainloop()
root.destroy()
Now you should have a frame with a title, then afterwards you can add windows for
different widgets if you like.
One point that must be stressed out is:
The .title() method must go before the .mainloop()
Example:
from tkinter import *
# Instantiating/Creating the object
main_menu = Tk()
# Set title
main_menu.title("Hello World")
# Infinite loop
main_menu.mainloop()
Otherwise, this error might occur:
File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/tkinter/__init__.py", line 2217, in wm_title
return self.tk.call('wm', 'title', self._w, string)
_tkinter.TclError: can't invoke "wm" command: application has been destroyed
And the title won't show up on the top frame.
Example of python GUI
Here is an example:
from tkinter import *;
screen = Tk();
screen.geometry("370x420"); //size of screen
Change the name of window
screen.title('Title Name')
Run it:
screen.mainloop();
I found this works:
window = Tk()
window.title('Window')
Maybe this helps?
Easy method:
root = Tk()
root.title('Hello World')
Having just done this myself you can do it this way:
from tkinter import Tk, Button, Frame, Entry, END
class ABC(Frame):
def __init__(self, parent=None):
Frame.__init__(self, parent)
self.parent = parent
self.pack()
ABC.make_widgets(self)
def make_widgets(self):
self.parent.title("Simple Prog")
You will see the title change, and you won't get two windows. I've left my parent as master as in the Tkinter reference stuff in the python library documentation.
For anybody who runs into the issue of having two windows open and runs across this question, here is how I stumbled upon a solution:
The reason the code in this question is producing two windows is because
Frame.__init__(self, parent)
is being run before
self.root = Tk()
The simple fix is to run Tk() before running Frame.__init__():
self.root = Tk()
Frame.__init__(self, parent)
Why that is the case, I'm not entirely sure.
self.parent is a reference to the actual window, so self.root.title should be self.parent.title, and self.root shouldn't exist.
widget.winfo_toplevel().title("My_Title")
changes the title of either Tk or Toplevel instance that the widget is a child of.
I found a solution that should help you:
from tkinter import Tk, Button, Frame, Entry, END
class ABC(Frame):
def __init__(self,master=None):
super().__init__(master)
self.pack()
self.master.title("Simple Prog")
self.make_widgets()
def make_widgets(self):
pass
root = Tk()
app = ABC(master=root)
app.mainloop()
Found at: docs.python.org