Related
This is a part of code from my school project.
from tkinter import *
from tkinter.font import Font
class student_window():
def __init__(self, master):
self.student_win = master
#window = Toplevel(self.master)
self.student_win.geometry("1280x720")
self.header1Font = Font(family='Helvetica', size=20)
self.optionFont = Font(family='Sans Serrif', size=20)
self.student_win.focus()
self.show_window()
def show_window(self):
print("ookk")
self.student_win.title("Student Window")
self.option_frame = Frame(self.student_win, width=200, height=720)
lbl_header = Label(self.option_frame,text="EXAMINATION", font=self.header1Font, fg='white', bg='#172D44').grid(row=0,column=0, sticky=NSEW)
lbl_welcome = Label(self.option_frame, text="Welcome,", fg='#E9F1F7', bg='#2A3F54').grid(row=1,column=0)
lbl_username = Label(self.option_frame, text="Username", fg='white', bg='#2A3F54').grid(row=2,column=0)
lbl_header2 = Label(self.option_frame, text="STUDENT CORNER", fg='white', bg='#2A3F54').grid(row=3, column=0)
self.btn_tests = Button(self.option_frame, text="Attempt Exam", fg='#E9F1F7', bg='#35495D', relief=FLAT)
self.btn_tests.grid(row=4,column=0, sticky=NSEW)
self.btn_attempts = Button(self.option_frame, text="Attempts", fg='#E9F1F7', bg='#2A3F54', relief=FLAT)
self.btn_attempts.grid(row=5, column=0, sticky=NSEW)
self.btn_result = Button(self.option_frame, text="Result", fg='#E9F1F7', bg='#2A3F54', relief=FLAT)
self.btn_result.grid(row=6, column=0, sticky=NSEW)
self.btn_goBack = Button(self.option_frame, text="Go Back", fg='#E9F1F7', bg='#2A3F54', relief=FLAT)
self.btn_goBack.grid(row=7, column=0, sticky=NSEW)
self.option_frame.configure(bg='#2A3F54')
self.option_frame.grid(row=0, column=0)
self.option_frame.grid_propagate(0)
self.main_frame = Frame(self.student_win, width=880, height=720)
self.main_result_frame = Frame(self.main_frame)
self.main_result_frame.grid(row=0,column=0)
self.attempts_frame = Frame(self.main_frame)
self.attempts_frame.grid(row=0, column=0)
self.test_frame = Frame(self.main_frame)
lbl_test = Label(self.test_frame, text="In test frame").pack()
self.test_frame.grid(row=0,column=0)
self.main_frame.grid(row=0,column=1)
self.main_frame.grid_propagate(0)
self.info_frame = Frame(self.student_win, width=200, height=720)
self.btn_username = Button(self.info_frame, text="Username", relief=FLAT)
self.btn_username.grid(row=0,column=0)
self.userInfo_frame = Frame(self.info_frame)
self.info_frame.grid(row=0, column=2)
self.info_frame.grid_propagate(0)
root = Tk()
student_window(root)
root.mainloop()
And it looks something like this.
The Student Panel for my project
The whole window is divided into three frames and want to expand each label and button of the left frame(self.option_frame) to fill it horizontally. I tried doing sticky=EW and sticky=NSEW but still some space is left. How do I fix that?
You need to call self.option_frame.columnconfigure(0, weight=1) to make column 0 to use all the available horizontal space.
I was just trying some things and what I have found to be working is to make the label width bigger than than the frame then anchoring the text to the left.
I have a canvas which is opening in the main window, but I want it to open in the canvas inside the Frame. I tried to add some crazy (as they're in my hands) attributes, but they didn't solve my problem, so I deleted them and have no idea what to do. Could you help me - how could I realize it?
code:
from tkinter import *
from tkinter import ttk
class Example(Frame): # Frame
def __init__(self, root):
Frame.__init__(self, root)
# ---- for grid ----
canvas_width = 1000 # MUTABLE!!!
canvas_height = 1000 # MUTABLE!!!
self.canvas_width = canvas_width
self.canvas_height = canvas_height
self.warehouse_canvas = Canvas(self, width=canvas_width, height=canvas_height, background="bisque")
self.x_scrollbar = Scrollbar(self, orient="horizontal", command=self.warehouse_canvas.xview)
self.y_scrollbar = Scrollbar(self, orient="vertical", command=self.warehouse_canvas.yview)
self.warehouse_canvas.configure(yscrollcommand=self.y_scrollbar.set, xscrollcommand=self.x_scrollbar.set)
self.warehouse_canvas.configure(scrollregion=(0, 0, canvas_width, canvas_height)) # c_w c_h изменены
self.x_scrollbar.grid(row=1, column=0, sticky="ew")
self.y_scrollbar.grid(row=0, column=1, sticky="ns")
self.warehouse_canvas.grid(row=0, column=0, sticky="nsew")
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
root.columnconfigure(1, weight=1)
root.rowconfigure(1, weight=1)
self.warehouse_canvas.bind("<ButtonPress-1>", self.scroll_start)
self.warehouse_canvas.bind("<B1-Motion>", self.scroll_move)
self.draw_grid(self.warehouse_canvas, canvas_width, canvas_height) # !!!
def draw_grid(root, warehouse_canvas, canvas_width, canvas_height):
for line in range(0, canvas_width, 10):
warehouse_canvas.create_line([(line, 0), (line, canvas_height)], fill='#d9d9d9', tags='grid_line_width')
for line in range(0, canvas_width, 10):
warehouse_canvas.create_line([(0, line), (canvas_width, line)], fill='#d9d9d9', tags='grid_line_height')
# warehouse_canvas.grid(row=0, column=0) color : #d9d9d9
def scroll_start(self, event):
self.warehouse_canvas.scan_mark(event.x, event.y)
def scroll_move(self, event):
self.warehouse_canvas.scan_dragto(event.x, event.y, gain=1)
if __name__ == "__main__":
root = Tk()
Example(root).grid(row=0, column=0, # положение
columnspan=2, rowspan=2, sticky="nsew")
root.mainloop()
What I have
Where I want it to be
Is there any way to do it without rewriting the whole code?
I am trying to color the canvas oval item, indicating when the program is busy (status indicator). The expected behavior is to turn the canvas oval red when clicking the check button, and turn it back off (to blue ) when the function is complete.
Here is the code I have so far
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.entryframe = Frame(self, width=800, height=500)
self.entryframe.pack(fill=X, padx=8, pady=8)
self.canvas = Canvas(self.entryframe, width=30, height=30)
# change fill color of the status oval
self.status = self.canvas.create_oval(10, 10, 30, 30, fill="blue", tags="state")
self.canvas.grid(row=0, column=1)
self.label = Label(self.entryframe, text="Enter your sentence:").grid(row=3, column=0, sticky=W)
self.text = Text(self.entryframe, wrap=WORD, width=70, height=10)
self.text.grid(row=4, column=0, sticky=W)
self.btn_h = Button(self.entryframe, text="Check", width="15", command=self.check_input).grid(row=5, column=0, padx=8, pady=8)
def check_input(self):
#change status oval color to red
self.canvas.itemconfig(status, fill='red')
# after the function is complete turn it back off
canvas.itemconfig(light_1, fill='blue')
root = App()
root.mainloop()
The current behavior is the item stays blue color, and does not change at all.
You saved the reference to the oval as a class attribute. So you will need to access it the same way by passing self:
import tkinter as tk
from threading import Thread
import time
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.entryframe = tk.Frame(self, width=800, height=500)
self.entryframe.pack(fill=tk.X, padx=8, pady=8)
self.canvas = tk.Canvas(self.entryframe, width=30, height=30)
# change fill color of the status oval
self.status = self.canvas.create_oval(10, 10, 30, 30, fill="blue", tags="state")
self.canvas.grid(row=0, column=1)
self.label = tk.Label(self.entryframe, text="Enter your sentence:").grid(row=3, column=0, sticky=tk.W)
self.text = tk.Text(self.entryframe, wrap=tk.WORD, width=70, height=10)
self.text.grid(row=4, column=0, sticky=tk.W)
self.btn_h = tk.Button(self.entryframe, text="Check", width="15", command=self.check_input).grid(row=5, column=0, padx=8, pady=8)
def check_input(self):
#change status oval color to red
self.canvas.itemconfig(self.status, fill='red') #use self.status instead
t = Thread(target=self.lengthy_process,args=("Arg1","Arg2"))
t.start()
def lengthy_process(self,param1,param2):
print (param1, param2)
time.sleep(10) #simulate the time taken for processing
print ("Finished!")
self.canvas.itemconfig(self.status, fill='blue')
root = App()
root.mainloop()
Also, judging from how you inherit the instance of tk and create the rest of the widgets, it looks like you have a mix of both import tkinter as tk and from tkinter import *. This is generally considered bad practice - it is more reasonable to simply use import tkinter as tk so you know which widgets belong to tk, and which belongs to ttk if required. Also read Why is “import *” bad?
I'm trying to create a canvas widget with a number of widgets embedded within it. Since there will frequently be too many widgets to fit in the vertical space I have for the canvas, it'll need to be scrollable.
import tkinter as tk # for general gui
import tkinter.ttk as ttk # for notebook (tabs)
class instructionGeneratorApp():
def __init__(self, master):
# create a frame for the canvas and scrollbar
domainFrame = tk.LabelFrame(master)
domainFrame.pack(fill=tk.BOTH, expand=1)
# make the canvas expand before the scrollbar
domainFrame.rowconfigure(0,weight=1)
domainFrame.columnconfigure(0,weight=1)
vertBar = ttk.Scrollbar(domainFrame)
vertBar.grid(row=0, column=1, sticky=tk.N + tk.S)
configGridCanvas = tk.Canvas(domainFrame,
bd=0,
yscrollcommand=vertBar.set)
configGridCanvas.grid(row=0, column=0, sticky=tk.N + tk.S + tk.E + tk.W)
vertBar.config(command=configGridCanvas.yview)
# add widgets to canvas
l = tk.Label(configGridCanvas, text='Products')
l.grid(row=1, column=0)
r = 2
for product in ['Product1','Product2','Product3','Product4','Product5','Product6','Product7','Product8','Product9','Product10','Product11','Product12','Product13','Product14','Product15','Product16','Product17','Product18','Product19','Product20']:
l = tk.Label(configGridCanvas, text=product)
l.grid(row=r, column=0)
c = tk.Checkbutton(configGridCanvas)
c.grid(row=r, column=1)
r += 1
ButtonFrame = tk.Frame(domainFrame)
ButtonFrame.grid(row=r, column=0)
removeServerButton = tk.Button(ButtonFrame, text='Remove server')
removeServerButton.grid(row=0, column=0)
# set scroll region to bounding box?
configGridCanvas.config(scrollregion=configGridCanvas.bbox(tk.ALL))
root = tk.Tk()
mainApp = instructionGeneratorApp(root)
root.mainloop()
As best as I can tell, I'm following the effbot pattern for canvas scrollbars, but I end up with either a scrollbar that isn't bound to the canvas, or a canvas that is extending beyond the edges of its master frame:
I've attempted the solutions on these questions, but there's still something I'm missing:
resizeable scrollable canvas with tkinter
Tkinter, canvas unable to scroll
Any idea what I'm doing wrong?
I have added some comments to #The Pinapple 's solution for future reference.
from tkinter import *
class ProductItem(Frame):
def __init__(self, master, message, **kwds):
Frame.__init__(self, master, **kwds)
self.grid_rowconfigure(1, weight=1)
self.grid_columnconfigure(0, weight=1)
self.text = Label(self, text=message, anchor='w')
self.text.grid(row=0, column=0, sticky='nsew')
self.check = Checkbutton(self, anchor='w')
self.check.grid(row=0, column=1)
class ScrollableContainer(Frame):
def __init__(self, master, **kwargs):
#our scrollable container is a frame, this frame holds the canvas we draw our widgets on
Frame.__init__(self, master, **kwargs)
#grid and rowconfigure with weight 1 are used for the scrollablecontainer to utilize the full size it can get from its parent
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
#canvas and scrollbars are positioned inside the scrollablecontainer frame
#the scrollbars take a command parameter which is used to position our view on the canvas
self.canvas = Canvas(self, bd=0, highlightthickness=0)
self.hScroll = Scrollbar(self, orient='horizontal',
command=self.canvas.xview)
self.hScroll.grid(row=1, column=0, sticky='we')
self.vScroll = Scrollbar(self, orient='vertical',
command=self.canvas.yview)
self.vScroll.grid(row=0, column=1, sticky='ns')
self.canvas.grid(row=0, column=0, sticky='nsew')
#We do not only need a command to position but also one to scroll
self.canvas.configure(xscrollcommand=self.hScroll.set,
yscrollcommand=self.vScroll.set)
#This is the frame where the magic happens, all of our widgets that are needed to be scrollable will be positioned here
self.frame = Frame(self.canvas, bd=2)
self.frame.grid_columnconfigure(0, weight=1)
#A canvas itself is blank, we must tell the canvas to create a window with self.frame as content, anchor=nw means it will be positioned on the upper left corner
self.canvas.create_window(0, 0, window=self.frame, anchor='nw', tags='inner')
self.product_label = Label(self.frame, text='Products')
self.product_label.grid(row=0, column=0, sticky='nsew', padx=2, pady=2)
self.products = []
for i in range(1, 21):
item = ProductItem(self.frame, ('Product' + str(i)), bd=2)
item.grid(row=i, column=0, sticky='nsew', padx=2, pady=2)
self.products.append(item)
self.button_frame = Frame(self.frame)
self.button_frame.grid(row=21, column=0)
self.remove_server_button = Button(self.button_frame, text='Remove server')
self.remove_server_button.grid(row=0, column=0)
self.update_layout()
#If the widgets inside the canvas / the canvas itself change size,
#the <Configure> event is fired which passes its new width and height to the corresponding callback
self.canvas.bind('<Configure>', self.on_configure)
def update_layout(self):
#All pending events, callbacks, etc. are processed in a non-blocking manner
self.frame.update_idletasks()
#We reconfigure the canvas' scrollregion to fit all of its widgets
self.canvas.configure(scrollregion=self.canvas.bbox('all'))
#reset the scroll
self.canvas.yview('moveto', '1.0')
#fit the frame to the size of its inner widgets (grid_size)
self.size = self.frame.grid_size()
def on_configure(self, event):
w, h = event.width, event.height
natural = self.frame.winfo_reqwidth() #natural width of the inner frame
#If the canvas changes size, we fit the inner frame to its size
self.canvas.itemconfigure('inner', width=w if w > natural else natural)
#dont forget to fit the scrollregion, otherwise the scrollbar might behave strange
self.canvas.configure(scrollregion=self.canvas.bbox('all'))
if __name__ == "__main__":
root = Tk()
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
sc = ScrollableContainer(root, bd=2)
sc.grid(row=0, column=0, sticky='nsew')
root.mainloop()
From what I can tell you are looking for something like this..
from tkinter import *
class ProductItem(Frame):
def __init__(self, master, message, **kwds):
Frame.__init__(self, master, **kwds)
self.grid_rowconfigure(1, weight=1)
self.grid_columnconfigure(0, weight=1)
self.text = Label(self, text=message, anchor='w')
self.text.grid(row=0, column=0, sticky='nsew')
self.check = Checkbutton(self, anchor='w')
self.check.grid(row=0, column=1)
class ScrollableContainer(Frame):
def __init__(self, master, **kwargs):
Frame.__init__(self, master, **kwargs) # holds canvas & scrollbars
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
self.canvas = Canvas(self, bd=0, highlightthickness=0)
self.hScroll = Scrollbar(self, orient='horizontal',
command=self.canvas.xview)
self.hScroll.grid(row=1, column=0, sticky='we')
self.vScroll = Scrollbar(self, orient='vertical',
command=self.canvas.yview)
self.vScroll.grid(row=0, column=1, sticky='ns')
self.canvas.grid(row=0, column=0, sticky='nsew')
self.canvas.configure(xscrollcommand=self.hScroll.set,
yscrollcommand=self.vScroll.set)
self.frame = Frame(self.canvas, bd=2)
self.frame.grid_columnconfigure(0, weight=1)
self.canvas.create_window(0, 0, window=self.frame, anchor='nw', tags='inner')
self.product_label = Label(self.frame, text='Products')
self.product_label.grid(row=0, column=0, sticky='nsew', padx=2, pady=2)
self.products = []
for i in range(1, 21):
item = ProductItem(self.frame, ('Product' + str(i)), bd=2)
item.grid(row=i, column=0, sticky='nsew', padx=2, pady=2)
self.products.append(item)
self.button_frame = Frame(self.frame)
self.button_frame.grid(row=21, column=0)
self.remove_server_button = Button(self.button_frame, text='Remove server')
self.remove_server_button.grid(row=0, column=0)
self.update_layout()
self.canvas.bind('<Configure>', self.on_configure)
def update_layout(self):
self.frame.update_idletasks()
self.canvas.configure(scrollregion=self.canvas.bbox('all'))
self.canvas.yview('moveto', '1.0')
self.size = self.frame.grid_size()
def on_configure(self, event):
w, h = event.width, event.height
natural = self.frame.winfo_reqwidth()
self.canvas.itemconfigure('inner', width=w if w > natural else natural)
self.canvas.configure(scrollregion=self.canvas.bbox('all'))
if __name__ == "__main__":
root = Tk()
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
sc = ScrollableContainer(root, bd=2)
sc.grid(row=0, column=0, sticky='nsew')
root.mainloop()
def create_layout(frame):
frame = Frame(frame, bg = 'red')
frame.pack(side = LEFT, fill=BOTH)
b = Button(frame, text='Button1', command=pressed, padx = 20)
b.pack(pady = 20, padx = 20)
c = Button(frame, text='Button2', command=pressed, padx=20)
c.pack(pady = 20, padx = 20)
I got this code so far, assume that from Tkinter import * has already been called and the frame has already had its size and colour set. It should look like the picture below. However i can't ever get button 3 and 4 to the frame on the right, whenever i add a button it goes in the red frame.
OK, the first set of buttons, button 1 & 2 are in the "frame", buttons 3 & 4 should be left out.
So with buttons 1 & 2, open the frame with the bg of red, pack it with side=tk.LEFT, fill with both & expand it.
With buttons 3 & 4, just side them LEFT and expand. That will work like a treat ;-)
You need to add another frame that sits to the right, and then pack button3 and button4 into that. Maybe change the previous frame you have there to frame1 and then have:
frame2 = Frame(frame, bg = "yellow")
frame2.pack(side = RIGHT, fill = BOTH)
Then, create the buttons and pack them in. Hope this helps!
You have 2 frames, and 4 buttons.
Let us create a function called create_widgets() which will only consist in calling 2 other functions create_frames() and create_buttons()
For the frames, we use the grid() layout manager:
def create_frames(self):
self.left_frame = tk.Frame(width=140, height=140, background='red')
self.left_frame.grid(row=0, column=0)
self.right_frame = tk.Frame(width=300, height=140, background='gold2')
self.right_frame.grid(row=0, column=1)
This will create this interface:
Let us design create_buttons() in a way it only consists in calling to 2 different functions, each having a specific task:
create_left_frame_buttons() to create buttons for the left frame
create_right_frame_buttons() to create buttons for the right frame
Here is their simple implementation:
def create_buttons(self):
self.create_left_frame_buttons()
self.create_right_frame_buttons()
def create_left_frame_buttons(self):
self.button1 = tk.Button(self.left_frame, text='Button1')
self.button1.grid(row=0, column=0, padx=30, pady=20)
self.button2 = tk.Button(self.left_frame, text='Button2')
self.button2.grid(row=1, column=0, padx=30, pady=20)
def create_right_frame_buttons(self):
self.button1 = tk.Button(self.right_frame, text='Button3')
self.button1.grid(row=0, column=0, padx=20, pady=50)
self.button2 = tk.Button(self.right_frame, text='Button4')
self.button2.grid(row=0, column=1, padx=70)
Note that I used the options padx and pady to create a suitable spacing between the buttons.
Up to this moment, this is the resulting interface:
You can see both the left and right frames are shrinking, and the result is ugly. To fix this issue, we can set rid_propagate(0) for each frame.
So based on these observations and following Tkinter best practices, here is the full code:
import tkinter as tk
class MainApplication(tk.Frame):
def __init__(self, master):
self.master = master
tk.Frame.__init__(self, self.master)
self.configure_gui()
self.create_widgets()
def configure_gui(self):
self.master.title('Simple layout')
self.master.geometry('440x140')
self.master.resizable(0, 0)
def create_widgets(self):
self.create_frames()
self.create_buttons()
def create_frames(self):
self.left_frame = tk.Frame(width=140, height=140, background='red')
self.left_frame.grid_propagate(0)
self.left_frame.grid(row=0, column=0)
self.right_frame = tk.Frame(width=300, height=140, background='gold2')
self.right_frame.grid_propagate(0)
self.right_frame.grid(row=0, column=1)
def create_buttons(self):
self.create_left_frame_buttons()
self.create_right_frame_buttons()
def create_left_frame_buttons(self):
self.button1 = tk.Button(self.left_frame, text='Button1')
self.button1.grid(row=0, column=0, padx=30, pady=20)
self.button2 = tk.Button(self.left_frame, text='Button2')
self.button2.grid(row=1, column=0, padx=30, pady=20)
def create_right_frame_buttons(self):
self.button1 = tk.Button(self.right_frame, text='Button3')
self.button1.grid(row=0, column=0, padx=20, pady=50)
self.button2 = tk.Button(self.right_frame, text='Button4')
self.button2.grid(row=0, column=1, padx=70)
if __name__ == '__main__':
root = tk.Tk()
main_app = MainApplication(root)
root.mainloop()
Demo: