tkinter: how do i make the Scrollbar react to the new size of the frame - python-3.x

from tkinter import *
from tkinter import ttk
class Scrollbar:
def __init__(self, master):
frame = ttk.Frame(master)
frame.pack()
self.canvas = Canvas(frame, width=700, height=930)
self.scrollbar = ttk.Scrollbar(frame, orient=VERTICAL, command=self.canvas.yview)
self.main_frame = ttk.Frame(self.canvas)
self.placing_widgets()
def placing_widgets(self):
self.canvas.pack(side=LEFT, fill=Y)
self.scrollbar.pack(side=RIGHT, fill=Y)
self.canvas.configure(yscrollcommand=self.scrollbar.set)
self.canvas.bind('<Configure>', lambda e: self.canvas.configure(scrollregion=self.canvas.bbox('all')))
self.canvas.create_window((0,0), window=self.main_frame, anchor='n')
Frame1(self.main_frame)
class Frame1:
def __init__(self, main_frame):
self.main_frame = main_frame
for i in range(100):
self.button = ttk.Button(self.main_frame, text=f"Button {i}", command=lambda i=i: self.destroying(i))
self.button.grid(row=i, column=0, pady=5)
def destroying(self, i):
for frame in self.main_frame.winfo_children():
frame.destroy()
Frame2(self.main_frame)
class Frame2:
def __init__(self, main_frame):
self.main_frame = main_frame
self.button = ttk.Button(self.main_frame, text='back', command=self.destroying)
self.button.pack()
def destroying(self):
for frame in self.main_frame.winfo_children():
frame.destroy()
Frame1(self.main_frame)
def main():
root = Tk()
root.config(background='black')
Scrollbar(root)
root.mainloop()
if __name__ == '__main__':
main()
I couldn't find a solution to this problem anywhere on the internet.
I have given a scrollbar to a lengthy frame but when I make the frame shorter, I don't know why the scrollbar doesn't resize to the frame's length.
Above is a short program that represents the problem I am talking about. The program has 2 different pages, one with 100 buttons and the other with one button. You can switch between pages with any buttons and see that the page with one button is scrollable which shouldn't be.
Could you please provide a solution to how I could make the scrollbar resize to the frame's length? Thank you for your time.

Related

Is there a way to make tkinter windows work independantly Tkinter?

I have been looking to create a code that opens a second tkinter window to display stuffs live while a program is running on my main window. However, doing so, my main window gets frozen during 5s and then displays stuff on my second window when it is completed.
Is there a way to live display in the second window ?
My code below:
import tkinter as tk
from tkinter import ttk
import time
class PopUpLog(tk.Tk):
def __init__(self, parent):
tk.Tk.__init__(self)
self.y=5
tk.Button(self.master, text="Write in pop-up", command=self.write).pack(side="left")
# canvas
frameL = tk.Frame(self)
frameL.pack(side="left", fill="both")
self.canvasL = tk.Canvas(frameL, height=800, width=800)
self.canvasL.pack(fill="both", expand=True)
# scrollbar
vsb = ttk.Scrollbar(self, orient="v", command=self.canvasL.yview)
vsb.pack(side="left", fill="y")
self.canvasL.configure(yscrollcommand=vsb.set)
self.canvasL.bind("<Configure>", lambda e:self.canvasL.configure(scrollregion=self.canvasL.bbox("all")))
def write(self, text="hi im a pop-up"):
for i in range(5):
self.canvasL.create_text(5, self.y, anchor='nw', justify='left', text=text)
self.y += 25
time.sleep(1)
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
tk.Button(self, text="Open window", command=self.popup).pack(side="left")
def popup(self):
self.top = PopUpLog(self)
self.top.geometry("400x400")
self.top.title("pop-up")
self.top.mainloop()
if __name__ == "__main__":
root = App()
root.mainloop()
So far, the program runs for 5s and then displays everything in self.top. BUT I need a live display (made every time create_text is called) in self.top but I can't even get that.
I am sorry if this is redundant to another question asked but I couldn't find helpful enough information.
Thanks a lot !
time.sleep is the reason why your window is freezing. This is the case for virtually any GUI toolkit. If you want the updates to happen incrementally you can use the after method which will execute the callback you assign after a certain number of milliseconds.
Also there should only be one mainloop. There is no need to start one per window and doing so could cause problems.
Here is an example using the after method:
class PopUpLog(tk.Tk):
def __init__(self, parent):
tk.Tk.__init__(self)
self.y=5
self.c=5 # counter
tk.Button(self.master, text="Write in pop-up", command=self.write).pack(side="left")
# canvas
frameL = tk.Frame(self)
frameL.pack(side="left", fill="both")
self.canvasL = tk.Canvas(frameL, height=800, width=800)
self.canvasL.pack(fill="both", expand=True)
# scrollbar
vsb = ttk.Scrollbar(self, orient="v", command=self.canvasL.yview)
vsb.pack(side="left", fill="y")
self.canvasL.configure(yscrollcommand=vsb.set)
self.canvasL.bind("<Configure>", lambda e:self.canvasL.configure(scrollregion=self.canvasL.bbox("all")))
def write(self, text="hi im a pop-up"):
if self.c > 0:
self.canvasL.create_text(5, self.y, anchor='nw', justify='left', text=text)
self.y += 25
self.c -= 1 # reduce counter
self.after(1000, self.write) # call again in 1 second
else:
self.c = 5 # when counter is 0 reset counter
class App(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
tk.Button(self, text="Open window", command=self.popup).pack(side="left")
def popup(self):
self.top = PopUpLog(self)
self.top.geometry("400x400")
self.top.title("pop-up")
if __name__ == "__main__":
root = App()
root.mainloop()

How to resize Canvas scrollable widget?

The idea is that the scrollable canvas and its text widgets grow or fill the entire root/toplevel when I resize it.
I can do this if I work on Frames but for a scrollable frame you need to create a canvas widget and make it scrollable. Now I don't know if the problem is the canvas or the inserted widgets on the canvas?
import tkinter as tk
from tkinter import ttk
class ScrollableFrame():
def __init__(self, container, *args, **kwargs):
self.container = container
self.canvas = tk.Canvas(self.container, bg="green")
self.scrollbar = ttk.Scrollbar(self.container, orient="horizontal", command=self.canvas.xview)
self.scrollable_frame = tk.Frame(self.canvas)
self.scrollable_frame.grid(sticky="wesn")
self.scrollable_frame.bind("<Configure>", lambda e: self.canvas.configure(scrollregion=self.canvas.bbox("all")))
self.canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
self.canvas.configure(xscrollcommand=self.scrollbar.set)
self.canvas.grid(row=0, column=0, sticky="wesn")
self.scrollbar.grid(row=1, column=0, sticky="wesn")
if __name__ == "__main__":
root = tk.Tk()
root.configure(bg="grey20")
s = ScrollableFrame(root)
t = tk.Text(s.scrollable_frame)
t.grid(row=0, column=0, sticky="wesn")
t2 = tk.Text(s.scrollable_frame)
t2.grid(row=0, column=1, sticky="wesn")
root.mainloop()
I'm glad for help
Base on the requirement in the comment, you want to grow/shrink the two text boxes when the root window is resized. Below is the modified code with the necessary changes to achieve it:
import tkinter as tk
from tkinter import ttk
class ScrollableFrame():
def __init__(self, container, *args, **kwargs):
self.container = container
self.canvas = tk.Canvas(self.container, bg="green")
self.scrollbar = ttk.Scrollbar(self.container, orient="horizontal", command=self.canvas.xview)
self.scrollable_frame = tk.Frame(self.canvas)
self.scrollable_frame.grid(sticky="wesn")
self.scrollable_frame.bind("<Configure>", lambda e: self.canvas.configure(scrollregion=self.canvas.bbox("all")))
self.canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw", tags="scrollable")
self.canvas.configure(xscrollcommand=self.scrollbar.set)
self.canvas.grid(row=0, column=0, sticky="wesn")
self.scrollbar.grid(row=1, column=0, sticky="we") # fill horizontally only
# make self.canvas to fill all the available space of container
container.rowconfigure(0, weight=1)
container.columnconfigure(0, weight=1)
# resize self.scrollable_frame when self.canvas is resized
self.canvas.bind("<Configure>", lambda e: self.canvas.itemconfig("scrollable", width=e.width, height=e.height))
if __name__ == "__main__":
root = tk.Tk()
root.configure(bg="grey20")
s = ScrollableFrame(root)
# make the two text boxes to fill all the space of s.scrollable_frame
s.scrollable_frame.rowconfigure(0, weight=1)
s.scrollable_frame.columnconfigure((0,1), weight=1)
t = tk.Text(s.scrollable_frame)
t.grid(row=0, column=0, sticky="wesn")
t2 = tk.Text(s.scrollable_frame)
t2.grid(row=0, column=1, sticky="wesn")
root.mainloop()
So basically the logic is when the root window is resized, the canvas is resized to fill the root window. Then the frame (scrollable_frame) inside the canvas is resized to fill the canvas and finally the two text widgets are resized to fill the scrollable_frame.

Ttk Frame, Background color

I'm using ttk, for my GUI. I know that it is also a very simple question ... I am trying to change the background color of the main window.
I tried to change the theme, because I am working on a Mac, (and Python 3.5) to avoid the problem with the theme 'aqua', which is the default.I've been reading about several solutions like these questions which are about the same problem... These are the numbers of the questions:
54476511,
38712352,
47327266,
23750141.
But, I haven't Solve the problem, yet.
Here it's my code.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from tkinter.scrolledtext import *
from tkinter import Tk, BOTH, W, N, E, S, messagebox, END
from tkinter.ttk import Button, Label, Style, Frame
class Example(Frame):
def __init__(self,master):
super().__init__()
self.initUI()
def initUI(self):
self.master.title("Example")
Style().theme_use("classic")
self.pack(fill=BOTH, expand=1)
self.columnconfigure(1, weight=1)
self.columnconfigure(3, pad=7)
self.rowconfigure(3, weight=1)
self.rowconfigure(5, pad=7)
self.txt_Pad = ScrolledText(self)
self.txt_Pad.grid(row=1, column=0, columnspan=2, rowspan=4, padx=5, sticky=E+W+S+N)
self.txt_Pad.insert(END,'Type your info here')
btn_save = Button(self, text="Save", command=self.save_command)
btn_save.grid(row=1, column=3)
btn_close = Button(self, text="Close", command=self.onClose)
btn_close.grid(row=2, column=3, pady=4)
btn_help = Button(self, text="Help", command=self.about_command)
btn_help.grid(row=5, column=0, padx=5)
def onClose(self):
self.master.destroy()
def about_command(self):
msb = messagebox.showinfo("About", "\"Insert a useful tip Here\"")
def save_command(self):
print('Your info it\'s save now')
def open_command(self):
print('Choose your File')
def main():
root = Tk()
root.geometry("350x300+300+300")
root.configure(bg='#0059b3')
app = Example(root)
root.mainloop()
if __name__ == '__main__':
main()
Any Suggestions would be appreciated.
Create a style then apply it.
from tkinter.scrolledtext import *
from tkinter import Tk, BOTH, W, N, E, S, messagebox, END
from tkinter.ttk import Button, Label, Style, Frame
class Example(Frame):
def __init__(self, master):
super().__init__()
self.initUI()
def initUI(self):
self.master.title("Example")
# create a new style
self.style = Style()
# configure it to the background you want
self.style.configure('My.TFrame', background='#0059b3')
#Style().theme_use("classic")
# apply it
self.config(style='My.TFrame')
self.pack(fill=BOTH, expand=1)
self.columnconfigure(1, weight=1)
self.columnconfigure(3, pad=7)
self.rowconfigure(3, weight=1)
self.rowconfigure(5, pad=7)
self.txt_Pad = ScrolledText(self)
self.txt_Pad.grid(row=1, column=0, columnspan=2, rowspan=4, padx=5, sticky=E+W+S+N)
self.txt_Pad.insert(END,'Type your info here')
btn_save = Button(self, text="Save", command=self.save_command)
btn_save.grid(row=1, column=3)
btn_close = Button(self, text="Close", command=self.onClose)
btn_close.grid(row=2, column=3, pady=4)
btn_help = Button(self, text="Help", command=self.about_command)
btn_help.grid(row=5, column=0, padx=5)
def onClose(self):
self.master.destroy()
def about_command(self):
msb = messagebox.showinfo("About", "\"Insert a useful tip Here\"")
def save_command(self):
print('Your info it\'s save now')
def open_command(self):
print('Choose your File')
def main():
root = Tk()
root.geometry("350x300+300+300")
root.configure(background='#0059b3')
app = Example(root)
root.mainloop()
if __name__ == '__main__':
main()
I left comments at the parts I changed.

Tkinter Python Frame with Scroll bar

I have below code which will create Label and Entry widgets. Also I created Scroll bar for this window but both scroll bars are not working.
Please let me know why the scroll bar is disabled. Did the code is doing wrong ?
Below is my code which will create 50 labels and entry widgets but still scroll bars are not enabled.
import tkinter as tk
from tkinter import ttk
class DoubleScrollbarFrame(ttk.Frame):
def __init__(self, master, **kwargs):
'''
Initialisation. The DoubleScrollbarFrame consist of :
- an horizontal scrollbar
- a vertical scrollbar
- a canvas in which the user can place sub-elements
'''
ttk.Frame.__init__(self, master, **kwargs)
# Canvas creation with double scrollbar
self.hscrollbar = ttk.Scrollbar(self, orient=tk.HORIZONTAL)
self.vscrollbar = ttk.Scrollbar(self, orient=tk.VERTICAL)
self.sizegrip = ttk.Sizegrip(self)
self.canvas = tk.Canvas(self, bd=0, highlightthickness=0,
yscrollcommand=self.vscrollbar.set,
xscrollcommand=self.hscrollbar.set)
self.vscrollbar.config(command=self.canvas.yview)
self.hscrollbar.config(command=self.canvas.xview)
def pack(self, **kwargs):
'''
Pack the scrollbar and canvas correctly in order to recreate the
same look as MFC's windows.
'''
self.hscrollbar.pack(side=tk.BOTTOM, fill=tk.X, expand=tk.FALSE)
self.vscrollbar.pack(side=tk.RIGHT, fill=tk.Y, expand=tk.FALSE)
self.sizegrip.pack(in_=self.hscrollbar, side=tk.BOTTOM, anchor="se")
self.canvas.pack(side=tk.LEFT, padx=5, pady=5,
fill=tk.BOTH, expand=tk.TRUE)
ttk.Frame.pack(self, **kwargs)
def get_frame(self):
'''
Return the "frame" useful to place inner controls.
'''
return self.canvas
if __name__ == '__main__':
# Top-level frame
root = tk.Tk()
root.title("Double scrollbar with tkinter")
root.minsize(width=600, height=600)
frame = DoubleScrollbarFrame(root, relief="sunken")
# Add controls here
subframe = ttk.Frame(frame.get_frame())
txt = ttk.Label(subframe, text="Add things here !")
for i in range(50):
ttk.Label(subframe, text="Field %d: " % i).grid(row=i, column=0,
sticky="w")
ttk.Entry(subframe, width=25).grid(row=i, column=1, sticky="ew")
subframe.pack(padx=15, pady=15, fill=tk.BOTH, expand=tk.TRUE)
frame.pack(padx=5, pady=5, expand=True, fill=tk.BOTH)
# launch the GUI
root.mainloop()

Python frames tkinter

Im having troubles with tkinter frames
The folowing code must display labels at left side and there should be more space the button and the label , there is something wrong with my column/row setup. What am i doing wrong?
What is the correct way for a program to display information? 1 global frame with several smaller frames in it? With tkinter when using a menu with page 1 page 2 and page 3 ,
page 1 has 3 input fields , child of FramePage1 , page 2 has 2 buttons child of FramePage2, page 3 has one big text field child of FramePage3. Is it the correct way to use for changing the pages
#menu tab1 -> command #calls function page1
def page1():
self.Framepage2.grid_forget()
self.Framepage1.grid()
#content of the page
or are there other ways to use different layout style pages?
import tkinter
import tkinter as tk
class sjabloon():
def __init__(self):
#make window
self.win = tk.Tk()
self.win.geometry("600x600+10+10")
#make top frame
self.frame_header = tk.Frame(self.win, background='black', width=600, height=50)
self.frame_header.grid(column=0, row=0 , columnspan= 10)
#make body frame
self.frame_body = tk.Frame(self.win, width=600, height=400)
self.frame_body.grid(column=0, row=1 , columnspan= 10)
#button1 select
tk.Label(self.frame_body, text="Select:").grid(column=0, row=2, stick='W')
self.button1 = tk.Button(self.frame_body, text="Select")
self.button1.grid(row=2, column=5, stick='W')
#button1 select
tk.Label(self.frame_body, text="Select:").grid(column=0, row=3, stick='W')
self.button2 = tk.Button(self.frame_body, text="Select")
self.button2.grid(row=4, column=5, stick='W')
#button submit
self.submit = tk.Button(self.frame_body, text="Start")
self.submit.grid(row=10, column=9, stick='W')
#make body footer
self.frame_footer = tk.Frame(self.win, background='yellow', width=600, height=50)
self.frame_footer.grid(column=0, row=3 , columnspan= 10)
if __name__ == "__main__":
sjabloon = sjabloon()
I suggest you to follow this tkinter GUI tutorial, he makes a pretty big app and even if it's not what you exactly looking for, it will help you.
In the part 4, he make a multiple frame architecture in the tkinter GUI.
For switching "pages", i know 2 choices (there's more i think but i don't know them, i'm still a beginner). You can create all the frames inside a window/Frame and raise to the front the one you want (that's in the tutorial part 4) or you can destroy the widgets "Page 1" inside the body frame and create the widgets "Page 2" inside it (obviously in methods/functions to let you switch between the pages).
For your first problem, i'm not sure if i understand your problem, you want more space around your button widget ? if that's what you want, you can use the option padx=(leftPadx,RightPadx) like that :
self.button1.grid(row=2, column=5, stick='W', padx=(50,0))
EDIT : i made a little architecture for you (from what i learn in that tutorial)
Basically, you create all the "Page", you add them in the bodyFrame and you raise to the front the one you want. To achieve that, for each "Page", you create a class that inherits tk.Frame and you add an instance of that class in the mainWindow
import tkinter as tk
from tkinter import ttk
LARGE_FONT = ("Verdana 12")
NORM_FONT = "Verdana 10"
SMALL_FONT = ("Verdana 8")
ERROR_404 = "Error 404 : Page not found !"
class sjabloon(tk.Tk):
def __init__(self, *args, **kwargs):
#make window
tk.Tk.__init__(self, *args, **kwargs)
self.geometry("600x600+10+10")
#make top frame
self.frame_header = tk.Frame(self, background='black', width=600, height=50)
self.frame_header.grid(column=0, row=0 , columnspan= 10)
#make body frame
container = tk.Frame(self, width=600, height=400)
container.grid(column=0, row=1 , columnspan= 10)
#list of Pages
self.frames = {}
#everytime you create a "Page", you add it there
for F in (StartPage, HomePage):
frame = F(container, self)
self.frames[F] = frame
frame.grid(row=1, column = 0, sticky="nsew", columnspan= 10)
self.show_page("StartPage")
#make body footer
self.frame_footer = tk.Frame(self, background='yellow', width=600, height=50)
self.frame_footer.grid(column=0, row=3 , columnspan= 10)
def show_page(self, page_name):
"""
let us use the NAME of the class to display(the function show_frame
use directly the class).
when we use the classe name, we can put our classes in defferent
files
"""
for F in self.frames:
if F.__name__ == page_name:
self.show_frame(F)
return
print(ERROR_404)
def show_frame(self, cont):
"""raise to the front the frame we want
:param cont: the frame
"""
frame = self.frames[cont]
frame.tkraise()
class HomePage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
#button1 select
tk.Label(self, text="Select:").grid(column=0, row=2, stick='W')
self.button1 = tk.Button(self, text="Select")
self.button1.grid(row=2, column=5, stick='W', padx=(50,0))
#button1 select
tk.Label(self, text="Select:").grid(column=0, row=3, stick='W')
self.button2 = tk.Button(self, text="Select")
self.button2.grid(row=4, column=5, stick='W', padx=(50,0))
#button submit
self.submit = tk.Button(self, text="Start")
self.submit.grid(row=10, column=9, stick='W')
class StartPage(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
label = tk.Label(self, text="""ALPHA application.
use at your own risk. There is no promise
of warranty""", font=LARGE_FONT)
label.pack(pady=10, padx=10)
button1 = ttk.Button(self, text="Agree",
command=lambda: controller.show_page("HomePage"))
button1.pack()
button2 = ttk.Button(self, text="Disagree",
command=controller.destroy)
button2.pack()
if __name__ == "__main__":
sjabloon = sjabloon()
sjabloon.mainloop()

Resources