How to seperate Tkinter Gui app source code into multiple files - python

i'm working on downloading manager python gui app using Tkinter and halfway there my code started to look very messy so i decided to seperate functions on different file and then import it:
my main code:
from tkinter import *
from functions import add_download
root = Tk()
root.title("The Pownloader!")
canvas = Canvas(root, width=700, height=500).pack()
# Buttons:
ADD_BUTTON = Button(root, text="ADD", bd=4, height=2, width=5, command=add_download)
SETTINGS_BUTTON = Button(root, text="SETTINGS", bd=4, height=2, width=5)
ABOUT_BUTTON = Button(root, text="ABOUT", bd=4, height=2, width=5)
EXIT_BUTTON = Button(root, text="EXIT", bd=4, height=2, width=5, command=quit)
# Mini-Buttons:
PAUSE_MINI_BUTTON = Button(root, text="PAUSE", font=(None, "8"), height=2, width=3)
RESUME_MINI_BUTTON = Button(root, text="RESUME", font=(None, "8"), height=2, width=3)
REMOVE_MINI_BUTTON = Button(root, text="REMOVE", font=(None, "8"), height=2, width=3)
# Side_Mini_Buttons:
DOWNLOAD_WINDOW = Button(root, text="Downloads", font=(None, "8"), height=3, width=6)
ERRORS_WINDOW = Button(root, text="Failed", font=(None, "8"), height=3, width=6)
COMPLETED_WINDOW = Button(root, text="Completed", font=(None, "8"), height=3, width=6)
# Positionning Buttons:
ADD_BUTTON.place(x=70, y=20)
SETTINGS_BUTTON.place(x=145, y=20)
ABOUT_BUTTON.place(x=220, y=20)
EXIT_BUTTON.place(x=295, y=20)
PAUSE_MINI_BUTTON.place(x=290, y=455)
RESUME_MINI_BUTTON.place(x=340, y=455)
REMOVE_MINI_BUTTON.place(x=390, y=455)
DOWNLOAD_WINDOW.place(x=1, y=100)
ERRORS_WINDOW.place(x=1, y=160)
COMPLETED_WINDOW.place(x=1, y=220)
# Download Frame:
DOWNLOAD_LIST_LABEL = Label(root, text="Download List:")
DOWNLOAD_LIST_LABEL.place(x=70, y=80)
DOWNLOAD_ENTRIES = Listbox(root, width=70, height=19)
DOWNLOAD_ENTRIES.place(x=70, y=100)
# Main Loop:
root.mainloop()
However my functions.py code looks like this:
def add_download():
# Defining The Pop-up frame:
top = Toplevel(root, width = 420, height = 150)
top.title("New Download")
# Putting on widgets:
link = StringVar()
LINK_LABEL = Label(top, text = "Paste Link:")
FIELD_ENTRY = Entry(top, width = 40, textvariable=link)
def on_click():
link_to_verify = (link.get()).strip()
if len(link_to_verify)>15:
if link_to_verify[0:11]=="http://www.":
DOWNLOAD_ENTRIES.insert(0, link_to_verify)
else:
print("Stupid")
else:
print("not a valid link")
BUTTONS_WIDGET = Frame(top)
ADD_BUTTON = Button(BUTTONS_WIDGET, text = "Add", width=10, command=on_click)
CANCEL_BUTTON = Button(BUTTONS_WIDGET, text = "Cancel", width=10, command=top.destroy)
# Positionning everythig:
LINK_LABEL.grid(column=0,row=0)
FIELD_ENTRY.grid(column=1,row=0)
BUTTONS_WIDGET.grid(column=1,row=2)
ADD_BUTTON.grid(column=0,row=0)
CANCEL_BUTTON.grid(column=1,row=0)
basically i wanted the function to call and show a pop-up window, i'm sure this could done in a million times better but i'm just learning, however i receive an error says:
Toplevel is not defined

Every file needs to import tkinter.
In addition, any variables in the main file which are needed by the imported functions need to be passed into the functions. For example, you should define add_download to accept the root window as a parameter.
def add_download(root):
...
Then, in the main program, pass root as that parameter:
ADD_BUTTON = Button(root, ..., command=lambda: add_download(root))

You will need to build a class to manage it.
Inside run.py:
import tkinter as tk
from interface import GUI
root = tk.Tk()
GUI(root)
Then inside your interface.py script you can call in additional modules:
import tkinter as tk
from aux import AuxGUI
from menu import MenuGUI
class GUI:
def __init__(self, master):
self.master = master
self.GUI_list = []
self.AuxGUI = AuxGUI(self.master, self.GUI_list) # Additional module
self.MenuGUI = MenuGUI (self.master, self.GUI_list) # Additional module
Then you can use OOP to access functions or objects to dynamically interact with each other.
self.GUI_list.append(self.AuxGUI)
self.GUI_list.append(self.MenuGUI)
Inside menu.py identify the correct index from the GUI_list:
import tkinter as tk
class MenuGUI:
def __init__(self, master, GUI_list):
self.master = master
self.AuxGUI = GUI_list[0]

Related

Python Tkinter: Changing between frames dynamically

I'm new to python and decided to fiddle about with a bit of GIU and was wondering how can I switch between frames in a different file dynamically? I've tried adding command=Navigator.change_to_page(self, createMenuFrame) to a button but for some reason, it just combines the frames into one. Is the way I'm trying to do this doable or better to stick with a single main.py file?
I've structured my project in the following way:
main.py:
from tkinter import *
import views.viewMaker as viewMaker
root = Tk()
root.geometry("1200x700")
root.resizable(0, 0)
# Main Frame
mainFrame = Frame(root, bg="gray", width=1200, height=700)
mainFrame.pack()
mainFrame.pack_propagate(0)
viewMaker.ViewMaker.define_views(mainFrame)
root.mainloop()
views/viewMaker.py:
from tkinter import *
import commands.navigator as Navigator
class ViewMaker:
def define_views(self):
mainMenuFrame = Frame(self, bg="white", width=300, height=700) #Define Main Menu
createMenuFrame = Frame(self, bg="white", width=300, height=700) #Define Creator Menu
ViewMaker.populate_main_menu(mainMenuFrame, createMenuFrame) #Populate Main Menu
ViewMaker.populate_create_menu(createMenuFrame) #Populate Create Menu
mainMenuFrame.pack()
mainMenuFrame.pack_propagate(0)
def populate_main_menu(self, createMenuFrame): #Define Main Menu Design
mainMenuTitle = Label(self, text="Title", bg="white", font=("Georgia", 16, "bold"))
mainMenuTitle.pack(pady=25)
generateButton = Button(self, text="Generate New", width=300, font=("Georgia", 12, "bold"))
generateButton.pack(pady=5, side=TOP)
createButton = Button(self, text="Create New", width=300, font=("Georgia", 12, "bold"), command=Navigator.change_to_page(self, createMenuFrame))
createButton.pack(pady=5, side=TOP)
quitButton = Button(self, text="Quit", width=300, font=("Georgia", 12, "bold"), command=self.master.master.destroy)
quitButton.pack(pady=25, side=BOTTOM)
def populate_create_menu(self): #Define Create Menu Design
createMenuTitle = Label(self, text="Create New", bg="white", font=("Georgia", 16, "bold"))
createMenuTitle.pack(pady=25)
commands/navigator.py:
from tkinter import *
def change_to_page(self, newPage):
newPage.pack()
newPage.pack_propagate()
self.pack_forget()
Note that command=Navigator.change_to_page(self, createMenuFrame) will execute Navigattor.change_to_page() immediately which shows the createMenuFrame.
Change it to command=lambda: Navigator.change_to_page(self, createMenuFrame) instead.

How to open a link with an specific button? Tkinter

Basically, when I want to open a specific link with a specific button it won't work. When you click the second button, it opens all the links inside the function.
from tkinter import *
import webbrowser
root = Tk()
root.title("links")
root.geometry("700x500")
root.config(bg="#3062C7")
def links_unit1():
global vid_1, vid_2
bg_v1 = Canvas(root, bg="#3062C7", width=700, height=500)
bg_v1.place(x=0, y=0)
vid_1 = Button(root, text="Virtual memory", command=all_vids1)
vid_1.place(x=20, y=20)
vid_2 = Button(root, text="lossy, lossless", command=all_vids1)
vid_2.place(x=30, y=50)
def all_vids1():
if vid_1:
webbrowser.open("https://youtu.be/AMj4A1EBTTY")
elif vid_2:
webbrowser.open("https://youtu.be/v1u-vY6NEmM")
vid_unit1 = Button(root, text="Unit 1", command=links_unit1)
vid_unit1.place(x=10, y=10)
root.mainloop()
You can't do it by checking the values of vid_1 and vid_2 because they will always be truthy. Instead you can create to anonymous function "on-the-fly" by using a lambda expression for the command= option of the Button as shown below:
from tkinter import *
import webbrowser
root = Tk()
root.title("links")
root.geometry("700x500")
root.config(bg="#3062C7")
def links_unit1():
bg_v1 = Canvas(root, bg="#3062C7", width=700, height=500)
bg_v1.place(x=0, y=0)
vid_1 = Button(root, text="Virtual memory",
command=lambda: webbrowser.open("https://youtu.be/AMj4A1EBTTY"))
vid_1.place(x=20, y=20)
vid_2 = Button(root, text="Lossy, Lossless",
command=lambda: webbrowser.open("https://youtu.be/v1u-vY6NEmM"))
vid_2.place(x=30, y=50)
vid_unit1 = Button(root, text="Unit 1", command=links_unit1)
vid_unit1.place(x=10, y=10)
root.mainloop()

Is using if statements possible with Tkinter Buttons

What I would like to occur is that when I press the other button, the first label is destroyed and only the corresponding label is on the GUI. Is there a way to incorporate If statements into this or should I approach it another way?
from tkinter import *
root = Tk()
root.geometry("250x50")
def func1():
label = Label(root, text = 'Hello', fg="White", bg="Orange" )
label.pack(fill=BOTH, expand=True)
def func2():
label = Label(root, text = 'Goodbye', fg="White", bg="Orange" )
label.pack(fill=BOTH, expand=True)
button1 = Button(root, text = "Button 1", command = func1, fg="White",
bg="Black", width=10, height=5)
button1.pack(side=LEFT)
button2 = Button(root, text = "Button 2", command = func2, fg="White",
bg="Black", width=10, height=5)
button2.pack(side=LEFT)
root.mainloop()
Here is the approach that #jasonsharper proposed: It is indeed easier to have a single Label, created at the start, then to use the two buttons to set its text, and other properties.
import tkinter as tk
if __name__ == '__main__':
root = tk.Tk()
root.geometry("250x50")
def set_label(txt):
label['text'] = txt
label['fg'] = "White"
label['bg'] = "Orange"
button1 = tk.Button(root, text = "Button 1", command = lambda x='hello': set_label(x), fg="White", bg="Black", width=10, height=5)
button1.pack(side=tk.LEFT)
button2 = tk.Button(root, text = "Button 2", command = lambda x='bye': set_label(x), fg="White", bg="Black", width=10, height=5)
button2.pack(side=tk.LEFT)
label = tk.Label(root, text='')
label.pack(fill=tk.BOTH, expand=True)
root.mainloop()
Note:
Please avoid import * --> to keep your namespace clean, use import tkinter as tk

Multiple Windows using Python with Tkinter

I have a school project and I need to create my GUI soon and I cant find an efficient way to do so and I need to make a ceaser Cipher button which brings up a window with an input in there and I need it all to look nice. Here is my code for now. Its not finished and needs to be finished but don't know how to?
from tkinter import *
root = Tk()
root.geometry("180x135")
class Welcome:
def __init__(self, master):
frame = Frame(master)
frame.pack()
self.button1 = Button(topFrame, text ="Decrypter Mode", bg='Black', fg="red", command = self.new_window)
self.button1.pack(side=RIGHT)
self.quitButton = Button(frame, bg='black', fg='white', text='Quit', command=master.destroy)
self.quitButton.pack(side=LEFT)
def new_window(self):
self.newWindow = tk.Toplevel(self.master)
self.app = Demo2(self.newWindow)
topFrame = Frame(root)
topFrame.pack()
bottomFrame = Frame(root)
bottomFrame.pack(side=BOTTOM)
one = Label(root, text='Welcome to Crypto-coder', bg='black', fg='white')
one.pack(fill=X)
one = Label(root, text='', bg='black', fg='white')
one.pack(fill=X,)
one = Label(root, text='', bg='black', fg='white')
one.pack(fill=X,)
one = Label(root, text='', bg='black', fg='white')
one.pack(fill=X,)
button1 = Button(topFrame, text ="Decrypter Mode", bg='Black', fg="red",)
button2 = Button(topFrame, text ="Encoder Mode", bg='black', fg="blue")
button1.pack(side=RIGHT)
button2.pack(side=LEFT)
W=Welcome(root)
root.mainloop()
I had to move some things for the code to show so you may have to fix it if you try it.

grid_remove on an Entry widget

Im new to Tkinter and am trying to build a simple GUI using grid manager which upon the push of button1, button2 appears along with an adjacent entry box. If you then press button2 the entry box and button2 dissapear. Below is a slice from the GUI code, the button dissapears but the entry box does not:
import Tkinter
from Tkinter import *
master = Tk()
CreateTestButton = Button(master, text="Create Test", command = CreateTest, fg="red", bg="white", font="Helvetica 10 bold")
CreateTestButton.grid(column=7, row=1)
def CreateTest():
TestEntry = Entry(master, text="", width = 100).grid(row=4,columnspan=6)
Label(self, text="Enter Test Name:").grid(row=3, column=0)
SaveTestButton = Button(master, text="Save to database", command=saveTest, fg="green", bg="white", font="Helvetica 10 bold")
SaveTestButton.grid(row=4, column=5)
def saveTest():
SaveTestButton.grid_remove()
TestEntry.grid_remove() #ERROR
mainloop()
How is one to remove entry boxes using grid manager in Tkinter? And other widgets for that matter I will also be needing to remove a list box, labels and widgets uppon a button click or event.
Regards,
Daniel
grid return nothing; By executing TestEntry = Entry(..).grid(...), TestEntry become None instead of Entry object.
Replace following line:
TestEntry = Entry(self, text="", width = 100).grid(row=4,columnspan=6)
with:
TestEntry = Entry(self, text="", width = 100)
TestEntry.grid(row=4,columnspan=6)
Complete code
from Tkinter import *
def CreateTest():
def saveTest():
SaveTestButton.grid_remove()
TestEntry.grid_remove() #ERROR
TestEntry = Entry(master, text="", width = 100)
TestEntry.grid(row=4,columnspan=6)
Label(master, text="Enter Test Name:").grid(row=3, column=0)
SaveTestButton = Button(master, text="Save to database", command=saveTest, fg="green", bg="white", font="Helvetica 10 bold")
SaveTestButton.grid(row=4, column=5)
master = Tk()
CreateTestButton = Button(master, text="Create Test", command = CreateTest, fg="red", bg="white", font="Helvetica 10 bold")
CreateTestButton.grid(column=7, row=1)
mainloop()

Categories