How to make whole Tkinter window scrollable - python

I am working on a GUI and have added some labelframes which barely fits the window. I want to add a menubar so after adding it, the last labelframe does not fit the window. Hence I want to make it scrollable. I tried many solutions on internet but still not able to scroll.
Here is the GUI pic:
Here is the code. Not sending all label frames code since one will give the idea.
self.window = Tk()
self.color = 'dark orange'
self.window.geometry('640x720')
self.window.resizable(False, False)
self.window.configure(background=self.color)
self.window.title('Classify')
#self.window.iconbitmap('py.ico')
self.frame = Frame(self.window)
self.frame.pack(expand=True, fill=BOTH)
self.canvas = Canvas(self.frame,background='dark orange')
self.v_scroll = Scrollbar(self.frame, orient=VERTICAL, command=self.canvas.yview,background='black',activebackground='black')
self.v_scroll.pack(side=RIGHT,fill=Y)
self.canvas['yscrollcommand'] = self.v_scroll.set
self.canvas.pack(expand=True, fill=BOTH)
self.frame2 = Frame(self.canvas,bg='dark orange')
self.canvas.create_window((0, 0), window=self.frame2, anchor=N + W)
self.menubar = Menu(self.frame2)
# Adding File Menu and commands
self.file = Menu(self.menubar, tearoff = 0)
self.menubar.add_cascade(label ='File', menu = self.file)
self.file.add_command(label ='New File', command = None)
self.window.config(menu = self.menubar)
self.frame = LabelFrame(self.canvas, text='File Selection', bg=self.color)
self.frame.place(width=580, height=80, bordermode=OUTSIDE, x=20,y=10)
More labelframes are there having same procedure with different y coordinate only are there but has same procedure so skipping to last lines.
self.window.update()
self.canvas.configure(scrollregion=self.canvas.bbox(ALL))
self.window.mainloop()
What changes should be done?

Related

Label text isn't appearing in tkinter Frame

I've been researching this question, but none of the solutions I've found seem to work. I'm trying to get a Label (self.status_bar in the code below) to appear in my frame, but any edits (i.e. using update() method or resizing the frame/text widget/window) I've made have gotten me nowhere.
from tkinter import *
from tkinter import filedialog
from tkinter import font
#Build frame with features to put into parent window
class TextEditor:
def __init__(self, master):
self.master = master
self.frame = tk.Frame(self.master)
#Create Scrollbar
self.text_scroll = tk.Scrollbar(self.frame)
self.text_scroll.pack(side=RIGHT, fill=Y)
#Create text box
self.text = tk.Text(self.frame, width=155, height=55, font=('Helvetica', 12), selectbackground="yellow",
selectforeground = "black", undo=True, yscrollcommand=self.text_scroll.set)
self.text.pack()
#Configure scrollbar
self.text_scroll.config(command=self.text.yview)
#Create menu
self.menu = tk.Menu(self.master)
self.master.config(menu=self.menu)
#Add file menu
self.file_menu = tk.Menu(self.menu, tearoff=False)
self.menu.add_cascade(label="File", menu=self.file_menu)
self.file_menu.add_command(label="Open")
self.file_menu.add_command(label="Save")
self.file_menu.add_command(label="New")
self.file_menu.add_separator()
self.file_menu.add_command(label="Exit", command=self.master.destroy)
#Add edit menu
self.edit_menu = tk.Menu(self.menu, tearoff=False)
self.menu.add_cascade(label="Edit", menu=self.edit_menu)
self.edit_menu.add_command(label="Cut")
self.edit_menu.add_command(label="Copy")
self.edit_menu.add_command(label="Undo")
self.edit_menu.add_command(label="Redo")
#Add status bar to bottom of app
self.status_bar = tk.Label(self.frame, text="Ready", anchor=E)
self.status_bar.pack(fill=X, side=BOTTOM, ipady=5)
#Pack frame into window
self.frame.pack()
#Instantiates the text editor app
def main():
root = tk.Tk()
app = TextEditor(root)
root.geometry("1220x660")
root.title("Text Editor")
root.mainloop()
if __name__ == '__main__':
main()
You are forcing the window to a size that is too small to fit all of the widgets. When you do that while using pack, pack will start to shrink widgets in order to make them fit, starting with the last widget that was packed. In this case that's the status bar. So, pack starts removing pixels from self.status_bar until there's enough room for the other widgets. Eventually, it has to completely remove the status bar, and then start shrinking the text widget.
The first step is to create the status bar first, so that the text widget is higher in the stacking order (ie: pack will try to shrink it before shrinking other widgets).
The second thing you should do is use the appropriate options to get the TextEditor window to fill the frame, and get the frame to fill the window. For example:
self.text.pack(fill="both", expand=True)
self.frame.pack(fill="both", expand=True)
I suggest, with the more complicated layout you have, that you use the grid method instead of pack. Here is the code with the widgets gridded instead of packed:
import tkinter as tk
from tkinter import filedialog
from tkinter import font
from tkinter.constants import *
#Build frame with features to put into parent window
class TextEditor:
def __init__(self, master):
self.master = master
self.frame = tk.Frame(self.master)
#Create Scrollbar
self.text_scroll = tk.Scrollbar(self.frame)
self.text_scroll.grid(row=0, column=1, sticky=E+NS) ### EDITED THIS LINE
#Create text box
self.text = tk.Text(self.frame, font=('Helvetica', 12), selectbackground="yellow", ### EDITED THIS LINE
selectforeground = "black", undo=True, yscrollcommand=self.text_scroll.set)
self.text.grid(row=0, column=0, sticky=NSEW) ### EDITED THIS LINE
#Configure scrollbar
self.text_scroll.config(command=self.text.yview)
#Create menu
self.menu = tk.Menu(self.master)
self.master.config(menu=self.menu)
#Add file menu
self.file_menu = tk.Menu(self.menu, tearoff=False)
self.menu.add_cascade(label="File", menu=self.file_menu)
self.file_menu.add_command(label="Open")
self.file_menu.add_command(label="Save")
self.file_menu.add_command(label="New")
self.file_menu.add_separator()
self.file_menu.add_command(label="Exit", command=self.master.destroy)
#Add edit menu
self.edit_menu = tk.Menu(self.menu, tearoff=False)
self.menu.add_cascade(label="Edit", menu=self.edit_menu)
self.edit_menu.add_command(label="Cut")
self.edit_menu.add_command(label="Copy")
self.edit_menu.add_command(label="Undo")
self.edit_menu.add_command(label="Redo")
#Add status bar to bottom of app
self.status_bar = tk.Label(self.frame, text="Ready", anchor=E)
self.status_bar.grid(row=1, column=0, sticky=S+EW) ### EDITED THIS LINE
# Configure the rows and columns so that they expand properly ### ADDED THESE LINES
self.frame.rowconfigure(0, weight=1) ### ADDED THESE LINES
self.frame.columnconfigure(0, weight=1) ### ADDED THESE LINES
#Pack frame into window
self.frame.pack(expand=YES, fill=BOTH) ### EDITED THIS LINE
#Instantiates the text editor app
def main():
root = tk.Tk()
app = TextEditor(root)
root.geometry("1220x660")
root.title("Text Editor")
root.mainloop()
if __name__ == '__main__':
main()
Notice how I also changed the line where the frame is packed into the window. The only thing keeping the frame filling the window before was the size of the text widget.
With these changes, the widgets expand properly, so I also removed the width and height parameters from the creation of self.text.

Frame not expanding

Maybe this is just how Tkinter works but Im unsure. Currently I have the Main window with three frames laid out next to each other. Frame ContainerFrame is a master, then characterFrame and planetFrame are placed inside the ContainerFrame. The issue is or what I would like to happen is that the frames would fill up a set area of the window regardless of whether or not their is data/ widgets in them.
Here is what I envision it to look like https://imgur.com/OjdKFh4
from tkinter import *
from tkinter import ttk
MainWindow = Tk()
def mainWindow():
MainWindow.option_add('*tearOff', False)
MainWindow.title("Interface")
MainWindow.geometry('800x600')
menubar = Menu(MainWindow)
MainWindow.config(menu = menubar)
File = Menu(menubar)
About = Menu(menubar)
menubar.add_cascade(menu = File, label = "File")
menubar.add_cascade(menu = About, label = "About")
def frameContainer():
containerFrame = Frame(MainWindow)
containerFrame.pack(anchor = "nw", side = "left", fill = "both", expand = False)
scroller = Scrollbar(orient = "vertical")
characterFrame = Frame(containerFrame, borderwidth="2", relief="sunken")
characterFrame.pack(anchor = "nw", side = "left", expand = True)
planetFrame = Frame(containerFrame ,borderwidth="2", relief="sunken")
planetFrame.pack(anchor = "nw", side = "right", expand = True)
scroller = Scrollbar(orient = "vertical")
scroller.pack(anchor = "e", side = "right", fill = "y", expand = False)
characterLable = Button(characterFrame, text ="Characters")
characterLable.pack()
Label(characterFrame, text ="Test1").pack()
Label(characterFrame, text ="Test2").pack()
Label(planetFrame, text ="Test1").pack()
Label(planetFrame, text ="Test2").pack()
mainWindow()
frameContainer()
MainWindow.mainloop()
Normally you would use a frame to organize a widget with a scrollbar, but a frame is not scrollable. If you want to scroll an area containing other widgets the usual thing to do is to use a canvas.
Study this guide: Tkinter Scrollbar Patterns
Pack can be difficult to use and the only way I have found to overcome this is to keep trying. It's usually easier to see what you are doing if you let the different frames have different bg colors. Also I've taken the liberty to change some of your variable names as they do not give a hint as to what they are or are too similar to other names, eg. mainWindow and MainWindow.
I have added some padding to some widgets to make it look better.
from tkinter import *
root = Tk()
def create_main_window():
root.option_add('*tearOff', False)
root.title("Interface")
root.geometry('400x300+800+50')
menubar = Menu(root)
root.config(menu = menubar)
File = Menu(menubar)
About = Menu(menubar)
menubar.add_cascade(menu = File, label = "File")
menubar.add_cascade(menu = About, label = "About")
def create_container_frame():
container = Frame(root, bg='tan')
container.pack(fill="both", expand=True)
scroller = Scrollbar(container, orient="vertical")
scroller.pack(side="right", fill="y")
characterFrame = Frame(container, bd=2, relief="sunken", bg='thistle')
characterFrame.pack(side="left", fill='y', padx=(10,0), pady=10)
character_button = Button(characterFrame, text ="Characters")
character_button.pack(padx=10, pady=(10,0))
Label(characterFrame, text ="Test1").pack()
Label(characterFrame, text ="Test2").pack()
planetFrame = Frame(container ,bd=2, relief="sunken", bg='khaki')
planetFrame.pack(side="left", fill='both', expand=True, padx=10, pady=10)
Label(planetFrame, text="Test1").pack(pady=(10,0))
Label(planetFrame, text="Test2").pack()
create_main_window()
create_container_frame()
root.mainloop()
Is this the layout you are aiming for?

Tkinter bind callback to activating a window

I'm writing a Tkinter application in Python 3 and I've created a custom Title Bar (the bar at the top of every Windows application with the application name, close button etc.). The way I achieved this was to create an invisible window (we'll call this window test) that controls the main application window's behavior (which we'll call Main App). Below is a piece of test code that illustrates this:
from tkinter import Tk, Toplevel
from tkinter.ttk import Button, Label, Frame, Style
class NewRoot(Tk):
def __init__(self):
Tk.__init__(self)
self.attributes('-alpha', 1.0) # This is normally set to 0.0
class MyMain(Toplevel):
def __init__(self, master):
Toplevel.__init__(self, master)
self.overrideredirect(1)
self.geometry('750x650+400+600')
self.style = Style()
self.style.configure('TTitleBar.Label',
width=8,
relief='flat',
foreground='red',
background='red',
anchor='center',
borderwidth=-1)
self.style.configure('TMainWindow.Label',
width=8,
relief='flat',
background='blue',
anchor='center',
borderwidth=-1)
self.tk_setPalette(background='green')
self.x_win = None
self.y_win = None
self.start_x = None
self.start_y = None
# make a frame for the title bar
title_bar = Frame(self, style='TTitleBar.Label')
title_bar.grid(row=0, column=0, sticky='wn')
label = Label(title_bar, text='Main App', style='TMainWindow.Label')
label.grid(row=0, column=0, padx=(4, 2), pady=(4, 0), sticky='nw')
minimize_button = Button(title_bar, text='MIN', command=self.minimize_window,
style='TMainWindow.Label', takefocus=False)
minimize_button.grid(row=0, column=2, padx=(563.5, 0), sticky='nw')
maximise_button = Button(title_bar, text='MAX', command=self.maximize_window,
style='TMainWindow.Label', takefocus=False)
maximise_button.grid(row=0, column=3, pady=(1.4, 0), sticky='nw')
close_button = Button(title_bar, text='CLOSE', command=self.close_window,
style='TMainWindow.Label', takefocus=False)
close_button.grid(row=0, column=4, sticky='nw')
window = Frame(self)
window.grid(row=1, column=0, sticky='ne')
# bind title bar motion to the move window function
title_bar.bind('<B1-Motion>', self.move_window)
title_bar.bind('<Button-1>', self.get_pos)
self.master.bind("<Map>", self.on_root_deiconify)
self.master.bind("<Unmap>", self.on_root_iconify)
self.mainloop()
def minimize_window(self):
self.master.iconify()
def maximize_window(self):
pass
def close_window(self):
self.master.destroy()
def on_root_iconify(self, event):
# print('unmap')
self.withdraw()
def on_root_deiconify(self, event):
# print('map')
self.deiconify()
def get_pos(self, event):
self.x_win = self.winfo_x()
self.y_win = self.winfo_y()
self.start_x = event.x_root
self.start_y = event.y_root
self.y_win = self.y_win - self.start_y
self.x_win = self.x_win - self.start_x
def move_window(self, event):
# print('+{0}+{1}'.format(event.x_root, event.y_root))
self.geometry('+{0}+{1}'.format(event.x_root + self.x_win, event.y_root + self.y_win))
self.start_x = event.x_root
self.start_y = event.y_root
if __name__ == '__main__':
root = NewRoot()
root.title('test')
app = MyMain(root)
In the code above, whenever the test window is minimized, the Main App window is also minimized, which works as intended.
The problem is that whenever the test window is made active, the Main App window doesn't become active also. For example, if another app covers Main App but test is not minimized, I need to click on the test icon in the Windows Task Bar three times for it to appear.
I was wondering if there is a way to fix this using something like:
self.master.bind(<some_command>, self.some_other_command)
However, I can't find a comprehensive list of bind commands anywhere.
Is this a good way of going about this, or is there something else I should be doing?
Also, I noticed that using self.overrideredirect(1) causes the shadows made by the windows to disappear, which causes overlapping windows in my application to 'merge together', since the background colors are the same. Is there a way to add the shadows back?
Thank you in advance.
I found a solution to this for anyone else with a similar problem. You can create a 'dummy' button in the invisible window that will become active when the window is in focus. You then have that call a function that places the main application window in focus.
class NewRoot(Tk):
def __init__(self):
Tk.__init__(self)
self.attributes('-alpha', 0.0)
entry = Button()
entry.pack()
entry.focus_set()
entry.pack_forget()
Then add this to __init__ in the MyMain class:
self.master.bind('<FocusIn>', self.on_root_deiconify)

tkinter image button not working inside a function [duplicate]

I am trying to place two image buttons on my image background in a certain position, but my buttons are not appearing. I think their images are behind the background.
I tried to use place and pack, both did not work. What could be the problem?
from tkinter import*
import tkinter as tk
import settings
class Application(Frame):
def __init__ (self, master):
Frame.__init__(self,master)
self.grid()
self.create_widgets()
def create_widgets(self):
button1 = PhotoImage(file ="button1.gif")
button2 = PhotoImage(file ="button2.gif")
settings_button = Button(self, image = button1,
command = self.mult_command, width = 15)
settings_button.place(x=1, y=1)
rules_button = Button(self, image = button2,
command = self.the_rules, width = 15)
rules_button.place(x=50, y=50)
def main_code():
window = Tk()
window.title("The Bouncer")
bg_image = PhotoImage(file ="pic.gif")
x = Label (image = bg_image)
x.image = bg_image
x.place(x = 0, y = 0, relwidth=1, relheight=1)
window.geometry("600x300")
app = Application(window)
window.mainloop()
main_code()
thanks
It is likely that your image is being garbage collected before it is displayed. This is a common Tkinter gotcha. Try changing the lines:
button1 = PhotoImage(file ="button1.gif")
button2 = PhotoImage(file ="button2.gif")
to
self.button1 = PhotoImage(file ="button1.gif")
self.button2 = PhotoImage(file ="button2.gif")
and use
settings_button = Button(self, image = self.button1, command = self.mult_command, width = 15)
etc.
This should keep a reference to your image, stopping it from getting garbage collected.
In addition to keeping a reference to the image, you have a problem with this line:
self.grid()
in the __init__ method of Application. It's gridding the Frame into the window, but since nothing is ever packed or gridded into the frame, it doesn't ever expand past a little, tiny frame, so you just don't see the Buttons inside it. A simple fix here would be the pack method, with arguments to fill the window and expand when needed:
self.pack(fill=BOTH, expand=1)

Why my image buttons are not appearing?

I am trying to place two image buttons on my image background in a certain position, but my buttons are not appearing. I think their images are behind the background.
I tried to use place and pack, both did not work. What could be the problem?
from tkinter import*
import tkinter as tk
import settings
class Application(Frame):
def __init__ (self, master):
Frame.__init__(self,master)
self.grid()
self.create_widgets()
def create_widgets(self):
button1 = PhotoImage(file ="button1.gif")
button2 = PhotoImage(file ="button2.gif")
settings_button = Button(self, image = button1,
command = self.mult_command, width = 15)
settings_button.place(x=1, y=1)
rules_button = Button(self, image = button2,
command = self.the_rules, width = 15)
rules_button.place(x=50, y=50)
def main_code():
window = Tk()
window.title("The Bouncer")
bg_image = PhotoImage(file ="pic.gif")
x = Label (image = bg_image)
x.image = bg_image
x.place(x = 0, y = 0, relwidth=1, relheight=1)
window.geometry("600x300")
app = Application(window)
window.mainloop()
main_code()
thanks
It is likely that your image is being garbage collected before it is displayed. This is a common Tkinter gotcha. Try changing the lines:
button1 = PhotoImage(file ="button1.gif")
button2 = PhotoImage(file ="button2.gif")
to
self.button1 = PhotoImage(file ="button1.gif")
self.button2 = PhotoImage(file ="button2.gif")
and use
settings_button = Button(self, image = self.button1, command = self.mult_command, width = 15)
etc.
This should keep a reference to your image, stopping it from getting garbage collected.
In addition to keeping a reference to the image, you have a problem with this line:
self.grid()
in the __init__ method of Application. It's gridding the Frame into the window, but since nothing is ever packed or gridded into the frame, it doesn't ever expand past a little, tiny frame, so you just don't see the Buttons inside it. A simple fix here would be the pack method, with arguments to fill the window and expand when needed:
self.pack(fill=BOTH, expand=1)

Categories