Having trouble with Tkinter using OOP - python

I have created a small application in tkinter before using just top down programming but I am starting another project, this time using OOP and classes. But I'm having a hard time getting started, I just need someone to point me in the right direction. I've already dabbled in OOP with PyGame but I'm having difficulty with tkinter. Heres my code, where i'm just trying to display a button to the screen:
import tkinter as tk
from tkinter import ttk as ttk
import sqlite3
class Button(tk.Frame):
def __init__(self):
tk.Frame.__init__(self)
tk.Button(root, text = "Hello", width = 25)
class MainApplication(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent
self.button = Button()
self.button.pack(side="bottom",fill="x")
if __name__ == "__main__":
root = tk.Tk()
MainApplication(root).pack(side="top", fill="both", expand=True)
root.mainloop()

Try this:
import tkinter as tk
from tkinter import ttk # The `as tkk` isn't needed
# Here you might want to consider inheriting from `tk.Button` but it isn't going to change anything
class Button(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
# It's always a good idea to keep a reference to all of your widgets
self.button = tk.Button(self, text="Hello", width=25)
# You should call `.pack`/`.grid`/`.place` here:
# Note it doesn't really matter which one you choose
self.button.pack(fill="both")
class MainApplication(tk.Frame):
def __init__(self, parent, *args, **kwargs):
tk.Frame.__init__(self, parent, *args, **kwargs)
self.parent = parent # Technically this isn't needed because the line above sets `self.master` to parent
self.button = Button(self) # Pass in self into the Button class
self.button.pack(side="bottom", fill="x")
if __name__ == "__main__":
root = tk.Tk()
main_app = MainApplication(root)
main_app.pack(side="top", fill="both", expand=True)
root.mainloop()
I passed in self when creating the Button object in self.button = Button() and I called self.button.pack(...) inside the Button class.
The whole point of OOP programming it to limit global variables and group similar objects in a single class definition. That is why both your Button and MainApplication classes shouldn't rely on root. Apart from that, your code is very nice :D

Related

How do I create different Tkinter frames across multiple files?

I am creating a GUI on Tkinter that needs to open or close windows when buttons are pressed, and I would prefer if each window was in its own file. I tried creating a very simple example of this with the three files below. The first window is supposed to have a button that, when pressed, closes the current window and opens the next one. I'm currently running into a problem where the window is created but the button is not. How do I fix this?
Main.py
from MyTkWindow import *
myWindow = MyTkWindow()
myWindow.start()
MyTkWindow.py
import tkinter as tk
from NextFrame import *
class MyTkWindow(tk.Frame):
def __init__(self, parent=None):
tk.Frame.__init__(self)
nextWin = NextWindow()
NextScreen = tk.Button(self, text="Next", command=lambda:[self.destroy(), nextWin.start()])
NextScreen.pack()
def start(self):
self.mainloop()
NextFrame.py
import tkinter as tk
class NextWindow(tk.Frame):
def __init__(self, parent=None):
tk.Frame.__init__(self)
Leave = tk.Button(self, text="Quit", command=lambda: self.destroy())
Leave.pack()
def start(self):
self.mainloop()
I got this to work with the indicated changes. The primary problem was due to not calling the pack() method of the windows/frames being created.
main.py:
from MyTkWindow import *
myWindow = MyTkWindow()
myWindow.pack() # ADDED
myWindow.start()
MyTkWindow.py:
import tkinter as tk
from NextFrame import *
class MyTkWindow(tk.Frame):
def __init__(self, parent=None):
tk.Frame.__init__(self, parent) # ADDED parent argument.
nextWin = NextWindow()
NextScreen = tk.Button(self, text="Next",
command=lambda: [self.destroy(),
nextWin.pack(), # ADDED
nextWin.start()])
NextScreen.pack()
def start(self):
self.mainloop()
NextFrame.py:
import tkinter as tk
class NextWindow(tk.Frame):
def __init__(self, parent=None):
tk.Frame.__init__(self, parent) # ADDED parent argument.
Leave = tk.Button(self, text="Quit",
command=lambda: self.destroy())
Leave.pack()
def start(self):
self.mainloop()
Suggestion: Read and start following the PEP 8 - Style Guide for Python Code because it will make your code more understanable and maintainable. Specifically, the Naming Conventions section especially with respect to class, variable, and module file names.

Tkinter code not showing button (v:3.6)

This is my practice from youtube, It's not showing button in window
import tkinter as tk
class Window(Frame):
def _init_ (self, master =None):
Frame.__init__(self, master)
self.master = master
self.init_window()
def init_window(self):
self.master.title('GUI')
self.pack(fill = BOTH, expand = 1)
quitButton = Button(self, text = "Quit")
quitButton.place(x=0, y =.1)
root = tk.Tk()
root.title('GUI')
root.geometry()
app = Window(master=root)
root.mainloop()
Just change your _init_ to __init__.
Python constructor syntax has double underscore.
UNDERSTANDING SELF AND __INIT__ METHOD IN PYTHON CLASS.
__init__ is a reseved method in python classes. It is known as a constructor in object oriented concepts. This method called when an object is created from the class and it allow the class to initialize the attributes of a class.

Tkinter Toplevel always in front

I am working on a program that uses a tkinter TopLevel window to display periodically updating log information to the user. My problem is that the main program is fullscreen, so whenever they interact with it after opening the log window, the log window isn't visible since it is now behind the main program.
Is there a way to force a Toplevel window (or actually, any Tkinter window) to remain permanently ontop of all other windows?
Consider this quick setup for example:
import tkinter as tk
from tkinter import ttk
class Example(tk.Frame):
def __init__(self, master, *args, **kwargs):
tk.Frame.__init__(self, master, *args, **kwargs)
self.pack()
btn = ttk.Button(self, text = "Press", command = self.openTopLevel)
btn.pack()
def openTopLevel(self):
topLevelWindow = tk.Toplevel(self)
root = tk.Tk()
main = Example(root)
root.mainloop()
When you Press the button and open the Toplevel Window, it is on top. But if you grab the Frame, move it around, etc, the Toplevel goes behind it. How do I stop that? Or is that not something Tkinter allows me to do?
To make a window stay in front of others in a tkinter application, use attributes('-topmost', 'true'). In your code, it is a one-line to add.
import tkinter as tk
from tkinter import ttk
class Example(tk.Frame):
def __init__(self, master, *args, **kwargs):
tk.Frame.__init__(self, master, *args, **kwargs)
self.pack()
btn = ttk.Button(self, text = "Press", command = self.openTopLevel)
btn.pack()
def openTopLevel(self):
topLevelWindow = tk.Toplevel(self)
# Make topLevelWindow remain on top until destroyed, or attribute changes.
topLevelWindow.attributes('-topmost', 'true')
root = tk.Tk()
main = Example(root)
root.mainloop()

Inheriting from Frame or not in a Tkinter application

I've seen two basic ways of setting up a tkinter program. Is there any reason to prefer one to the other?
from Tkinter import *
class Application():
def __init__(self, root, title):
self.root = root
self.root.title(title)
self.label = Label(self.root, text='Hello')
self.label.grid(row=0, column=0)
root = Tk()
app = Application(root, 'Sample App')
root.mainloop()
and
from Tkinter import *
class Application(Frame):
def __init__(self, title, master=None):
Frame.__init__(self, master)
self.grid()
self.master.title(title)
self.label = Label(self, text='Hello')
self.label.grid(row=0, column=0)
app = Application('Sample App')
app.mainloop()
The option I prefer* is to inherit from the class Tk. I think it is the more reasonable choice since the window is, in effect, your application. Inheriting from Frame doesn't make any more sense to me then inheriting from Button or Canvas or Label. Since you can only have a single root, it makes sense that that is what you inherit from.
I also think it makes the code more readable if you do the import as import Tkinter as tk rather than from Tkinter import *. All of your calls then explicitly mention the tk module. I don't recommend this for all modules, but to me it makes sense with Tkinter.
For example:
import Tkinter as tk
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.label = tk.Label(text="Hello, world")
self.label.pack(padx=10, pady=10)
app = SampleApp()
app.mainloop()
* Note: since originally writing this answer I have changed my position. I now prefer to inherit from Frame rather than Tk. There's no real advantage one way or the other, it's more of a philosophical choice than anything else. Regardless, I believe that whether you inherit from Frame or Tk, I think either choice is better than the first example in the code that inherits from nothing.
The one slight advantage inheriting from Frame has over Tk is in the case where you want your application to support multiple identical windows. In that case, inheriting from Frame lets you create the first window as a child of root, and additional windows as children of instances of Toplevel. However, I've seen very few programs that ever have a need to do this.
For more information about how I think Tkinter programs should be structured, see my answer to the question Python Tkinter program structure.
A Frame is usually used as a geometry master for other widgets.
Since an application usually has numerous widgets, you'll often want to contain them all in a Frame, or at least use the Frame to add some borderwidth, padding, or other nicety.
Many example snippets you might find on the web do not use a Frame because
they just want to demonstrate some feature in the shortest amount of code.
So, use a Frame if you need it, otherwise, do not.
Edit: I think the best way to organize a GUI is given in this Tkinter tutorial:
simpleApp.py:
import Tkinter as tk
class SimpleApp(object):
def __init__(self, master, **kwargs):
title=kwargs.pop('title')
frame=tk.Frame(master, **kwargs)
frame.pack()
self.label = tk.Label(frame, text=title)
self.label.pack(padx=10,pady=10)
if __name__=='__main__':
root = tk.Tk()
app = SimpleApp(root,title='Hello, world')
root.mainloop()
This is mainly like your first example in that SimpleApp inherits from object, not Frame. I think this is better than subclassing Frame since we are not overriding any Frame methods. I prefer to think of SimpleApp as having a Frame rather than being a Frame.
Having SimpleApp subclass object does have a significant advantage over subclassing tk.Tk, however: it makes it easy to embed SimpleApp in a larger app:
import simpleApp
import Tkinter as tk
class BigApp(object):
def __init__(self, master, **kwargs):
title=kwargs.pop('title')
frame=tk.Frame(master, **kwargs)
frame.pack()
self.simple = simpleApp.SimpleApp(frame,title=title)
frame.pack(padx=10, pady=10)
self.simple2 = simpleApp.SimpleApp(frame,title=title)
frame.pack()
if __name__=='__main__':
root = tk.Tk()
app = BigApp(root,title='Hello, world')
root.mainloop()
Thus, simpleApp.py can be a stand-alone script as well as an importable module.
If you try this with SimpleApp inheriting from tk.Tk, you end up with extra undesired windows.
There can be an advantage to setting your top level object to inherit from Tk instead of Frame. The advantage arises when you have dynamic element to your GUI, e.g. a Label whose contents you want to set with a textvariable=foo instead of text= 'Label text'.
In this case, it is very helpful to use the Tkinter.DoubleVar, Tkinter.IntVar, and Tkinter.StringVar objects to hold the data, since the GUI will automatically update whenever these objects are set. However, to use these objects, you must specify their master as the root Tkinter.Tk() instance running. This is easier if you explicitly make your main object be a subclass of Tkinter.Tk, then have that generate frames and widgets, so you can pass along the Tk instance and set up your variables properly.
Here is a short example program to illustrate the idea.
import Tkinter as tk
class Tkclass(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
app=Application(self)
app.master.title("Animal to Meat")
app.mainloop()
class Application(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.grid(sticky=tk.N+tk.S+tk.E+tk.W)
self.meatvar = tk.StringVar(master=parent)
self.meatvar.set("Meat?")
self.createWidgets()
def createWidgets(self):
top=self.winfo_toplevel()
top.rowconfigure(0, weight=1)
top.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
self.columnconfigure(0, weight=1)
self.columnconfigure(1, weight=1)
self.columnconfigure(2, weight=1)
self.columnconfigure(3, weight=1)
self.cowButton = tk.Button(self, text='Cow', command=self.setBeef)
self.cowButton.grid(row=0,column=0)
self.pigButton = tk.Button(self, text='Pig',command=self.setPork)
self.pigButton.grid(row=0,column=1)
self.meatLabel = tk.Label(self)
self.meatLabel.configure(textvariable=self.meatvar)
self.meatLabel.grid(row=0,column=2)
self.quit = tk.Button(self, text='Quit',command=self.QuitApp)
self.quit.grid(row=0, column=3)
def setBeef(self):
self.meatvar.set("Beef")
def setPork(self):
self.meatvar.set("Pork")
def QuitApp(self):
top=self.winfo_toplevel()
top.quit()
main = Tkclass()

Using Tkinter in python to edit the title bar

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

Categories

Resources