Not sure about Tkinter class structure and layout - python

I am starting by making a simple prompt window where the user will fill out the name of the directory to grab images from and where to save a resultant .csv file (and some other info).
After pressing enter in this (in this case) I want this prompt window to close and the main window where all the work will be performed by the user to appear.
In short, is this the correct way to begin structuring the classes for my GUI? Also why does the line:
frame = Frame(master, height=500,padx=20, pady=20)
Not change the formatting/layout of this initial window?
I feel that once i have a solid grasp on how to build this initial window, I will have the proper foundation to continue the rest on my own.
#imports
from Tkinter import *
import glob
from PIL import Image, ImageTk
#end imports
class prompt_window:
def __init__(self, master):
frame = Frame(master, height=500,padx=20, pady=20)
#Build prompt window labels
self.source_label = Label(root, text="Source Directory")
self.destination_label = Label(root, text="Destination Directory")
self.region_sz_label = Label(root, text="Region Size")
self.save_file_name_label = Label(root, text="Save File Name")
#Build prompt window entry forms
self.source_entry = Entry(root)
self.destination_entry = Entry(root)
self.region_sz_entry = Entry(root)
self.save_file_name_entry = Entry(root)
#Build enter button
self.enter_button = Button(root, text="Enter")
#Align labels in prompt window grid
self.source_label.grid(row=0)
self.destination_label.grid(row=1)
self.region_sz_label.grid(row=2)
self.save_file_name_label.grid(row=3)
#Align entry forms in prompt window grid
self.source_entry.grid(row=0, column=1)
self.destination_entry.grid(row=1, column=1)
self.region_sz_entry.grid(row=2, column=1)
self.save_file_name_entry.grid(row=3, column=1)
#Add button
self.enter_button.grid(row=4,column=2)
#create root
root = Tk()
#create prompt window
prompt = prompt_window(root)
#run indefinitely
root.mainloop()

You need to call one of the layout methods (.pack, .grid, or .place) on your Frame to make it visible. In my version of your program I've changed the class name from prompt_window to PromptWindow to conform to the PEP-8 style guide. I've also changed the Tkinter import statement so we aren't cluttering our namespace with all those Tkinter names.
Organizing GUI code is a little different to organizing a plain CLI program. Once we've defined everything we sit in a loop waiting for events to respond to. So for this program we need to set things up so that when the prompt window closes the main window opens and it has access to the data gathered by the prompt window.
You said that you want the prompt window to be a different window to the main window, so we need to create a window for it. A Tkinter program can only have one root window, so we create a Toplevel window to hold the prompt Frame.
You don't need to save the Labels as attributes of the PromptWindow instance since you aren't doing anything with them after you create them.
I've changed the Entry widgets slightly: I've given each of them a StringVar to hold their data. This makes it possible to access that data after the prompt window is closed. When a widget's parent window is closed you can't access it anymore. So instead of saving each Entry as an attribute of PromptWindow we instead save the associated StringVar.
When the "Enter" button is pressed or the prompt window's close widget is pressed we close the prompt window and make the main window visible.
I've created a simple MainWindow class that contains a button that lets us print the data gathered by the prompt window.
import Tkinter as tk
class PromptWindow(object):
def __init__(self, master):
self.master = master
self.win = tk.Toplevel(master)
self.win.title("Prompt Window")
# Intercept close window events so that when the user closes this window
# (via the close widget or the Enter button) the main window reopens
self.win.protocol("WM_DELETE_WINDOW", self.close)
frame = tk.Frame(self.win, height=500,padx=20, pady=20)
frame.pack()
# Build prompt window labels
tk.Label(frame, text="Source Directory").grid(row=0)
tk.Label(frame, text="Destination Directory").grid(row=1)
tk.Label(frame, text="Region Size").grid(row=2)
tk.Label(frame, text="Save File Name").grid(row=3)
# Build prompt window entry widgets
self.source = tk.StringVar()
tk.Entry(frame, textvariable=self.source).grid(row=0, column=1)
self.destination = tk.StringVar()
tk.Entry(frame, textvariable=self.destination).grid(row=1, column=1)
self.region_sz = tk.StringVar()
tk.Entry(frame, textvariable=self.region_sz).grid(row=2, column=1)
self.save_file_name = tk.StringVar()
tk.Entry(frame, textvariable=self.save_file_name).grid(row=3, column=1)
# Build enter button
tk.Button(frame, text="Enter", command=self.close).grid(row=4,column=2)
def close(self):
# Close this window
self.win.destroy()
# Reopen the main window
self.master.deiconify()
class MainWindow(object):
def __init__(self):
# Create root window
root = tk.Tk()
# and make it invisible
root.withdraw()
root.title("Main Window")
# Add some widgets
tk.Label(root, text="This is the main window").pack()
tk.Button(root, text="Show data", command=self.show_data).pack()
# Create prompt window
self.prompt = PromptWindow(root)
# Loop forever
root.mainloop()
# Display the data gathered by the prompt window
def show_data(self):
prompt = self.prompt
src = prompt.source.get()
dest = prompt.destination.get()
region = prompt.region_sz.get()
fname = prompt.save_file_name.get()
fmt = 'src: {!r}, dest: {!r}, region: {!r}, fname: {!r}'
print(fmt.format(src, dest, region, fname))
MainWindow()

Related

Tkinter toplevel window is not defined

I wonder if someone could tell me if its possible to update toplevel windows using external functions. I've replicated my issue below what I need to do is update the Toplevel(master) using the function updatelabel(). I have used similar external function to update items in root which works like a dream. However, with the top level window I always get the
NameError: name 'newWindow' is not defined
The only work around I found was to kill the newWindow using newWindow.destroy() on each load but this method makes the screen pop up and then close again which doesn't look pretty. Any help most welcome thanks.
from tkinter import *
from tkinter.ttk import *
master = Tk()
master.geometry("200x200")
def updatelabel():
Label(newWindow,
text="I changed").pack()
def openNewWindow():
# Toplevel object which will
# be treated as a new window
newWindow = Toplevel(master)
# sets the title of the
# Toplevel widget
newWindow.title("New Window")
# sets the geometry of toplevel
newWindow.geometry("200x200")
# A Label widget to show in toplevel
Label(newWindow,
text="I want to change").pack()
button1 = Button(newWindow,
text="Click me to change label", command=updatelabel).pack()
btn = Button(master,
text="open a new window",
command=openNewWindow)
btn.pack(pady=10)
mainloop()
Your “newWindow” is defined in your “openNewWindow” function and so it basically only exists in there, you could probably fix this by either defining “newWindow” outside of the function, or by using it as an argument(just add it to the brackets and give it a name in the function itself’s brackets) calling “updateLabel”
I think this should work, though I haven’t worked with tkinter in a bit so don’t blame me if it doesn’t
from tkinter import *
from tkinter.ttk import *
master = Tk()
master.geometry("200x200")
def updatelabel(newWindow):
Label(newWindow,
text="I changed").pack()
def openNewWindow():
# Toplevel object which will
# be treated as a new window
newWindow = Toplevel(master)
# sets the title of the
# Toplevel widget
newWindow.title("New Window")
# sets the geometry of toplevel
newWindow.geometry("200x200")
# A Label widget to show in toplevel
Label(newWindow,
text="I want to change").pack()
button1 = Button(newWindow,
text="Click me to change label", command= lambda: updatelabel(newWindow)).pack()
btn = Button(master,
text="open a new window",
command=openNewWindow)
btn.pack(pady=10)
mainloop()

Opening a new window using a link in tkinter

How do I open a new window using a link in tkinter .
(For eg : in a login window i want to add a link that says "New user ? click here" and when I click on "click here" it takes me to the register window .
Please help me
enter image description here
[1]: https://i.stack.imgur.com/K5GV0.png
Please click the above link to see the image
Creating new toplevel windows works almost exactly the same as creating new widgets.
Toplevel windows are created using the Toplevel function:
t = Toplevel(parent)
Unlike regular widgets, you don't have to "Grid" a toplevel fo it to appear on screen. Once you've created a toplevel you can add children widgets within and grid them like in the main window. In other words toplevel behaves exactly like the automatic created root window.
To destroy a window use the method:
window.destroy()
You can open new windows in tkinter with the tkinter.Toplevel() command.
import tkinter as tk
class Gui:
"""Gui class"""
def __init__(self):
self.root = tk.Tk()
self.new_window = tk.Button(master=self.root, text="Open new window", width=20, pady=4, command=self.new_window)
self.new_window.pack()
self.root.mainloop()
def new_window(self):
"""Create a new top level window"""
new_window = tk.Toplevel()
tk.Label(master=new_window, text="This is a new window").pack()
if __name__ == '__main__':
Gui()
You can create a function to open a new window and then bind it to that Label, for example:
import tkinter as tk
def newWindow():
# Window object (top level)
newWindow = Toplevel(master)
# Title
newWindow.title("New Window 1")
# Geometry
newWindow.geometry("300x300")
root = tk.Tk()
label = tk.Label(text="Hello!", width=50, height=10, master=root)
label.pack()
label.bind("<Button-1>", newWindow)

How to close top level tkinter window with a button, if button in the window already is bound to a function

I'm somewhat new to tkinter and Python and am working on a semester long project. Basically I have a main tkinter window, then from that window, topLevel windows are called depending on the user input. In the each topLevel window I have a button that performs a function, I also want this button to close the topLevel window after performing that function. What would be the best way to approach this problem?
I have tried to destroy or close the window, but it ends up closing the main window also. I am just looking for a way to close the topLevel window and perform the function with the click of a button
class MainWindow:
# Entry box
self.entry = StringVar()
self.text_box = Entry(master, textvariable=self.entry)
self.text_box.grid(row=1, column=2)
# Displays and binds button, so when clicked, the enter_button function is called
self.input_button = Button(master, text='Enter', command=self.enter_button)
self.input_button.grid(row=1, column=3, sticky='W')
def enter_button(self):
# Get user input and perform the given command
command = self.entry.get()
# Creates a root for a toplevel window
top = Toplevel()
if command == '1':
add_gui = AddPayment(top)
top.mainloop()
elif command == '2':
#rest of classes/commands
main
def main():
root = Tk()
app = MainWindow(root)
root.mainloop()
if __name__ == '__main__':
main()
AddPayment class
class AddPayment:
def __init__(self,master):
self.master = master
self.add_label = Label(master, text='How much is the payment for?')
# payment box
self.pay = StringVar()
self.pay_box = Entry(master, textvariable=self.pay)
self.add_button = Button(master, text='Add', command=self.add_payment)
# position widgets
self.pay_box.grid(row=1, column=2)
self.add_label.grid(row=1, column=1)
self.add_button.grid(row=1, column=3)
def add_payment(self):
database.add_pay(self.pay.get())
In this example I would like something in the add_payment function to close the topLevel window after the add_pay function is performed somehow. Thanks in advance
You have a couple problems. For one, you should never call mainloop more than once. You need to remove the call to mainloop() from the enter_button function.
The other problem is that you're not saving a reference to the toplevel, so you've made it more-or-less impossible to destroy it. You simply need to save a reference and then call destroy on the reference.
self.top = Toplevel()
...
self.top.destroy()

Python: How to create a dialog Window prior to main app Windows

Preface:
I have a Python ControlGPIO code with a working GUI (let us call it MainGUI).
I wish to have a dialog pop up window, prior to running MainGUI, in a way that the user can enable/ disable features in MainGUI. BUT MainGUI should start running only after dialog pop up window is closed.
My question is: How can I make a popup window that will postpone MainGUI untill it is closed?
Code below- boot_windows is my dialog pop up window (where all the enable/disable checkboxes will be ), BUT obviously does not postpone App as I need
root = Tk()
#output_gpioPins = [4,22,6,26]
#input_gpioPins = [3,21,5,27]
#ip = '192.168.2.112'
boot_windows = Toplevel(root)
text1 = ttk.Label(boot_windows, text="Hello World !!!")
text1.grid()
App = ContorlGPIOWindow(root, ip = '192.168.2.113', with_sf_bt=1, with_hw_bt=1, switch_names=['Light Kitchen','Light Room1', 'Window1', 'Window2'])
root.mainloop()
You can't do precisely what you want. Widgets exist in a tree-like structure. All windows except the root require a root window. The root window must be created first (which is why it's called the root window).
If you don't want the user to see it, you can hide it until it is ready to be displayed.
import tkinter as tk
root = tk.Tk()
root.withdraw()
boot_window = tk.Toplevel(...)
...
You can then call root.deiconify() when you are ready for it to be visible.
Another common solution is to use the root window for your dialog or splash screen or whatever, and then simply replace its contents with the real contents when you're ready.
As for how to wait for the popup... the root window has a method named wait_window which will enter the event loop and not return until the given window has been destroyed.
Here's an example of it's use:
import Tkinter as tk
class MainGUI(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="Hello, world!")
label.pack(fill="both", expand=True, padx=20, pady=20)
class Popup(tk.Toplevel):
def __init__(self, root):
tk.Toplevel.__init__(self, root)
label = tk.Label(self, text="Click to continue...")
label.pack(fill="both", expand=True, padx=20, pady=20)
button = tk.Button(self, text="OK", command=self.destroy)
button.pack(side="bottom")
if __name__ == "__main__":
root = tk.Tk()
root.withdraw()
popup = Popup(root)
root.wait_window(popup)
main = MainGUI(root)
main.pack(fill="both", expand=True)
root.deiconify()
root.mainloop()

Why tk.Button.configure after tkFileDialog.askopenfilename takes effect only after the method is left?

In the following code excerpt, which draws a simple GUI with one tick box and a button, I'd like to change the button description and its relief after the button is pressed and the user has selected a file (which is followed by a time consuming calculation).
The button called select_master_button is bound to the command SelectMaster.
In the SelectMaster method the configure button instruction is placed directly after the tkFileDialog.askopenfilename invocation.
The idea is to have the button's description and the relief changed so that the user is aware of the processing activity.
However, when the user selects a file and leaves the file dialog there is no change in the button configuration and only after the whole method is finished the button configuration takes effect.
In this example the last instruction reverts the button's relief and tells the user that the file selection is active. This of course works fine, since it is the last instruction before the method is left.
Why tk.Button.configure after tkFileDialog.askopenfilename takes effect only after the method is left?
I'm on Python 2.7.
I understand that placing the button configure instruction before the file dialog would provide the remedy but I'd like to understand the current behaviour still.
# -*- coding: utf-8 -*-
import Tkinter as tk
import tkFileDialog
import tkMessageBox
from openpyxl import Workbook, load_workbook
class MyApp():
def __init__(self, parent):
self.sort_tick_value = tk.IntVar()
self.sort_tick_value.set(1)
### GUI ###
self.myParent = parent
self.myParent.geometry("400x400")
self.myContainer = tk.Frame(parent)
self.myContainer.pack(expand=tk.YES, fill=tk.BOTH, padx=10, pady=5,
ipadx=5, ipady=5)
self.sort_selection_frame = tk.Frame(self.myContainer)
self.sort_selection_frame.pack(side=tk.TOP, expand=tk.NO, fill=tk.BOTH,
pady=10)
self.select_master_frame = tk.Frame(self.myContainer)
self.select_master_frame.pack(side=tk.TOP, expand=tk.NO, fill=tk.BOTH,
pady=10)
#### Tick Box ####
self.sort_tick_box =tk.Checkbutton(self.sort_selection_frame,
text="Sort in groups",
variable=self.sort_tick_value)
self.sort_tick_box.pack(side=tk.TOP, anchor=tk.W)
#### Choose Master File Button ####
self.select_master_button = tk.Button(self.select_master_frame)
self.select_master_button.configure(text="Select master file - "
".xlsx\n(optional)",
command=self.SelectMaster)
self.select_master_button.pack(side = tk.TOP, anchor=tk.N)
def SelectMaster(self):
selected_master = tkFileDialog.askopenfilename(
parent=root, title='Choose the master file', initialdir = 'D:/')
self.select_master_button.configure(text="Select master file - "
".xlsx\n(scanning file, "
"please wait)", relief=tk.SUNKEN)
print "Scanning the selected master file ..."
wb = load_workbook(selected_master, read_only=True)
ws = wb.active
while …:
# time consuming calculation ...
self.select_master_button.configure(text="Select master file\n(active)",
relief=tk.RAISED)
root = tk.Tk()
root.wm_title("Title“)
myApp = MyApp(root)
root.mainloop()

Categories

Resources