Display thousands of records in CSV data in Tkinter - python

I want to load the data from a CSV file which have around 20,000 records and want to display it in tkinter in a scrollable table format. I used canvas, labels, grids, and scrollbar to achieve this. However, it can load only around 100-300 records in Tkinter without hanging. What could be the possible reason for this? I find that parsing through CSV is not the culprit. Is it the canvas and scrollable area? Can Tkinter show huge data as tables?
class Table(Frame):
def __init__(self, parent, csv_data, start=0):
Frame.__init__(self, parent, background="#EEEEEE", height=400)
self._widgets = []
self._canvas = Canvas(self, width=1300, height=500)
self._canvas.grid(row=0, column=0, sticky="news") # changed
self._vertical_bar = Scrollbar(self, orient="vertical", command=self._canvas.yview)
self._vertical_bar.grid(row=0, column=1, sticky="ns")
self._canvas.configure(yscrollcommand=self._vertical_bar.set)
self._horizontal_bar = Scrollbar(self, orient="horizontal", command=self._canvas.xview)
self._horizontal_bar.grid(row=1, column=0, sticky="we")
self._canvas.configure(xscrollcommand=self._horizontal_bar.set)
self.frame = Frame(self._canvas)
self._canvas.create_window((0, 0), window=self.frame, anchor="nw")
self.frame.bind("<Configure>", self.resize)
lineNum = start-1;
for row in csv_data:
print(lineNum)
lineNum += 1;
if lineNum < start:
continue;
if lineNum == start+200:
break;
current_row = []
#index
for column in range(len(row)+1):
v = StringVar()
label = Label(self.frame, textvariable=v)
label.config(font=("Calibri", 10), background="white",highlightcolor='white', highlightbackground='white', borderwidth=0)
label.grid(row=lineNum, column=column+1, sticky="nsew", padx=1, pady=1)
current_row.append(label)
if column == 0:
v.set(lineNum)
else:
v.set(row[column-1])
self._widgets.append(current_row)
for column in range(lineNum):
self.grid_columnconfigure(column, weight=1)
def resize(self, event=None):
self._canvas.configure(scrollregion=self._canvas.bbox("all"))
#self._canvas.itemconfig(self.frame, height=500, width=1300)
#self.__canvas.itemconfigure("self.frame", width=1300)
def set(self, row, column, value):
widget = self._widgets[row][column]
widget.configure(text=value)

Related

How do I create a scrollbar when using a grid in tkinter?

I'm a little bit stuck on this problem regarding my program. I tried adding as many comments as possible to give a sense of what everything does in the code, but essentially. The program has a field and value entry box. When the "add field/value button" is clicked, more of the entry widgets are added. If this keeps occurring then obviously it'll go off screen. So I've limited the size of the application, but the problem then is I need a scrollbar. I've tried looking it up, but my frame uses grid, and everywhere they use pack which isn't compatible in this case. I get the scrollbar to appear, however it doesn't seem to work. I've seen some people use canvas, and more than one frame, etc. I'm missing something important but I don't know how do the exact same thing with a grid. Think you experts can lend me hand to get it working?
from tkinter import *
import tkinter as tk
class Insert(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
container = tk.Frame(self)
container.grid_columnconfigure(0, weight=1)
container.grid_rowconfigure(0, weight=1)
container.pack(side="top", fill="both", expand=True)
self.frameslist = {}
for frame in (Create,):
frame_occurrence = frame.__name__
active_frame = frame(parent=container, controller=self)
self.frameslist[frame_occurrence] = active_frame
active_frame.grid(row=0, column=0, sticky="snew")
self.show_frame("Create")
def show_frame(self, frame_occurrence):
active_frame = self.frameslist[frame_occurrence]
active_frame.tkraise()
class Create(tk.Frame):
def __init__(self, parent, controller):
tk.Frame.__init__(self, parent)
self.controller = controller
#For all widgets (nested list, 2 widgets per row)
self.inputlist = []
#For just the entries
self.newinputlist = []
#Create two labels, add them into the inputlist to be iterated
labels = [tk.Label(self, text="Field"), tk.Label(self, text="Values")]
self.inputlist.append(labels[:])
#Insert the labels from the list
for toplabels in range(1):
self.inputlist[toplabels][0].grid(row=toplabels, column=0, padx=10, pady=5)
self.inputlist[toplabels][1].grid(row=toplabels, column=1, padx=10, pady=5)
#Create the first two entry boxes, append them to the inputlist, and newinput list
first_entries = [tk.Entry(self, borderwidth=5), tk.Text(self, borderwidth=5, height= 5, width=20)]
self.newinputlist.append(first_entries[:])
self.inputlist.append(first_entries[:])
#Insert the entries from the newinputlist
for x in range(0, len(self.newinputlist) + 1):
self.newinputlist[0][x].grid(row=1, column=x, padx=10, pady=5)
#Create two buttons (Both share same row), append them to list
button_input_1 = [tk.Button(self, text="ADD FIELD/VALUE", command=self.add_insert), tk.Button(self, text="BACK")]
self.inputlist.append(button_input_1[:])
#Insert buttons at the bottom of the grid
for button in range(len(self.inputlist) - 2, len(self.inputlist)):
self.inputlist[button][0].grid(row=button, column=0, padx=10, pady=5)
self.inputlist[button][1].grid(row=button, column=1, padx=10, pady=5)
def add_insert(self):
#Create two new entries, append them to the list
add_input = [tk.Entry(self, borderwidth=5), tk.Text(self, borderwidth=5, height= 5, width=20)]
self.inputlist.insert(-1, add_input)
self.newinputlist.append(add_input)
#Because there are new entry boxes, old grid should be forgotten
for widget in self.children.values():
widget.grid_forget()
#Use the index for the row, get all widgets and place them again
for index, widgets in enumerate(self.inputlist):
widget_one = widgets[0]
widget_two = widgets[1]
widget_one.grid(row=index, column=0, padx=10, pady=5)
widget_two.grid(row=index, column=1, padx=10)
#Create scrollbar when this button is pressed
scrollbar = tk.Scrollbar(self, orient="vertical")
scrollbar.grid(row=0, column=2, stick="ns", rowspan=len(self.inputlist) + 1)
if __name__ == "__main__":
app = Insert()
app.maxsize(0, 500)
app.mainloop()
You could create a Canvas and insert your Entry objects into a Frame.
Here is a simplified example that creates a 2D bank of Buttons using the canvas.create_window.
import tkinter as tk
root = tk.Tk()
# essential to enable full window resizing
root.rowconfigure(0, weight = 1)
root.columnconfigure(0, weight = 1)
# scrollregion is also essential when using scrollbars
canvas = tk.Canvas(
root, scrollregion = "0 0 2000 1000", width = 400, height = 400)
canvas.grid(row = 0, column = 0, sticky = tk.NSEW)
scroll = tk.Scrollbar(root, orient = tk.VERTICAL, command = canvas.yview)
scroll.grid(row = 0, column = 1, sticky = tk.NS)
canvas.config(yscrollcommand = scroll.set)
# I've used a labelframe instead of frame so button are neatly collected and named
frame = tk.LabelFrame(root, labelanchor = tk.N, text = "Buttonpad")
# Note I've placed buttons in frame
for fila in range(20):
for col in range(5):
btn = tk.Button(frame, text = f"{fila}-{col}")
btn.grid(row = fila, column = col, sticky = tk.NSEW)
# Frame is now inserted into canvas via create_window method
item = canvas.create_window(( 2, 2 ), anchor = tk.NW, window = frame )
root.mainloop()

tkinter frame inside of canvas not expanding to fill area

I have a scrollable frame class that I borrowed from some code I found, and I'm having trouble adjusting it to fit my needs. It was managed by .pack(), but I needed to use .grid(), so I simply packed a frame (self.region) into it so I could grid my widgets inside of that. However, the widgets inside of this frame won't expand to meet the edges of the container and I'm not sure why. Theres a lot of issues similar to mine out there, but none of the solutions seemed to help. I tried using .grid_columnconfigure, .columnconfigure(), and .bind("Configure") all to no avail. Does anyone have any suggestions to get the widgets in my scrollable region to expand east and west to fill the window?
import tkinter as tk
from tkinter import ttk
class ScrollableFrame(ttk.Frame):
"""taken from https://blog.tecladocode.com/tkinter-scrollable-frames/ and modified to
allow for the use of grid inside self.region
Class that allows for the creation of a frame that is scrollable"""
def __init__(self, container, *args, **kwargs):
super().__init__(container, *args, **kwargs)
canvas = tk.Canvas(self)
scrollbar = ttk.Scrollbar(self, orient="vertical", command=canvas.yview)
self.scrollable_frame = ttk.Frame(canvas)
canvas.create_window((0, 0), window=self.scrollable_frame, anchor="nw")
canvas.configure(yscrollcommand=scrollbar.set)
canvas.pack(side="left", fill="both", expand=True)
canvas.rowconfigure(0, weight=1)
canvas.columnconfigure(0, weight=1)
scrollbar.pack(side="right", fill="y")
self.scrollable_frame.bind(
"<Configure>",
lambda e: canvas.configure(
scrollregion=canvas.bbox("all")
)
)
self.scrollable_frame.rowconfigure(0, weight=1)
self.scrollable_frame.columnconfigure(0, weight=1)
self.region=ttk.Frame(self.scrollable_frame)
self.region.pack(fill='both', expand=1)
self.region.grid_rowconfigure(0, weight=1)
self.region.grid_columnconfigure(0, weight=1)
class OtherWindow():
"""data collector object and window"""
def __init__(self, window):
self.window=tk.Toplevel(window)
self.window.grid_columnconfigure(0, weight=1)
self.window.grid_columnconfigure(1, weight=1)
self.window.grid_columnconfigure(2, weight=1)
self.window.grid_columnconfigure(3, weight=1)
self.window.grid_rowconfigure(3, weight=1)
self.lbl1=ttk.Label(self.window, text="this is a sort of long label")
self.lbl1.grid(row=0, column=0, columnspan=2)
self.lbl2=ttk.Label(self.window, text="this is another label")
self.lbl2.grid(row=0, column=2)
self.lbl3=ttk.Label(self.window, text="Other information: blah blah blah blah")
self.lbl3.grid(row=0, column=3)
self.directions=ttk.Label(self.window, text='These are instructions that are kind of long and take'+\
'up about this much space if I had to guess so random text random text random text', wraplength=700)
self.directions.grid(row=1, column=0, columnspan=4)
self.scrolly=ScrollableFrame(self.window)
self.scrolly.grid(row=2, column=0, columnspan=4,sticky='nsew')
self.frame=self.scrolly.region
self.fillScrollRegion()
self.continueBtn=ttk.Button(self.window, text="Do Something", command=self.do)
self.continueBtn.grid(row=3, column=0, columnspan=4, sticky='nsew')
def fillScrollRegion(self):
"""fills scrollable region with label widgets"""
for i in range(15):
for j in range(5):
lbl=ttk.Label(self.frame, text="Sample text"+str(i)+' '+str(j))
lbl.grid(row=i, column=j, sticky='nsew')
def do(self):
pass
root=tk.Tk()
app=OtherWindow(root)
root.mainloop()
The problem is that the scrollframe container Frame is not filling the Canvas horizontally. Instead of bothering to fix some copy/paste, example scrollframe, and explain it, I'll just give you my scrollframe. It is substantially more robust than the one you are using, and the problem you are having doesn't exist with it. I've already plugged it into a version of your script below.
The solution to your scrollframe's issue is found in my on_canvas_configure method. It simply tells the container frame to be the same width as the canvas, on canvas <Configure> events.
import tkinter as tk, tkinter.ttk as ttk
from typing import Iterable
class ScrollFrame(tk.Frame):
def __init__(self, master, scrollspeed=5, r=0, c=0, rspan=1, cspan=1, grid={}, **kwargs):
tk.Frame.__init__(self, master, **{'width':400, 'height':300, **kwargs})
#__GRID
self.grid(**{'row':r, 'column':c, 'rowspan':rspan, 'columnspan':cspan, 'sticky':'nswe', **grid})
#allow user to set width and/or height
if {'width', 'height'} & {*kwargs}:
self.grid_propagate(0)
#give this widget weight on the master grid
self.master.grid_rowconfigure(r, weight=1)
self.master.grid_columnconfigure(c, weight=1)
#give self.frame weight on this grid
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
#_WIDGETS
self.canvas = tk.Canvas(self, bd=0, bg=self['bg'], highlightthickness=0, yscrollincrement=scrollspeed)
self.canvas.grid(row=0, column=0, sticky='nswe')
self.frame = tk.Frame(self.canvas, **kwargs)
self.frame_id = self.canvas.create_window((0, 0), window=self.frame, anchor="nw")
vsb = tk.Scrollbar(self, orient="vertical")
vsb.grid(row=0, column=1, sticky='ns')
vsb.configure(command=self.canvas.yview)
#attach scrollbar to canvas
self.canvas.configure(yscrollcommand=vsb.set)
#_BINDS
#canvas resize
self.canvas.bind("<Configure>", self.on_canvas_configure)
#frame resize
self.frame.bind("<Configure>", self.on_frame_configure)
#scroll wheel
self.canvas.bind_all('<MouseWheel>', self.on_mousewheel)
#makes frame width match canvas width
def on_canvas_configure(self, event):
self.canvas.itemconfig(self.frame_id, width=event.width)
#when frame dimensions change pass the area to the canvas scroll region
def on_frame_configure(self, event):
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
#add scrollwheel feature
def on_mousewheel(self, event):
self.canvas.yview_scroll(int(-event.delta / abs(event.delta)), 'units')
#configure self.frame row(s)
def rowcfg(self, index, **options):
index = index if isinstance(index, Iterable) else [index]
for i in index:
self.frame.grid_rowconfigure(i, **options)
#so this can be used inline
return self
#configure self.frame column(s)
def colcfg(self, index, **options):
index = index if isinstance(index, Iterable) else [index]
for i in index:
self.frame.grid_columnconfigure(i, **options)
#so this can be used inline
return self
class AuxiliaryWindow(tk.Toplevel):
def __init__(self, master, **kwargs):
tk.Toplevel.__init__(self, master, **kwargs)
self.geometry('600x300+600+200')
self.attributes('-topmost', True)
self.title('This Is Another Title') #:D
#if you reconsider things, you can accomplish more with less
labels = ["this is a sort of long label",
"this is another label",
"Other information: blah blah blah blah"]
for i, text in enumerate(labels):
ttk.Label(self, text=text).grid(row=0, column=i)
self.grid_columnconfigure(i, weight=1)
#doing it this way the text will always fit the display as long as you give it enough height to work with
instr = tk.Text(self, height=3, wrap='word', bg='gray94', font='Arial 8 bold', bd=0, relief='flat')
instr.insert('1.0', ' '.join(['instructions']*20))
instr.grid(row=1, columnspan=3, sticky='nswe')
#instantiate the scrollframe, configure the first 5 columns and return the frame. it's inline mania! :p
self.scrollframe = ScrollFrame(self, 10, 2, 0, cspan=3).colcfg(range(5), weight=1).frame
self.fillScrollRegion()
#why store a reference to this? Do you intend to change/delete it later?
ttk.Button(self, text="Do Something", command=self.do).grid(row=3, columnspan=3, sticky='ew')
def fillScrollRegion(self):
"""fills scrollable region with label widgets"""
r, c = 30, 5 #math is our friend
for i in range(r*c):
ttk.Label(self.scrollframe, text=f"row_{i%r} col_{i//r}").grid(row=i%r, column=i//r, sticky='nsew')
def do(self):
pass
class Root(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.geometry('+550+150')
self.title('This Is A Title Probably Or Something') #:D
aux = AuxiliaryWindow(self)
self.mainloop()
Root() if __name__ == "__main__" else None

How to move popup window when scrolling a tkinter treeview?

I have a tkinter treeview with a vertical scrollbar. To make it (look like) editable, I create a popup Entry when the user double-clicks on a cell of the treeview. However, I can't make the popup to move when the treeview is scrolled.
import tkinter as tk
from tkinter import ttk
class EntryPopup(ttk.Entry):
def __init__(self, parent, itemId, col, **kw):
super().__init__(parent, **kw)
self.tv = parent
self.iId = itemId
self.column = col
self['exportselection'] = False
self.focus_force()
self.bind("<Return>", self.onReturn)
def saveEdit(self):
self.tv.set(self.iId, column=self.column, value=self.get())
print("EntryPopup::saveEdit---{}".format(self.iId))
def onReturn(self, event):
self.tv.focus_set()
self.saveEdit()
self.destroy()
class EditableDataTable(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.parent = parent
self.tree = None
self.entryPopup = None
columns = ("Col1", "Col2")
# Create a treeview with vertical scrollbar.
self.tree = ttk.Treeview(self, columns=columns, show="headings")
self.tree.grid(column=0, row=0, sticky='news')
self.tree.heading("#1", text="col1")
self.tree.heading("#2", text="col2")
self.vsb = ttk.Scrollbar(self, orient="vertical", command=self.tree.yview)
self.tree.configure(yscrollcommand=self.vsb.set)
self.vsb.grid(column=1, row=0, sticky='ns')
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(0, weight=1)
self.entryPopup = None
self.curSelectedRowId = ""
col1 = []
col2 = []
for r in range(50):
col1.append("data 1-{}".format(r))
col2.append("data 2-{}".format(r))
for i in range(min(len(col1),len(col2))):
self.tree.insert('', i, values=(col1[i], col2[i]))
self.tree.bind('<Double-1>', self.onDoubleClick)
def createPopup(self, row, column):
x,y,width,height = self.tree.bbox(row, column)
# y-axis offset
pady = height // 2
self.entryPopup = EntryPopup(self.tree, row, column)
self.entryPopup.place(x=x, y=y+pady, anchor='w', width=width)
def onDoubleClick(self, event):
rowid = self.tree.identify_row(event.y)
column = self.tree.identify_column(event.x)
self.createPopup(rowid, column)
root = tk.Tk()
for row in range(2):
root.grid_rowconfigure(row, weight=1)
root.grid_columnconfigure(0, weight=1)
label = tk.Label(root, text="Double-click to edit and press 'Enter'")
label.grid(row=0, column=0, sticky='news', padx=10, pady=5)
dataTable = EditableDataTable(root)
dataTable.grid(row=1, column=0, sticky="news", pady=10, padx=10)
root.geometry("450x300")
root.mainloop()
To reproduce the problem, double-click on the treeview. While the edit box is open, move your mouse pointer to hover over the treeview. Now scroll using the mouse wheel. The treeview moves but the popup edit box does not.
I have done something similar before by binding a function to mousewheel and recalculate all the new x & y location of your hovering widgets.
class EditableDataTable(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.parent = parent
self.tree = None
self.entryPopup = None
self.list_of_entries = []
...
self.tree.bind("<MouseWheel>", self._on_mousewheel)
def _on_mousewheel(self, event):
if self.list_of_entries:
def _move():
for i in self.list_of_entries:
try:
iid = i.iId
x, y, width, height = self.tree.bbox(iid, column="Col2") #hardcoded to col2
i.place(x=x, y=y+height//2, anchor='w', width=width)
except ValueError:
i.place_forget()
except tk.TclError:
pass
self.master.after(5, _move)
def createPopup(self, row, column):
x,y,width,height = self.tree.bbox(row, column)
# y-axis offset
pady = height // 2
self.entryPopup = EntryPopup(self.tree, row, column)
self.list_of_entries.append(self.entryPopup)
self.entryPopup.place(x=x, y=y+pady, anchor='w', width=width)
Note that this only works on the second column, but should be easy enough to implement for the rest.
You will need to do the math and move the entry widget when the tree is scrolled. I have edited your code and I programmed the scrollbar buttons only. If you click the button the entry widget will scroll with the tree. I did not program the wheelmouse scrolling or dragging the scrollbar. You should be able to figure out the rest.
import tkinter as tk
import tkinter.font as tkfont
from tkinter import ttk
class EntryPopup(ttk.Entry):
def __init__(self, parent, itemId, col, **kw):
super().__init__(parent, **kw)
self.tv = parent
self.iId = itemId
self.column = col
self['exportselection'] = False
self.focus_force()
self.bind("<Return>", self.onReturn)
def saveEdit(self):
self.tv.set(self.iId, column=self.column, value=self.get())
print("EntryPopup::saveEdit---{}".format(self.iId))
def onReturn(self, event):
self.tv.focus_set()
self.saveEdit()
self.destroy()
class EditableDataTable(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.parent = parent
self.tree = None
self.entryPopup = None
columns = ("Col1", "Col2")
# Create a treeview with vertical scrollbar.
self.tree = ttk.Treeview(self, columns=columns, show="headings")
self.tree.grid(column=0, row=0, sticky='news')
self.tree.heading("#1", text="col1")
self.tree.heading("#2", text="col2")
self.vsb = ttk.Scrollbar(self, orient="vertical", command=self.tree.yview)
self.tree.configure(yscrollcommand=self.vsb.set)
self.vsb.grid(column=1, row=0, sticky='ns')
self.grid_columnconfigure(0, weight=1)
self.grid_rowconfigure(0, weight=1)
self.entryPopup = None
self.curSelectedRowId = ""
col1 = []
col2 = []
for r in range(50):
col1.append("data 1-{}".format(r))
col2.append("data 2-{}".format(r))
for i in range(min(len(col1),len(col2))):
self.tree.insert('', i, values=(col1[i], col2[i]))
self.tree.bind('<Double-1>', self.onDoubleClick)
self.vsb.bind('<ButtonPress-1>', self.func)
def func(self, event):
print(self.vsb.identify(event.x, event.y))
if hasattr(self.entryPopup, 'y'):
item = self.vsb.identify(event.x, event.y)
if item == 'uparrow':
self.entryPopup.y += 20
elif item == 'downarrow':
self.entryPopup.y -= 20
self.entryPopup.place(x=self.entryPopup.x, y=self.entryPopup.y, )
def createPopup(self, row, column):
x, y, width, height = self.tree.bbox(row, column)
# y-axis offset
pady = height // 2
self.entryPopup = EntryPopup(self.tree, row, column)
self.entryPopup.x = x
self.entryPopup.y = y+pady
self.entryPopup.place(x=x, y=y+pady, anchor='w', width=width)
def onDoubleClick(self, event):
rowid = self.tree.identify_row(event.y)
column = self.tree.identify_column(event.x)
self.createPopup(rowid, column)
root = tk.Tk()
for row in range(2):
root.grid_rowconfigure(row, weight=1)
root.grid_columnconfigure(0, weight=1)
label = tk.Label(root, text="Double-click to edit and press 'Enter'")
label.grid(row=0, column=0, sticky='news', padx=10, pady=5)
dataTable = EditableDataTable(root)
dataTable.grid(row=1, column=0, sticky="news", pady=10, padx=10)
root.geometry("450x300")
root.mainloop()
I have simpler solution than tracking mouse events:
self.tree.configure(yscrollcommand = self.ScrollTree)
def ScrollTree(self, a, b):
if self.entryPopup is not None:
pos = self.tree.bbox(self.entryPopup.iid , 'value')
# if cell visible
if pos != '':
self.entryPopup.place(x=pos[0], y=pos[1], width = pos[2], height = pos[3])
else:
self.entryPopup.place_forget()
# update attached scrollbar
self.vsb.set(a, b)

Python Tkinter: Scrolling two text boxes at the same time; lines get out of sync

I have a Text widget, textbox, where the user can type, and another Text widget, linenumbers, directly to the left of it which displays line numbers. I have it working, but with one problem: the more lines I create, the more the two Text widgets get out of sync when scrolled.
Here's a gif showing what happens.
And here's my code (scroll_both and update_scroll came from another StackOverflow thread):
class MainGUI:
def scroll_both(self, action, position, type=None):
self.textbox.yview_moveto(position)
self.linenumbers.yview_moveto(position)
def update_scroll(self, first, last, type=None):
self.textbox.yview_moveto(first)
self.linenumbers.yview_moveto(first)
self.scrollbar_y.set(first, last)
def on_textbox_update(self, event):
modified = self.textbox.edit_modified()
if modified:
#If the number of lines in the textbox has changed:
if self.linecount != self.textbox.index("end").split('.')[0]:
#Update textbox's linecount
self.linecount = self.textbox.index("end").split('.')[0]
# Clear the line count sidebar
self.linenumbers.config(state="normal")
self.linenumbers.delete(0.0, "end")
# Fill it up again
for LineNum in range(1, int(self.linecount)):
self.linenumbers.insert(self.linenumbers.index("end"), str(LineNum)+"\n", "justifyright")
if len(str(LineNum)) == 1:
self.linenumbers.config(width=2)
else:
self.linenumbers.config(width=len(str(LineNum)))
self.linenumbers.config(state="disabled")
def __init__(self, master):
self.master = master
Grid.rowconfigure(master, 1, weight=1)
Grid.columnconfigure(master, 2, weight=1)
self.linenumbers = Text(master, width=2, borderwidth=0, takefocus=0, padx=6, fg="gray")
self.linenumbers.config(font="TkTextFont")
self.linenumbers.tag_configure("justifyright", justify="right")
self.linenumbers.grid(row=0, column=1, rowspan=2, sticky=NSEW)
self.textbox = Text(master, padx=6, borderwidth=0, wrap=NONE)
self.textbox.config(font="TkTextFont")
self.textbox.grid(row=0, column=2, rowspan=2, sticky=NSEW)
self.textbox.bind("<<Modified>>", self.on_textbox_update)
self.scrollbar_y = ttk.Scrollbar(master)
self.scrollbar_y.grid(row=0, column=3, rowspan=2, sticky=NSEW)
self.scrollbar_y.config(command=self.scroll_both)
self.textbox.config(yscrollcommand=self.update_scroll)
self.linenumbers.config(yscrollcommand=self.update_scroll)
root = Tk()
MainGUI = MainGUI(root)
root.mainloop()

Scrollable frame does not fill canvas

I use Python 2.7 and I have a scrollable frame where the canvas is not shrinking to fit the frame I want to make scrollable.
I looked at this question for an answer but it does not work when I run it:
How to resize a scrollable frame to fill the canvas?
When I print the width of the frame inside the canvas, it says 0.
I also ran the code from the answer of this question on my computer :
Scrollable Frame does not resize properly using tkinter in Python
but it will still show the white canvas to the left of the labels, and it does not resize when the labels are deleted.
It looks like this:
This is my code, based on the answer in this question:
Adding a scrollbar to a group of widgets in Tkinter
from Tkinter import *
class Scrollable_frame(Frame):
def __init__(self, parent, title, values):
self.parent = parent
Frame.__init__(self, self.parent)
self.canvas = Canvas(self, borderwidth=0, background="#ffffff")
self.scrollbar = Scrollbar(self, command=self.canvas.yview)
self.innerFrame = Radiobutton_frame(self.canvas,title,values)
self.canvas.configure(yscrollcommand=self.scrollbar.set)
self.canvas.grid(row=0, column=0, sticky= N+S)
self.scrollbar.grid(row=0, column=1, sticky = N+S)
self.canvas.create_window((0,0),window = self.innerFrame,anchor="nw")
self.innerFrame.bind("<Configure>", self.set_canvas_scrollregion)
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
def set_canvas_scrollregion(self, event):
width = event.width - 4
self.canvas.itemconfigure("self.innerFrame ", width=width)
self.canvas.config(scrollregion=self.canvas.bbox("all"))
class Radiobutton_frame(LabelFrame):
def __init__(self, parent, title, values):
"""
In: parent - Canvas
title - String
values - List of Int
"""
self.radiobuttons = {}
self.parent = parent
self.selection = StringVar()
self.selection.set("init")
LabelFrame.__init__(self, self.parent, text = title)
for value in values:
self.add_radiobutton(value)
def add_radiobutton(self, value):
"""
Adds a radiobutton to the frame.
In: item - String
"""
# Associate to same variable to make them function as a group
self.radiobuttons[value] = Radiobutton(master = self,
variable = self.selection,
text = value,
value = value)
self.radiobuttons[value].pack(anchor=W)
# Usage example
root = Tk()
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
scrollableFrame = Scrollable_frame(root, "Canvas not resizing", range(30))
scrollableFrame.grid(row=0, column=0, sticky=N+S+E+W)
if __name__ == '__main__':
root.mainloop()
I don't think above question's code snippet fits to a Minimal, Complete, and Verifiable example but at the very least it's runnable.
You have three mistakes compared to that of: How to resize a scrollable frame to fill the canvas?
The most significant of which is that in the linked question, the OP uses the option tags where you don't. Replace:
self.canvas.create_window((0,0),window = self.innerFrame,anchor="nw")
with:
self.canvas.create_window((0,0),window = self.innerFrame, anchor="nw", tags="my_tag")
Another mistake is that you're binding the event of a frame's resizing as opposed to the actual Canvas' resizing, also pointed out in Bryan's comment here. Replace:
self.innerFrame.bind("<Configure>", self.set_canvas_scrollregion)
with:
self.canvas.bind("<Configure>", self.set_canvas_scrollregion)
Lastly, tkinter doesn't seem to accept space character with tags, replace:
self.canvas.itemconfigure("self.innerFrame ", width=width)
with:
self.canvas.itemconfigure("my_tag", width=width)
Finally, you should have:
from Tkinter import *
class Scrollable_frame(Frame):
def __init__(self, parent, title, values):
self.parent = parent
Frame.__init__(self, self.parent)
self.canvas = Canvas(self, borderwidth=0, background="#ffffff")
self.scrollbar = Scrollbar(self, command=self.canvas.yview)
self.innerFrame = Radiobutton_frame(self.canvas,title,values)
self.canvas.configure(yscrollcommand=self.scrollbar.set)
self.canvas.grid(row=0, column=0, sticky= N+S)
self.scrollbar.grid(row=0, column=1, sticky = N+S)
self.canvas.create_window((0,0),window = self.innerFrame,anchor="nw",
tags="my_tag")
self.canvas.bind("<Configure>", self.set_canvas_scrollregion)
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
def set_canvas_scrollregion(self, event):
width = event.width - 4
self.canvas.itemconfigure("my_tag", width=width)
self.canvas.config(scrollregion=self.canvas.bbox("all"))
class Radiobutton_frame(LabelFrame):
def __init__(self, parent, title, values):
"""
In: parent - Canvas
title - String
values - List of Int
"""
self.radiobuttons = {}
self.parent = parent
self.selection = StringVar()
self.selection.set("init")
LabelFrame.__init__(self, self.parent, text = title)
for value in values:
self.add_radiobutton(value)
def add_radiobutton(self, value):
"""
Adds a radiobutton to the frame.
In: item - String
"""
# Associate to same variable to make them function as a group
self.radiobuttons[value] = Radiobutton(master = self,
variable = self.selection,
text = value,
value = value)
self.radiobuttons[value].pack(anchor=W)
# Usage example
root = Tk()
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
scrollableFrame = Scrollable_frame(root, "Canvas not resizing", range(30))
scrollableFrame.grid(row=0, column=0, sticky=N+S+E+W)
if __name__ == '__main__':
root.mainloop()

Categories

Resources