I am currently writing a simple logbook application for my sailing holidays. Therefore I wanted to create a small gui for entering data in lines for the logbook.
I am using grid layout with frames and canvas for the main frame, to show a scroll bar, when there are to many lines.
Everything is looking fine, until the command
self.canvasCenter.configure(scrollregion=self.canvasCenter.bbox("all"))
is called. If I comment this line out, the canvas is using the full frame. If this line is active - the canvas is using only 50% of the available screen and date is cut off.
I tried to strip down the code to the necessary things (not using any additional files, configs, classes, etc.). So that it is executable stand alone:
#!/usr/bin/python
# -*- coding: iso-8859-1 -*-
import sys
if sys.version_info.major == 2:
# We are using Python 2.x
import Tkinter as tk
elif sys.version_info.major == 3:
# We are using Python 3.x
import tkinter as tk
#from lib import logBookData
#from config import *
#Titel of the application shown in title bar
appTitle = "My APP"
#Layout geometry (size of the window)
lWidth = 768
lHeight = 900
layoutGeometry = '900x768'
#Colours for foreground and background for labels and entry fields
#headerBG = "Light Blue"
headerBG = "white"
labelBG = "white"
labelFG = "black"
#Number of columns (keep in Sync with headers and width list below!)
cNumber = 14
#Column width for number of columns defined above
cWidth = [5,7,5,5,5,5,5,5,5,5,5,5,5,10]
cHeaders = ["C1", "C2", "C3", "C4", "C5", "C6", "C7", "C8", "C9", "C10", "C11", "C12", "C13", "C14" ]
class logBookGUI(tk.Tk):
def __init__(self,parent):
tk.Tk.__init__(self,parent)
self.parent = parent
self.lineCount = 0
self.initialize()
def initialize(self):
#create a masterframe
self.masterFrame = tk.Frame(self, bg=headerBG, bd=4, relief="groove") #, width = lWidth, height = lHeight)
self.masterFrame.grid(sticky=tk.NSEW)
self.masterFrame.columnconfigure(cNumber, weight=1)
#create Headerframe for Standard Info
self.headerFrame = tk.Frame(self.masterFrame, bg=headerBG, bd=2, relief="groove")
self.headerFrame.grid(row=0, column=0, sticky=tk.NSEW)
self.headerFrame.columnconfigure(cNumber, weight=1)
#Label Bootsname
boatNameLabel = tk.Label(self.headerFrame, text="Bootsname", anchor="w", fg=labelFG,bg=labelBG)
boatNameLabel.grid(column=0, row=0, sticky=tk.NSEW)
#Field Bootsname
self.boatName = tk.StringVar()
self.boatNameEntry = tk.Entry(self.headerFrame,textvariable=self.boatName, width = cWidth[0])
self.boatNameEntry.grid(column=1,row=0, sticky=tk.NSEW)
self.boatName.set(u"Solva")
for i in xrange(cNumber-2):
button = tk.Button(self.headerFrame,text=u"T %s" %i, command=self.OnButtonClick)
button.grid(column=i,row=0, sticky=tk.NSEW)
button = tk.Button(self.headerFrame,text=u"TEST", command=self.OnButtonClick)
button.grid(column=cNumber-2,row=0, sticky=tk.NSEW)
button = tk.Button(self.headerFrame,text=u"Neue Zeile", command=self.newLineClick)
button.grid(column=cNumber-1,row=1, sticky=tk.NSEW)
#create center frame
self.centerFrame = tk.Frame(self.masterFrame, bg=headerBG, bd=2, relief="groove")
self.centerFrame.grid(row=1, column=0, sticky=tk.NSEW)
self.centerFrame.columnconfigure(cNumber, weight=1)
#create a canvas for center frame
self.canvasCenter = tk.Canvas(self.centerFrame)
self.canvasCenter.grid(row=0, column=0, sticky=tk.NSEW)
self.canvasCenter.columnconfigure(cNumber, weight=1)
# Create a vertical scrollbar linked to the canvas.
vsbar = tk.Scrollbar(self.centerFrame, orient=tk.VERTICAL, command=self.canvasCenter.yview)
vsbar.grid(row=0, column=cNumber, sticky=tk.NSEW)
self.canvasCenter.configure(yscrollcommand=vsbar.set)
# Create a frame on the canvas to contain the content.
self.contentFrame = tk.Frame(self.canvasCenter, bg="Red", bd=2, relief="groove")
self.contentFrame.grid(row=0, column=0, sticky=tk.NSEW)
self.contentFrame.columnconfigure(cNumber, weight=1)
column = 0
for header in cHeaders:
label = tk.Label(self.contentFrame, text=header, anchor="w", fg=labelFG,bg=labelBG,borderwidth=2,relief="groove", width = cWidth[column])
label.grid(column = column, row=1, sticky="EW")
column += 1
self.addLine()
# Create canvas window to hold the centerframe.
self.canvasCenter.create_window((0,0), window=self.contentFrame, anchor=tk.NW)
# Update buttons frames idle tasks to let tkinter calculate buttons sizes
self.contentFrame.update_idletasks()
self.canvasCenter.configure(scrollregion=self.canvasCenter.bbox("all"))
self.grid_columnconfigure(cNumber,weight=1)
self.resizable(True,False)
self.update()
self.geometry("%sx%s" % (lHeight, lWidth))
def newLineClick(self):
self.addLine()
def OnButtonClick(self):
pass
def OnPressEnter(self,event):
pass
def addLine(self):
tkLine = []
try:
for i in xrange(cNumber):
self.entryVar = tk.StringVar()
self.entry = tk.Entry(self.contentFrame,textvariable=self.entryVar,width=cWidth[i])
self.entry.grid(column=i, row = self.lineCount + 3, sticky='EW')
tkLine.append(self.entryVar)
self.canvasCenter.configure(scrollregion=self.canvasCenter.bbox("all"))
except IndexError:
print ("Error in config file. Number of columns and given columns do not match - check file config.py")
sys.exit()
self.update()
self.lineCount += 1
if __name__ == "__main__":
app = logBookGUI(None)
app.title(appTitle)
app.mainloop()
Could someone give me an advice how to use scrollregion, so that the whole size of the frame is used?
Many thanks,
Gernot
Related
I know this has been asked before but none of the solutions that I've seen so far seem to work.
So here's the exact case: I'm building an app that has 3 different tabs (using ttk.Notebook).
The initial contents of the 3 tabs is different: the second is the smallest and the third is he biggest.
Now here comes the issue (I'm using grid, btw): when I change from the first to the second, there is an extra empty space (because the content of the first Tab is a little bigger than the one of the second, and the same happens when I go from the third to the second/first, so after using the third tab and changing to another one, the size stays as if I still was in the third.
I add some of the code that I'm using, but since I'm not sure what is going wrong, I'll let you ask for whatever extra code you might need to help me find the solution.
# Tk modules
import tkinter as tk
from tkinter import ttk
# # define main()
def main():
app = App()
MainWindow(app)
app.mainloop()
# Frame for data input
class RegisterChooseType(ttk.Frame):
def __init__(self, container):
# Inheritance
super().__init__(container)
self.container = container
# Var for selection
self.selected_value = tk.IntVar()
# Options
self.options = {"padx": 10, "pady": 10}
# Doc types
self.doc_types = ("Incoming Bill", "Outgoing Bill", "Contract", "Other")
# Labels
self.barcode_label = ttk.Label(self, text="Barcode:").grid(
row=0, column=0, sticky="EW", **self.options
)
self.doc_type_label = ttk.Label(self, text="Document type:").grid(
row=0, column=2, sticky=tk.W, **self.options
)
# Entries
self.barcode_entry = ttk.Entry(self)
self.barcode_entry.grid(row=0, column=1, sticky="EW", **self.options)
self.grid()
def submit(self):
self.current_dict["func"](self, self.current_frame)
class DeleteTab(ttk.Frame):
def __init__(self, container):
# Inheritance
super().__init__(container)
# self.columnconfigure(0, weight=1)
# self.columnconfigure(1, weight=1)
self.options = {"padx": 10, "pady": 10}
## Type Barcode
# Label
self.barcode_label = ttk.Label(self, text="Barcode:").grid(
row=0, column=0, sticky="EW", **self.options
)
# Entries
self.barcode_entry = ttk.Entry(self)
self.barcode_entry.grid(row=0, column=1, sticky="EW", **self.options)
# self.pack(fill="x")
self.grid(sticky="W")
class ViewTab(ttk.Frame):
def __init__(self, container):
# Inheritance
super().__init__(container)
self.container = container
self.options = {"padx": 10, "pady": 10}
# Doc types
self.doc_types = ("Incoming Bill", "Outgoing Bill", "Contract", "Other")
## Type Barcode
# Labels
# Barcode
self.barcode_label = ttk.Label(self, text="Barcode:")
self.barcode_label.grid(row=0, column=0, sticky="EW", **self.options)
# Document type
self.doc_type_label = ttk.Label(self, text="Document Type:")
self.doc_type_label.grid(row=0, column=2, sticky="EW", **self.options)
# Entries
# Barcode
self.barcode_entry = ttk.Entry(self)
self.barcode_entry.grid(row=0, column=1, sticky="EW", **self.options)
# Document type
self.doc_type_comb = ttk.Combobox(self)
self.doc_type_comb["state"] = "readonly" # Doesn't allow new entries
self.doc_type_comb["values"] = self.doc_types
self.doc_type_comb.grid(
row=0, column=3, columnspan=3, sticky=tk.EW, **self.options
)
## View Button
self.viewSubmitButton = ttk.Button(
self, text="View", command=lambda: print("View")
)
self.viewSubmitButton.grid(
column=4,
row=self.grid_size()[0],
sticky="EW",
**self.options,
)
# New LabelFrame
self.deliver_view_LabelFrame = ttk.LabelFrame(self)
self.deliver_view_LabelFrame["text"] = "View Document"
self.deliver_view_LabelFrame.grid(
row=1,
columnspan=self.grid_size()[0],
column=0,
sticky="ew",
padx=10,
pady=10,
)
# Inside LabelFrame
##TreeView
self.dataDisplay = ttk.Treeview(self.deliver_view_LabelFrame, show="")
self.dataDisplay.pack(fill="x", **self.options)
self.grid(sticky="ews")
# Create window
class MainWindow:
def __init__(self, container):
self.container = container
## Create Parent
self.tab_parent = ttk.Notebook()
self.tab_parent.enable_traversal()
# Create tab frames
self.tab_register = ttk.Frame(self.tab_parent)
self.tab_view = ttk.Frame(self.tab_parent)
self.tab_delete = ttk.Frame(self.tab_parent)
# Adding the tabs to the main object (self.tab_parent)
self.tab_parent.add(self.tab_register, text="Register Documents")
self.tab_parent.add(self.tab_delete, text="Delete Documents")
self.tab_parent.add(self.tab_view, text="View Documents")
# Create empt variables
self.current_tab = None
self.last_tab = None
# Focus on barcode
# self.register.barcode_entry.focus()
self.tab_parent.bind("<<NotebookTabChanged>>", self.on_tab_change)
# Pack notebook
# self.tab_parent.pack(expand=True, fill="x", side="left")
self.tab_parent.grid(sticky="e")
## Triggers when changing tabs
def on_tab_change(self, event):
if self.current_tab != None:
self.current_tab.destroy()
# Get the current tab name
selected = event.widget.tab("current")["text"]
# Create frame depending on Tab chosen
if selected == "Register Documents":
self.current_tab = RegisterChooseType(self.tab_register)
self.clean_tabs()
self.last_tab = self.current_tab
elif selected == "Delete Documents":
self.current_tab = DeleteTab(self.tab_delete)
self.clean_tabs()
self.last_tab = self.current_tab
elif selected == "View Documents":
self.current_tab = ViewTab(self.tab_view)
self.clean_tabs()
self.last_tab = self.current_tab
self.current_tab.barcode_entry.focus()
def clean_tabs(self):
if self.last_tab != None:
# for widget in self.last_tab.winfo_children():
# # for widget in self.last_tab.grid_slaves():
# widget.destroy()
self.last_tab.destroy()
# ## INTERESTING
# self.last_tab.blank = ttk.Label(self.last_tab, text="1")
# self.last_tab.blank.grid()
# print(self.last_tab.grid_slaves()[0].cget("text"))
# self.container.geometry("1x1")
self.tab_parent.update()
# self.container.update()
# self.container.geometry("")
# self.container.update()
# Creatig App
class App(tk.Tk):
def __init__(self):
super().__init__()
self.geometry("")
self.title("iMPETU Document Handling v.0.1.0a")
self.resizable(0, 0)
if __name__ == "__main__":
main()
I tried deleting one by one the widgets of the current tab before changing to another one, also doing the same AFTER changing to the new tab (that's why I have 2 variables for current and last tab), I also tried leaving an empty tag (supposedly 0 pixels) but that would push all the contents down.
I tried withdraw, deiconify just out of curiosity, I also tried using self.container.update() to update the main window, also tried changing the geometry to "1x1" in the clean_tabs function and then changing again to "" at the end of the on_tab_change and see if that would maybe force it to resize but none of that worked.
You can resize all the frames (parents of instances of RegisterChooseType, DeleteTab and ViewTab) to size 1x1 at the beginning of clean_tabs():
def clean_tabs(self):
for tab in self.tab_parent.tabs():
# tab is the name of the frame
# so get the widget using .nametowidget()
# then call .config(width=1, height=1) to resize the frame to size 1x1
self.tab_parent.nametowidget(tab).config(width=1, height=1)
...
All of the tutorials I have seen deomonstrate the tkinter filedialog.askopenfilename function by only using the information collected within the function that is linked to the tkinter button. I can pass information in the function, but I would like to pass variables (filepath, images, etc.) outside the function and have them update variables in my GUI.
I have commented out the location I would like to call the variables in main_gui_setup function below. Any help will be appreciated, as it has been very demoralizing not being able to open a file. If this problem persists, my future as a programmer may be limited to creating tic-tac-toe games or instructional videos for Youtube.
from tkinter import *
import tkinter as tk
from tkinter import ttk
from tkinter import filedialog, messagebox
from PIL import ImageTk, Image # was import PIL.Image, PIL.ImageTk
import cv2
def main():
root = Tk()
window1 = Window(root, "X-ray Assist", "Give up")
return None
# can't pass by reference in python
class Window():
n = 0
file_path = ""
img1_info = ""
def __init__(self, root, title, message):
self.root = root
self.root.title(title)
#self.root.geometry(geometry)
self.screen_width = root.winfo_screenwidth()
self.screen_height = root.winfo_screenheight()
#self.root.attributes('-topmost', 1)
# SET APP WINDOW SIZE
scr_size_main = self.scr_size() # create instance of scr_size
self.root.geometry("%dx%d+%d+%d" % (self.root_width, self.root_height, self.root_x, self.root_y))
# CREATE MAIN WINDOW GUI
create_gui = self.main_gui_setup()
self.root.mainloop()
pass
def scr_size(self):
'''Reads monitor size and adjusts GUI frame sizes'''
self.root_width = int(self.screen_width*0.52)
self.root_height = int(self.screen_height*0.9)
self.root_x = int(self.screen_width*0.23)
self.root_y = int(self.screen_height*0.02)
self.img_ht_full = int(self.screen_height*0.82)
self.tools_nb_width = int(self.screen_width*0.22)
self.tools_nb_height = int(self.screen_height*0.48)
self.hist_nb_width = int(self.screen_width*0.22)
self.hist_nb_height = int(self.screen_height*0.23)
def open_image(self):
main_win = ttk.Frame(self.root)
main_win.grid(column=0, row=0)
self.file_path = filedialog.askopenfilename(initialdir='/', title='Open File',
filetypes=(('tif files', '*.tif'), ('all files', '*.*')))
self.file_path_label = ttk.Label(main_win, text=self.file_path)
self.file_path_label.grid(column=0, row=0, columnspan=1, sticky="nw", padx=(5,0), pady=1)
self.img1_8bit = cv2.imread(self.file_path, 0) #, cv2.IMREAD_ANYDEPTH | cv2.IMREAD_GRAYSCALE)
#self.img1_8bit_resize = cv2.resize(self.img1_8bit, (self.img_ht_full, self.img_ht_full)) #, interpolation = cv2.INTER_CUBIC)
#self.img1_height, self.img1_width = self.img1_8bit.shape # not resized for screen
#img1_info = text = f"{self.img1_height} {self.img1_8bit.dtype} {self.img1_16bit.dtype}"
#print(self.img1_width, " x ", self.img1_height, " bitdepth = ", self.img1_8bit.dtype)
#img1_info = ttk.Label
#print(f"{self.img1_height} {self.img1_width} {self.img1_8bit.dtype}")
#img1_info.grid(column=3, row=1, columnspan=1, sticky="w", padx=(5,0), pady=1)
#img = io.imread(main_win.filename) #scikit
self.img1_16bit = cv2.imread(self.file_path, cv2.IMREAD_ANYDEPTH | cv2.IMREAD_GRAYSCALE)
#self.img_canvas = tk.Canvas(self.root, width=self.img_ht_full, height=self.img_ht_full)
#self.img_canvas.grid(column=1, row=2, columnspan=10, rowspan=10, sticky="nw")
#self.img_canvas.image = ImageTk.PhotoImage(image=Image.fromarray(self.img1_8bit_resize))
#self.img_canvas.create_image(0,0, image=self.img_canvas.image, anchor="nw")
# .create_line(x1, y1, x2, y2, fill="color")
#self.img_canvas.create_line((self.img_ht_full/2), 0, (self.img_ht_full/2), (self.img_ht_full), fill="yellow")
#self.img_canvas.create_line(0, (self.img_ht_full/2), (self.img_ht_full), (self.img_ht_full/2), fill="yellow")
def main_gui_setup(self):
main_win = ttk.Frame(self.root)
main_win.grid(column=0, row=0)
image_win = ttk.Frame(main_win, borderwidth=25, relief="groove", width=self.img_ht_full, height=self.img_ht_full)
image_win.grid(column=1, row=2, columnspan=10, rowspan=10, sticky="nw")
toolbar = ttk.Frame(main_win, borderwidth=5) #, width=1100, height=15)
toolbar.grid(column=0, row=0, columnspan=10, rowspan=1, sticky="nw", padx=20)
hist_win = ttk.Frame(main_win, borderwidth=25, relief="groove", width=300, height=200)
panel_info = ttk.Label(main_win, text=f"{self.screen_width} x {self.screen_height}")
panel_info.grid(column=5, row=1, columnspan=1, sticky="e", pady=1)
# SCROLL SLIDER AT BOTTOM
slider = ttk.Scrollbar(main_win, orient="horizontal")
slider.grid(column=1, row=13, columnspan=7, padx=5, pady=5, sticky="ew")
#X-RAY AND DETECTOR SETTINGS - will input these from separate class
kv = ttk.Label(main_win, text="125kV")
kv.grid(column=0, row=2, columnspan=1, padx=15, pady=5)
file_path_label = ttk.Label(main_win, text="No image loaded")
file_path_label.grid(column=1, row=1, columnspan=1, sticky="nw", padx=(5,0), pady=1)
# CREATE BUTTONS
open = ttk.Button(toolbar, text="Open", width=10, command=self.open_image)
open.grid(column=0, row=0)
save = ttk.Button(toolbar, text="Save", width=10)
save.grid(column=1, row=0)
b1 = ttk.Button(toolbar, text="1", width=10)
b1.grid(column=2, row=0)
b2 = ttk.Button(toolbar, text="2", width=10)
b2.grid(column=3, row=0)
pass
main()
You aren't thinking of event driven programming correctly. In event driven programming you have callbacks to the functions you defined. Let's look at your code:
def get_path(self):
...
self.path_label = ...
...
def main_gui_setup(self):
main_win = ttk.Frame(self.root)
main_win.pack()
open = ttk.Button(main_win, text="Open", width=10, command=self.get_path)
open.pack()
# Problematic code:
# main_label = ttk.Label(main_win, self.path_label)
# main_label.pack()
When main_gui_setup is called it creates a frame and a button inside it. When the button is clicked it calls get_path which sets up the path_label variable. The problem that you were facing is that that as soon as you create your button (without waiting for the button to be pressed), you create the label called main_label.
For a simple fix to your problem try this:
def get_path(self):
...
self.file_path = ...
self.path_label = ...
...
def button_callback(self, main_win):
# set up
self.get_path()
# My guess is that you wanted `self.file_path` here instead of `self.path_label`
main_label = ttk.Label(main_win, self.file_path)
main_label.pack()
def main_gui_setup(self):
main_win = ttk.Frame(self.root)
main_win.pack()
# I am using a lambda and passing in `main_win` because you haven't
# assigned it to `self` using `self.main_win = ...`
open = ttk.Button(main_win, text="Open", width=10, command=lambda: self.button_callback(main_win))
open.pack()
I am still confused by what You are trying to accomplish:
from tkinter import Tk, Button, Label, filedialog
class MainWindow(Tk):
def __init__(self):
Tk.__init__(self)
self.open_file_btn = Button(self, text='Open', command=self.open_file)
self.open_file_btn.pack()
self.file_name = None
def open_file(self):
self.file_name = filedialog.askopenfilename()
Label(self, text=self.file_name).pack()
root = MainWindow()
root.mainloop()
I will explain what will happen here! (You can change the .askopenfilename() attributes obviously).
When the program opens the filedialog and You select a file, that file name will now get assigned to self.file_name and that variable can be used anywhere in the class.
also from what I have seen You should learn more about classes and PEP 8
I'm a car park admittance thingy for college. Basically what is does when it's run, a window comes up, asks the user to enter make, model, colour, and reg plate. Then it saves this data to a list, array or whatever. The user presses a button to enter their car into the car park, and also to see what cars are currently in the car park. When I press admit vehicle, I need the data to be saved to this list/array/tree, and also for a integer variable to decrease by one. Here's the relevant code. There is more, but this is the relevant bits.
# Admit Button
btn_admit = ttk.Button(bottom_frame)
btn_admit.config(text='Admit Vehicle')
btn_admit.bind('<Button-1>', self.admit) # I need this to reduce the variable as well
...
def admit(self, event):
self.useful_msg.set("Vehicle Admitted") # This is only here to show a message currently it does nothing else
This is the 'spaces available' variable:
self.num_spaces = IntVar(mid_frame)
self.num_spaces.set = 0
lbl_num_spaces = Label(mid_frame)
lbl_num_spaces.config(textvariable=self.num_spaces, bg='yellow')
Finally, this is the code for the window that shows the tree of cars that are in the car park (with some example cars hard-coded for now):
class ShowCarsGui:
def __init__(self, master):
self.master = master
self.master.geometry('1200x600+100+100')
# Frames
top_frame = tk.Frame(self.master)
tree_container = tk.Frame(self.master)
bottom_bar = tk.Frame(self.master)
# Widgets:
# Logo
carpark_icon = tk.PhotoImage(file='car.gif')
lbl_carpark_icon = tk.Label(top_frame)
lbl_carpark_icon.config(image=carpark_icon)
lbl_carpark_icon.image = carpark_icon
# Header
lbl_header = tk.Label(top_frame)
lbl_header.config(text="Vehicles in car park", font='helvetica 32 bold')
# Tree(ttk)
self.tree = ttk.Treeview(tree_container)
self.tree["columns"] = ("Make", "Model", "Colour", "Registration")
self.tree["height"] = 10
self.tree["show"] = 'headings' # Gets rid of default first column
vsb = ttk.Scrollbar(tree_container)
vsb.configure(orient='vertical', command=self.tree.yview)
hsb = ttk.Scrollbar(tree_container)
hsb.configure(orient='horizontal', command=self.tree.xview)
self.tree.configure(yscroll=vsb.set, xscroll=hsb.set)
self.tree_populate()
# Button
quit_button = tk.Button(bottom_bar)
quit_button.config(text='Quit', width=25)
quit_button.bind('<Button-1>', self.close_window)
# Positioning frames
top_frame.grid_rowconfigure(0, minsize=150) # Make row 150 pixels high
top_frame.grid(row=0)
tree_container.grid(row=1)
bottom_bar.grid(row=2)
# Top
lbl_carpark_icon.grid(row=0, column=0, padx=10, sticky='w')
lbl_header.grid(row=0, column=1, padx=20)
# Middle
self.tree.grid(column=0, row=0, sticky='nsew')
vsb.grid(column=1, row=0, sticky='ns')
hsb.grid(column=0, row=1, sticky='ew')
# Bottom
quit_button.grid(row=0, column=0)
def close_window(self, event):
self.master.destroy()
def tree_populate(self):
# Eventually this needs to come from car park object
tree_columns = ("Make", "Model", "Colour", "Registration")
tree_data = [
("Ford", "Ka", "Blue", "FD54 2WE"),
("Vauxhall", "Corsa", "Green", "KJ61 9YH"),
("VW", "Polo", "Silver", "AA54 9TQ"),
("Nissan", "Qashqai", "Red", "YRE 456W"),
("Toyota", "Starlet", "Gold", "J234 WYE"),
]
for col in tree_columns:
self.tree.heading(col, text=col, anchor='w')
for country_data in tree_data:
self.tree.insert("", 0, values=country_data)
Finally here is the code for the entire program:
import tkinter as tk
from tkinter import *
from tkinter import ttk
class CarParkGui:
def __init__(self, master):
self.master = master
self.master.configure(bg='light cyan')
self.master.title("Collyer's Car Park")
self.master.option_add('*Font', 'Georgia 12') # Font for all widgets
self.master.option_add('*Font', 'helvetica 20 bold')
self.master.option_add('*Background', 'light cyan') # background of all widgets
self.master.geometry('1200x500+100+100') # w,h,x,y (top left corner)
self.top() # Build top bar
self.middle() # Define middle frame
self.bottom() # Define Bottom Frame
def top(self):
# Frame for top section
top_frame = Frame(self.master)
# Logo
carpark_icon = PhotoImage(file='car.gif')
lbl_carpark_icon = Label(top_frame) # Instance of tkinter label (parent is frame)
lbl_carpark_icon.config(image=carpark_icon)
lbl_carpark_icon.image = carpark_icon # Have to have this as well as previous one
# Header
lbl_header = Label(top_frame)
lbl_header.config(text='Admit Vehicle', font='helvetica 32 bold')
# Grid positioning for top frame
top_frame.grid_rowconfigure(0, minsize=150) # Make row 150 pixels high
top_frame.grid(row=0)
# Within Frame
lbl_carpark_icon.grid(row=0, column=0, padx=10)
lbl_header.grid(row=0, column=1, padx=20)
def middle(self):
# Frame to contain other widgets
mid_frame = Frame(self.master)
# Label - Car Make
lbl_make = Label(mid_frame)
lbl_make.config(text='Make') # Presentation
# Label - Car Model
lbl_model = Label(mid_frame)
lbl_model.config(text='Model')
# Label - Colour
lbl_colour = Label(mid_frame)
lbl_colour.config(text='Colour')
# Label - Registration
lbl_reg = Label(mid_frame)
lbl_reg.config(text='Registration')
# Label - Spaces
lbl_spc = Label(mid_frame)
lbl_spc.config(text='Spaces')
# Text Entry - Make
self.make = StringVar(mid_frame)
txt_make = Entry(mid_frame)
txt_make.config(textvariable=self.make, width=20)
# Text Entry - Model
self.model = StringVar(mid_frame)
txt_model = Entry(mid_frame)
txt_model.config(textvariable=self.model, width=20)
# Text Entry - Colour
self.colour = StringVar(mid_frame)
txt_colour = Entry(mid_frame)
txt_colour.config(textvariable=self.colour, width=20)
# Text Entry - Registration
self.reg = StringVar(mid_frame)
txt_reg = Entry(mid_frame)
txt_reg.config(textvariable=self.reg, width=20)
# Label for number of space available (WILL BE UPDATED)
self.num_spaces = IntVar(mid_frame)
self.num_spaces.set = 0
lbl_num_spaces = Label(mid_frame)
lbl_num_spaces.config(textvariable=self.num_spaces, bg='yellow')
# Display
mid_frame.grid_columnconfigure(0, minsize=100) # Make row 150 pixels high
mid_frame.grid(row=1, sticky='w')
# Row 0
lbl_make.grid(row=1, column=0)
txt_make.grid(row=1, column=1)
lbl_spc.grid(row=1, column=2)
lbl_num_spaces.grid(row=1, column=3, sticky='w')
# Row 1
lbl_model.grid(row=2, column=0)
txt_model.grid(row=2, column=1, padx='10')
# Row 2
lbl_colour.grid(row=3, column=0)
txt_colour.grid(row=3, column=1, padx='10')
# Row 3
lbl_reg.grid(row=4, column=0)
txt_reg.grid(row=4, column=1, padx='10')
def bottom(self):
# Frame for bottom section
bottom_frame = Frame(self.master)
# Grid reference for bottom frame
bottom_frame.grid(row=2)
# Guidance message (WILL BE UPDATED)
self.useful_msg = StringVar(bottom_frame)
self.useful_msg.set("Enter your vehicle details")
self.lbl_msg = Label(bottom_frame) # Use self so we can change config at
self.lbl_msg.config(textvariable=self.useful_msg, fg='red', width=20)
# Admit Button
btn_admit = ttk.Button(bottom_frame)
btn_admit.config(text='Admit Vehicle')
btn_admit.bind('<Button-1>', self.admit)
# Show Vehicles Button
btn_show = ttk.Button(bottom_frame)
btn_show.config(text='Show Vehicles In Car Park')
btn_show.bind('<Button-1>', self.show)
# Within bottom_frame
# row 0
self.lbl_msg.grid(row=0, column=0)
# row 1
btn_admit.grid(row=1, column=0, sticky='e')
btn_show.grid(row=1, column=1, sticky='e')
def admit(self, event):
self.useful_msg.set("Vehicle Admitted")
def show(self, event):
self.new_window = Toplevel(self.master)
self.app = ShowCarsGui(self.new_window)
___ This is where ShowCarsGui() is ____
def main():
root = Tk()
CarParkGui(root)
root.mainloop()
main()
To do "two things with one button", the correct solution is to create a function that does these "two things", and then associate that function with the button.
btn_admit = ttk.Button(bottom_frame)
btn.configure(command=self.admit)
def admit(self):
self.num_spaces.set(self.num_spaces.get()-1)
self.useful_msg.set("Vehicle Admitted")
Note: the above example uses the command attribute rather than a binding. Using the command attribute is preferable because it has built-in support for keyboard traversal. And, of course, you can do as much or as little as you want in admit -- you aren't limited to one or two things.
I see the text if I enlarge the window manually, so the text is there! (On the label) but if not enlarged the window I can not see, I see only a piece of the label
How can I fix it? Do I have to resize?
I've tried changing the font size, the size of the label and some other things and I could not even solve the problem.
This is the code:
from Tkinter import *
import ttk
import time
import threading
def start():
start_stop.config(text="QUIT", command=stop, image=photo2)
thread = threading.Thread(target=progBar, args=())
thread.daemon = True
thread.start()
def progBar():
for i in xrange(300):
if i < 50:
lbl1.config(height=0, width=4, font=('times', 400, 'bold'),
text="AAAA!!")
lbl1.config(fg='black')
if i % 5:
lbl1.config(bg='white')
else:
lbl1.config(bg='red')
if i == 50:
lbl1.destroy()
if i >= 200:
s = ttk.Style()
s.theme_use('clam')
if i % 5:
s.configure("red.Horizontal.TProgressbar",
foreground='#205F8C', background='#205F8C')
else:
s.configure("red.Horizontal.TProgressbar",
foreground='red', background='red')
pbar_det.config(style="red.Horizontal.TProgressbar")
pbar_det.step(0.33)
master.update()
# Busy-wait
time.sleep(0.1)
master.destroy()
def stop():
master.destroy()
master = Tk()
photo1 = PhotoImage(file="Press1.gif")
photo2 = PhotoImage(file="Press2.gif")
ws = master.winfo_screenwidth() # width of the screen
hs = master.winfo_screenheight() # height of the screen
width = ws
height = 120
x = ws-width # Window's coords
y = hs-height
start_stop = Button(master, text='START', command=start, image=photo1)
start_stop.grid(row=0, column=1, pady=2, padx=2, sticky=E+W+N+S)
pbar_det = ttk.Progressbar(master, orient="horizontal", length=ws-450,
mode="determinate")
pbar_det.grid(row=0, column=0, pady=2, padx=2, sticky=E+W+N+S)
lbl1 = Label(master)
lbl1.grid(row=0, column=1, pady=2, padx=2)
lbl2 = Label(master)
lbl2.grid(row=0, column=1, pady=2, padx=2)
master.geometry('%dx%d+%d+%d' % (width, height, x, y))
master.mainloop()
Try anchoring the text within the label to the left.
lbl1.config(anchor="nw") # nw = north-west = top left
I see at least four problems in the code:
you are forcing the size of the widget to a specific height when you call master.geometry(...). This prevents the window from growing or shrinking to fit the internal widgets
you are specifying a really huge font (400 points) which can't fit in the height you've picked. Because of the huge size of the font relative to the space you've given it (by explicitly setting the height of the window) what you see is the natural whitespace above the letters that is present in all fonts).
you are putting two labels and one button on top of each other in the same row and column
you are using threading. Tkinter isn't thread safe. Your code might work, but it might not.
If you remove the call to master.geometry(), the window will expand to fit the height of the text. Or, you can not make the font so huge.
I found this introductory tutorial online, but when I run it, I get just a blank window. Any idea what's going wrong?
# A more complex example showing the use of classes in GUI programming
from tkinter import *
class GUIExample:
def __init__(self, master):
self.master = master # for showInfo()
# Create frame to hold widgets
frame = Frame(master)
# Create label for header
self.headLbl = Label(frame, text="Widget Demo Program", relief=RIDGE)
self.headLbl.pack(side=TOP, fill=X)
# Dummy frame for border
spacerFrame = Frame(frame, borderwidth=10)
# Create frame to hold center part of the form
centerFrame = Frame(spacerFrame)
leftColumn = Frame(centerFrame, relief=GROOVE, borderwidth=10)
rightColumn = Frame(centerFrame, relief=GROOVE, borderwidth=10)
# Some colorful widgets
self.colorLabel = Label(rightColumn, text="Select a color")
self.colorLabel.pack(expand=YES, fill=X)
entryText = StringVar(master)
entryText.set("Select a color")
self.colorEntry = Entry(rightColumn, textvariable=entryText)
self.colorEntry.pack(expand=YES, fill=X)
# Create some radio buttons
self.radioBtns = []
self.radioVal = StringVar(master)
btnList = ("black", "red", "green", "blue", "white", "yellow")
for color in btnList:
self.radioBtns.append(Radiobutton(leftColumn, text=color, value=color, \
indicatoron=TRUE, variable=self.radioVal, command=self.updateColor))
else:
if (len(btnList) > 0):
self.radioVal.set(btnList[0])
self.updateColor()
for btn in self.radioBtns:
btn.pack(anchor=W)
# Make the frames visible
leftColumn.pack(side=LEFT, expand=YES, fill=Y)
rightColumn.pack(side=LEFT, expand=YES, fill=BOTH)
centerFrame.pack(side=TOP, expand=YES, fill=BOTH)
# Create an indicator checkbutton
self.indicVal = BooleanVar(master)
self.indicVal.set(TRUE)
self.updateIndic()
Checkbutton(spacerFrame, text="Show indicator", command=self.updateIndic, \
variable=self.indicVal).pack(side=TOP, fill=X)
# Create the Info button
Button(spacerFrame, text="Info", command=self.showInfo).pack(side=TOP, fill=X)
# Create the Quit button
Button(spacerFrame, text="Quit", command=self.quit).pack(side=TOP, fill=X)
def quit(self):
import sys
sys.exit()
def updateColor(self):
self.colorLabel.configure(fg=self.radioVal.get())
self.colorEntry.configure(fg=self.radioVal.get())
def updateIndic(self):
for btn in self.radioBtns:
btn.configure(indicatoron=self.indicVal.get())
def updateColorPrev(self):
if (self.colorprevVal.get()):
for btn in self.radioBtns:
color = btn.cget("text")
btn.configure(fg=color)
else:
for btn in self.radioBtns:
btn.configure(fg="black")
def showInfo(self):
toplevel = Toplevel(self.master, bg="white")
toplevel.transient = self.master
toplevel.title("Program info")
Label(toplevel, text="A simple Tkinter demo", fg="navy", bg="white").pack(pady=20)
Label(toplevel, text="Written by Bruno Dufour", bg = "white").pack()
Label(toplevel, text="http://www.cs.mcgill.ca/ Ėbdufou1/", bg="white").pack()
Button(toplevel, text="Close", command=toplevel.withdraw).pack(pady=30)
root = Tk()
ex = GUIExample(root)
root.title("A simple widget demo")
root.mainloop()
They forgot
frame.pack()
and
spacerFrame.pack()
I didn't test rest of code.
EDIT: BTW: In place of
def quit(self):
import sys
sys.exit()
I would use something more natural - and less drastic
def quit(self):
self.master.destroy()