Python Tkinter - multiple listboxes and scrollbars - python

having issues with getting the scrollbars set correctly. The following code renders 2 listboxes, one on top of the other, and 2 scrollbars. However, scrollbar spans the entire height of both boxes, and scrollbar2 is only the 2nd listbox (lb2). I need scrollbar and lb to be the same height, and scrollbar2 and lb2 to be the same height. Here is a screenshot of what I currently have http://tinyurl.com/mxo9llb
frame = Frame(app,bd=2,relief=SUNKEN)
scrollbar = Scrollbar(frame, orient="vertical")
lb = Listbox(frame, width=30, height=10, yscrollcommand=scrollbar.set)
scrollbar.config(command=lb.yview)
scrollbar.pack(side="right", fill="y")
lb.pack(side="top",fill="both", expand=True)
scrollbar2 = Scrollbar(frame, orient="vertical")
lb2 = Listbox(frame, width=30, height=10, yscrollcommand=scrollbar2.set)
scrollbar2.config(command=lb2.yview)
scrollbar2.pack(side="right",fill="y")
lb2.pack(side="top",fill="both", expand=True)
for item in ad_members:
lb.insert(END, item)
for item in ad_members:
lb2.insert(END, item)
frame.pack(side='right',padx=15)

For such a layout, you're better off using grid rather than pack. You can't do what you want using pack unless you add some extra frames to help with the layout.
Using pack, you need to create two additional frames: one for the top half and one for the bottom half. Then, within the frame you can put each scrollbar on the right and the listbox on the left. Finally, pack the frames on top of each other.
Using grid, you would put each listbox/scrollbar pair on a different row, with the listbox in column zero and the scrollbar in column one.

Related

Python Tkinter Scrollbar panning upwards instead of downwards also alignment issue while using Scrollbar

I am using TKinter to build a GUI for my python application.
I want to add a scrollbar to the main screen because I have a lot of data to display but very less space.
Currently I use the lines:
root=Tk()
root.state("zoomed")
#...rest of the code... (Relevant parts mentioned below:)
root.mainloop()
to take maximum advantage of available screen space but even that is not cutting it for me.
Here is John Elder(CEO of Codemy.com)'s code that I use to add a scrollbar:
# Create A Main Frame
main_frame = Frame(root)
main_frame.pack(fill=BOTH, expand=1)
# Create A Canvas
my_canvas = Canvas(main_frame)
my_canvas.pack(side=LEFT, fill=BOTH, expand=1)
# Add A Scrollbar To The Canvas
my_scrollbar = ttk.Scrollbar(main_frame, orient=VERTICAL, command=my_canvas.yview)
my_scrollbar.pack(side=RIGHT, fill=Y)
# Configure The Canvas
my_canvas.configure(yscrollcommand=my_scrollbar.set)
my_canvas.bind('<Configure>', lambda e: my_canvas.configure(scrollregion = my_canvas.bbox("all")))
def _on_mouse_wheel(event):
my_canvas.yview_scroll(-1 * int((event.delta / 100)), "units")
my_canvas.bind_all("<MouseWheel>", _on_mouse_wheel)
# Create ANOTHER Frame INSIDE the Canvas
encompasser = Frame(my_canvas, bg=mainbg)
# Add that New frame To a Window In The Canvas
my_canvas.create_window((0,0), window=encompasser, anchor="n")
#Adding the scroll bar part ends here...
encompasser=Frame(my_canvas,bg=mainbg, borderwidth=0)
encompasser.pack(fill=BOTH, expand=1)
#second_frame = LabelFrame(my_canvas, borderwidth=0, bg=mainbg).pack()
header=LabelFrame(encompasser,bg=mainbg, borderwidth=0)
header.pack()
greet=Label(header,font="Arial 20",text="Prescription Generator V4.0.0", bg=mainbg, fg="black").pack(pady=10)
utilitiesF=LabelFrame(header,bg=mainbg, borderwidth=0)
utilitiesF.pack()
settingLF=LabelFrame(utilitiesF,bg=mainbg, borderwidth=0)
megaF=LabelFrame(encompasser,bg=mainbg, borderwidth=0)
megaF.pack()
dateF=LabelFrame(utilitiesF, padx=10, pady=10, bg=mainbg, borderwidth=0)
dateF.grid(row=0, column=1)
date=Label(dateF,font="Arial 14",text="Date:", bg=mainbg).grid(row=0,column=0, sticky=E)
edate=Entry(dateF, width=9,borderwidth=0, font="Arial 14")
edate.insert(0,today)
edate.grid(row=0, column=1, padx=5)
my_tree_frame=LabelFrame(header, padx=10, pady=10, bg=subbg, borderwidth=0)
my_tree_frame.pack()
Dormant scrollbar added
After Adding the scroll bar to the canvas and opening the canvas in a frame, I add things to the frame called "encompasser".
Problem: when I click on the button: Show Database, The following Treeview opens up:
Treeview opens up, moving the rest of my project down
And when the rest of my project moves down, the scroll bar just does not scroll down.
Scroll Bar stuck and not working on moving it up or down
What am I doing wrong here?
I know one solution is to open the treeview widget as a Toplevel() but I want it to be opened within the same page. Please answer my question, thank you!
One more thing: the entire alignment gets messed up when I try to pack that frame which comprises of all my widgets inside the encompasser. The whole project shifts towards the left hand side and aesthetically it does not look nice.
Showing what happened when I removed the frame called "encompasser"
First, you have created another frame using the same variable name encompasser:
encompasser=Frame(my_canvas,bg=mainbg, borderwidth=0)
encompasser.pack(fill=BOTH, expand=1)
The above two lines should be removed.
Second, you should update the scrollregion when the internal frame encompasser is resized, not my_canvas.
Remove the line:
my_canvas.bind('<Configure>', lambda e: my_canvas.configure(scrollregion = my_canvas.bbox("all")))
and add the below line after creating encompasser:
encompasser.bind('<Configure>', lambda e: my_canvas.configure(scrollregion=my_canvas.bbox("all")))

Repeated replacement of a widget with a widget of equal size - how its done?

I'm currently working on a tkinter based python gui and in my program you are able to press a button and then the first widget is getting invisible and the other one ist displayed one the same position of the first one. After that you are able to switch back and the second widget disappears and the first one will be at the same position visible and so on...
My goal is that the two widgets get the same size if you are switching the wdigets and the user will not see any bigger/smaller widgets, but I'm not able to say how I can get there - may you can help me?
My tkinter window:
self.window = tkinter.Tk()
self.window.resizable(True, True)
self.window.grid_rowconfigure(2, weight=1)
self.window.grid_columnconfigure(0, weight=2)
self.window.grid_columnconfigure(1, weight=1)
My frame for the two widgets:
contentFrame = tkinter.Frame(master=self.window, bg="#FFF", highlightthickness=0, borderwidth=0)
contentFrame.grid_rowconfigure(0, weight=1)
contentFrame.grid_columnconfigure(0, weight=1)
contentFrame.grid(column=0, row=2, padx=5, pady=5, sticky="nsew")
My first widget (red one):
canvas = tkinter.Canvas(master=contentFrame, bg="#F00", highlightthickness=0, borderwidth=0)
canvas.pack(fill="both", expand=True)
My second widget (blue one)
xmlEditor = tkinter.Text(master=contentFrame, bg="#00F", highlightthickness=0, borderwidth=0)
And whats happening when change from red to blue one:
self.window.children["!frame2"].children["!canvas"].pack_forget()
self.window.children["!frame2"].children["!text"].pack(fill="both", expand=True)
And whats happening when change from blue to red one:
self.window.children["!frame2"].children["!text"].pack_forget()
self.window.children["!frame2"].children["!canvas"].pack(fill="both", expand=True)
Look at the video here to see the problem in action :)
What I would do is use an intermediate frame which controls the size. Then, you can pack either the red or blue frame inside this frame.
Start by creating a subframe with the size you want. Lets say you want the area to be 500x500:
subframe = tk.Frame(contentFrame, width=500, height=500)
subframe.pack(fill="both", expand=True)
Next, turn geometry propagation off so that the frame won't grow or shrink to fit its children:
subframe.pack_propagate(False)
Finally, put your red and blue frames inside this subframe:
canvas = tkinter.Canvas(master=subframe, bg="#F00", highlightthickness=0, borderwidth=0)
xmlEditor = tkinter.Text(master=subframe, bg="#00F", highlightthickness=0, borderwidth=0)

Avoid the status bar (footer) from disappearing in a GUI when reducing the size of the screen

I have developed a quite large GUI using tkinter.
Everything is working great when resizing, except for the case of reducing a lot the height of the window. All the frames are placed using 'pack()'.
The basic vertical structure of the GUI is (ordered from top to bottom):
Toolbar frame
self.frame_Toolbar=Frame(self.root, bg=colorBackground)
self.frame_Toolbar.pack(side=TOP, fill=X)
Middle frame containing all the basic information
self.frame_Middle=Frame(self.root)
self.frame_Middle.pack(side=TOP, fill=BOTH, expand=YES)
Status bar
self.frame_Status=Frame(self.root, bg=colorStatus, bd=1, relief=SUNKEN)
self.frame_Status.pack(side=BOTTOM, fill=X)
The middle frame is enlarged if the window is resized, which is great since it is what I want.
The issue is that when reducing a lot the height of the window, the status bar disappears because the middle frame adapts to the internal widgets. However, I would like that both the status bar and toolbar would be maintained and the middle frame would reduce as much as needed even if hiding some widgets.
To sum up, I would like that both the toolbar and status bar have like a minimum height.
Is that possible?
Edit: I add a functional MCVE code with the same error
# Import graphical interface
from tkinter import *
# Fixed window
root_fw=Tk()
# Toolbar
frame_Toolbar=Frame(root_fw, bg='red', height=50)
frame_Toolbar.pack(side=TOP, fill=X)
# Middle
frame_Middle=Frame(root_fw, bg='blue', height=300)
frame_Middle.pack(side=TOP, fill=BOTH, expand=YES)
# Status bar
frame_Status=Frame(root_fw, bg='green', bd=1, relief=SUNKEN, height=20)
frame_Status.pack(side=BOTTOM, fill=X)
root_fw.mainloop()
Once all of the widgets are at their minimum size, when you shrink the window tkinter must start reducing the size of one or more widgets. It does this in the order that widgets appear in the stacking order. Simply put, it starts removing space from the last widget that was packed.
So, the short answer is to pack the statusbar before you pack the middle widget. Generally speaking, a GUI will have one "hero" widget that takes up most of the space; it is this window that should be packed last so that the other widgets will not be clipped when the window is resized.
My recommendation is to always separate widget creation from widget layout, and to group layout together. I find this practice makes layout problems much easier to solve. I would therefore modify your code to look like this:
from tkinter import *
root_fw=Tk()
frame_Toolbar=Frame(root_fw, bg='red', height=50)
frame_Middle=Frame(root_fw, bg='blue', height=300)
frame_Status=Frame(root_fw, bg='green', bd=1, relief=SUNKEN, height=20)
frame_Toolbar.pack(side=TOP, fill=X)
frame_Status.pack(side=BOTTOM, fill=X)
frame_Middle.pack(side=TOP, fill=BOTH, expand=YES)
root_fw.mainloop()
I suggest you to switch to the Grid layout because you can configure rows and columns. The minimum height of a row can be set with the minsize option of the rowconfigure method.
In addition, you need to configure the middle row to resize with the window by setting its weight to 1 and finally you can set a minimum size for the widow to be at least big enough to show both bars.
from tkinter import *
# Fixed window
root_fw = Tk()
# resize row 1 and column 0 with window
root_fw.rowconfigure(1, weight=1)
root_fw.columnconfigure(0, weight=1)
# set minimum height for row 0 and 2
root_fw.rowconfigure(0, minsize=50)
root_fw.rowconfigure(2, minsize=20)
# set window min size
root_fw.minsize(70, 70)
# Toolbar
frame_Toolbar = Frame(root_fw, bg='red', height=50, width=200)
frame_Toolbar.grid(row=0, sticky="ew")
# Middle
frame_Middle = Frame(root_fw, bg='blue', height=300, width=200)
frame_Middle.grid(row=1, sticky="ewsn")
# Status bar
frame_Status = Frame(root_fw, bg='green', bd=1, relief="sunken", height=20, width=200)
frame_Status.grid(row=2, sticky="ew")
root_fw.mainloop()

tkinter put scrollbar on canvas at bottom position

Question on scrollbar positions on my tkinter canvas. I have a frame with 3 canvas widgets. Courtesy to this post for the idea. I added a horizontal scrollbar and each canvas has a 50+ column 500+ row pandas dataframe. The load is not very fast but that isn't an objective.
New rows will be added to the bottom of each dataframe. This new row needs a validation. So instead of scrolling down every time, it would be great if the scrollbar / or canvas shows the bottom part.
See below the code where the 3x canvas and 3x scrollbars (x+y) are defined.
def createBox(window):
list_ = ['df1', 'df2', 'df3'] # 3 dataframes
for i in range(3):
mybox = LabelFrame(window, padx=5, pady=4)
mybox.grid(row=i, column=0)
createWindow(mybox, list_[i], i)
def createWindow(box, lt_actual, i):
canvas = Canvas(box, borderwidth=0)
frame = Frame(canvas)
vsbY = Scrollbar(box, orient="vertical", command=canvas.yview)
canvas.configure(yscrollcommand=vsbY.set, width=1200, heigh=200)
vsbY.pack(side="right", fill="y")
vsbX = Scrollbar(box, orient="horizontal", command=canvas.xview)
canvas.configure(xscrollcommand=vsbX.set, width=1200, heigh=200)
vsbX.pack(side="bottom", fill="x")
#canvas.yview_moveto(1) - no effect
#canvas.yview_moveto(1.0) - no effect
canvas.pack(side="left", fill="both", expand=True)
canvas.create_window((4,4), window=frame, anchor="nw", tags="frame")
# be sure that we call OnFrameConfigure on the right canvas
frame.bind("<Configure>", lambda event, canvas=canvas: OnFrameConfigure(canvas))
I read on this forum and on some info (effbot) pages that i should use the moveto() / yview_moveto() command option but so far this doesn't seem to work.
Question 1. Should I put the y-scrollbar to the bottom or should i put the canvas view to the bottom.
Question 2. Can you provide some guidance on how to use the moveto or should I follow a different approach?
Thanks so much!
The yview_moveto method of the canvas is indeed the right function to use. Its argument is the fraction of the total height of the canvas that you want off-screen. So using 0 as argument shows the top of the canvas and 1, the bottom.
Reference:
Tkinter.Canvas.yview_moveto-method
Adjusts the canvas to the given Scroll offset.
Offset '0.0' is the beginning of the scrollregion, '1.0' the end.
import tkinter as tk
root = tk.Tk()
canvas = tk.Canvas(root)
canvas.grid(row=0, column=0)
canvas.create_oval(0, 0, 20, 20, fill='red')
canvas.create_oval(0, 800, 20, 820, fill='blue')
ys = tk.Scrollbar(root, orient='vertical', command=canvas.yview)
ys.grid(row=0, column=1, sticky='ns')
# configure scrolling
canvas.configure(yscrollcommand=ys.set, scrollregion=canvas.bbox('all'))
# show bottom of canvas
canvas.yview_moveto('1.0')
root.mainloop()
By the way, I don't see any difference between putting the y-scrollbar to the bottom or putting the canvas view to the bottom because the two are linked. But I guessed you wanted to know whether to do it using a method of the scrollbar or of the canvas, and I gave the answer above.
I have found that it is necessary to use idle_tasks before moving the scroll tab:
self.canvas.update_idletasks()
self.canvas.yview_moveto(0)

table display in tkinter with scroll bars

I'm writing a small GUI in python3/tkinter. What I want to do is generate a window with a table of data (like a spreadsheet) and have the table be scrollable both horizontally and vertically. Right now I'm just trying to display data, so I'm using a grid of Labels. The data display works fine but I can't get the scroll bars to act correctly. Here is the relevant part of my code; the class that this is in inherits from tk.Toplevel
frame = self.frame = tk.Frame(self)
self.frame.grid(row=1, columnspan=2, padx=2, pady=2, sticky=tk.N+tk.E+tk.S+tk.W)
self.text_area = tk.Canvas(self.frame, background="black", width=400, height=500, scrollregion=(0,0,1200,800))
self.hscroll = tk.Scrollbar(self.frame, orient=tk.HORIZONTAL, command=self.text_area.xview)
self.vscroll = tk.Scrollbar(self.frame, orient=tk.VERTICAL, command=self.text_area.yview)
self.text_area['xscrollcommand'] = self.hscroll.set
self.text_area['yscrollcommand'] = self.vscroll.set
self.text_area.grid(row=0, column=0, sticky=tk.N+tk.S+tk.E+tk.W)
self.hscroll.grid(row=1, column=0, sticky=tk.E+tk.W)
self.vscroll.grid(row=0, column=1, sticky=tk.N+tk.S)
self._widgets = []
for row in range(rows):
current_row = []
for column in range(columns):
label = tk.Label(self.text_area, text="",
borderwidth=0, width=width)
label.grid(row=row, column=column, sticky="nsew", padx=1, pady=1)
current_row.append(label)
self._widgets.append(current_row)
The table displays OK and the scrollbars appear but are not functional:
Any ideas?
You have a couple of problems. First, you can't use grid to place labels in the canvas and expect them to scroll. When you scroll a canvas, only the widgets added with create_window will scroll. However, you can use grid to put the labels in a frame, and then use create_window to add the frame to the canvas. There are several examples of that technique on this site.
Second, you need to tell the canvas how much of the data in the canvas should be scrollable. You use this by setting the scrollregion attribute of the canvas. There is a method, bbox which can give you a bounding box of all of the data in the canvas. Usually it's used like this:
canvas.configure(scrollregion=canvas.bbox("all"))
An option I have used is the leave the labels static and changed the data when the scrollbar is used. This is easy for vertical scrolling but if the columns are of different widths does not work for horizontal scrolling - it would require resizing the labels.

Categories

Resources