Scrollable Frame() inside Text() in tkinter - text

I've embedded 100 rows in 2 columns. Each row is inside a single frame, and all packed inside a text widget. I'm trying to add a scroll bar, but it scrolls only text, while the columns won't move.
frame_fields = tk.Text(self.window)
frame_fields.pack_propagate(0)
tf = {}
text_labels = tk.Text(frame_fields)
text_labels.pack(side='left', expand='yes', fill='both')
scrollbar = tk.Scrollbar(frame_fields)
scrollbar.config(command=text_labels.yview)
scrollbar.pack(side='right', fill='y')
text_labels.config(yscrollcommand=scrollbar.set)
for f in range(100):
tf[f] = tk.Frame(text_labels)
e_find = tk.Entry(tf[f])
e_replace = tk.Entry(tf[f])
e_find.pack(side='left')
e_replace.pack(side='left')
tf[f].pack()
frame_fields.pack()
How should I change this code, so text_labels scroll?
A Solution
The code below creates the scrollable cells:
def create_cells(self):
"""Create cells for exception text"""
# ----------------------------------------------
frame_fieldsscroll = tk.Text(self.main, relief='flat')
text_fields = tk.Text(frame_fieldsscroll, relief='flat')
frame_fieldsscroll.window_create('insert', window=text_fields)
tf = {}
for f in range(31):
e_find = tk.Entry(text_fields, width=16, relief='flat')
e_replace = tk.Entry(text_fields, width=16, relief='flat')
e_find.insert(0, 'find'+str(f))
e_replace.insert(0, 'replace'+str(f))
e_find.grid(row=0, column=0)
e_replace.grid(row=0, column=1)
text_fields.window_create('insert', window=e_find)
text_fields.window_create('insert', window=e_replace)
text_fields.insert('end', '\n')
scrollbar = tk.Scrollbar(frame_fieldsscroll, width=15)
scrollbar.config(command=text_fields.yview)
text_fields.config(yscrollcommand=scrollbar.set)
frame_fieldsscroll.pack()
scrollbar.pack(side='right', fill='y')
text_fields.pack(fill='both')
text_fields.configure(state='disabled')
frame_fieldsscroll.configure(state='disabled')

For objects in a window to be scrollable along with the text you must add them with the window_create method of the text widget instead of using pack or grid or place.

Related

Tkinter dialog's elements position

I am building custom Tkinter dialog window with Entry and Combobox. I am stuck with placing text and enter frames. Currently I am placing them manually. I am looking for the way to let tkinter do it automatically (maybe with pack() method). And also configure TopLevel size automatically.
My code:
def ask_unit_len():
values = ['millimeters', 'micrometers', 'nanometers']
top = Toplevel()
top.geometry('170x100')
top.resizable(False, False)
top.focus_set()
top.grab_set()
top.title('Enter length and units')
label_length = Label(top, text='Length:')
label_length.place(x=0, y=0)
units_type = StringVar()
length = StringVar()
answer_entry = Entry(top, textvariable=length, width=10)
answer_entry.place(x=55, y=0)
label_units = Label(top, text='Units:')
label_units.place(x=0, y=30)
combo = Combobox(top, width=10, textvariable=units_type,
values=values)
combo.place(x=50, y=30)
button = Button(top, text='Enter',
command=lambda:
mb.showwarning("Warning",
"Enter all parameters correctly")
if (units_type.get() == "" or not length.get().isdigit()
or int(length.get()) <= 0)
else top.destroy())
button.place(x=65, y=70)
top.wait_window(top)
return int(length.get()), units_type.get()
So, is there any way to perform this?

Grid fill empty space in scrollable area tkinter

I'm buildinging a gui with a list of labels in a scrollable area. Now I want that the labels fill the empty space via grid manager. So I use the columnconfigure(0, weight=1) and rowconfigure(0, weight=1) method. It works fine for the scrollbar but not for the labels inside the scrollable area. Example showing my issue:
class app():
def __init__(self):
self.root = tk.Tk()
self.root.geometry("341x448")
self.root.minsize(340,440)
self.root.rowconfigure(0, weight=1)
self.root.columnconfigure(0, weight=1)
def display(self):
self.container = ttk.Frame(self.root)
self.container.rowconfigure(0, weight=1)
self.container.columnconfigure(0, weight=1)
self.canvas = tk.Canvas(self.container)
scrollbar = ttk.Scrollbar(self.container, orient = tk.VERTICAL, command = self.canvas.yview)
self.scrollable_frame = ttk.Frame(self.canvas)
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(yscrollcommand = scrollbar.set)
for i in range(15):
Label = ttk.LabelFrame(self.scrollable_frame, text = "Sample scrolling label")
Label.grid(row = i, column = 0, columnspan=2, sticky=tk.NSEW)
Label.columnconfigure(0, weight=1)
Button = ttk.Button(Label, text=f"Button {i}")
Button.grid(row=0, column=0, sticky=tk.NW)
self.container.grid(row = 0, column = 0, sticky = "nswe")
self.canvas.grid(row = 0, column = 0, sticky = 'nswe')
scrollbar.grid(row = 0, column = 2, sticky = "ns")
self.root.mainloop()
if __name__ =="__main__":
start = app()
start.display()
There are a couple of reasons why your labels do not fill the horizontal space:
You grid the labels in self.scrollable_frame but you have not configured its grid to expand. You need to add
self.scrollable_frame.columnconfigure(0, weight=1)
You have not set the width and height of self.scrollable_frame when you put it in the canvas, so by default, it stays the size it needs to display all its content. If you want it to expand to fill all the space available in the canvas, you can bind the canvas resizing event to a function that will resize the frame accordingly. So add
self.canvas.bind("<Configure>", self.resize)
in self.display() and create the self.resize() function
def resize(self, event):
w = self.scrollable_frame.winfo_reqwidth()
h = self.scrollable_frame.winfo_reqheight()
self.canvas.itemconfigure(1, width=max(w, event.width), height=max(h, event.height))
that makes the frame expand if the canvas is larger than the minimum size required to display all the widgets.
By the way, I suggest you to follow PEP 8 style guide, especially for names, e.g. capitalize class names but not variable names. And above all, be consistent, it will make the code clearer and easier to understand. In particular, I find using Label and Button as variable names quite confusing as they are class names in tkinter.

Extracting string variable from tkinter curselection

Quick question :
I run a TKinter prompt on Python 3.6 and I would like to create a variable from the curselection function inside my listbox. I would like to keep the string of that variable so that I could use it later for naming other variables for instance.
Here's my code :
#Extracting municipalities from shapefile
MunList = []
MunMap = arcpy.env.workspace +'\munic_s.shp'
cursor = arcpy.SearchCursor(MunMap)
for row in cursor:
MunVar = row.getValue("munic_s_24")
MunList.append(MunVar)
del cursor
MunList = sorted(MunList)
print(MunList)
def test(event=None):
#print(listbox.get(ACTIVE))
print(listbox.get(listbox.curselection()))
root = Tk()
root.title("Scrolldown Menu")
scrollbar = Scrollbar(root)
scrollbar.pack(side=RIGHT, fill=Y)
listbox = Listbox(root, selectmode=SINGLE)
for lines in MunList:
listbox.insert(END, lines)
listbox.pack(side=LEFT,fill=BOTH)
listbox.config(yscrollcommand=scrollbar.set)
listbox.config(borderwidth=3, exportselection=0, height=20, width=50)
print(listbox.bbox(0))
(listbox.bind("<Double-Button-1>", test))
scrollbar.config(command=listbox.yview)
mainloop()
I create a 'test' function that selects the ACTIVE item on my cursor and bind it to my listbox with a double-click. When I run it and double-click any name in my list, it prints it. However, I can't seem to be able to make a string variable out of it. When I try something like this :
test_var = (listbox.bind("<Double-Button-1>", test))
print(test_var)
I get some sort of index :
257891528test
But I need the actual string of the variable (example : Washington)
Thanks!
In case anyone has the same question, I found the answer :
root = Tk()
root.title("TEST_TK_Scroll menu")
# Add a grid
mainframe = Frame(root)
mainframe.grid(column=0, row=0, sticky=(N, W, E, S))
mainframe.columnconfigure(0, weight=1)
mainframe.rowconfigure(0, weight=1)
mainframe.pack(pady=100, padx=100)
def close_window ():
root.destroy()
button = Button (text= 'Confirm selection', command=close_window)
button.pack()
sel=[]
def selection(event):
selected = (listbox.get(listbox.curselection()))
print(selected)
sel.append(selected)
scrollbar = Scrollbar(mainframe)
scrollbar.pack(side=RIGHT, fill=Y)
listbox = Listbox(mainframe, selectmode=SINGLE)
for lines in MunList:
listbox.insert(END, lines)
listbox.pack(side=LEFT,fill=BOTH)
listbox.config(yscrollcommand=scrollbar.set)
listbox.config(borderwidth=3, exportselection=0, height=20, width=50)
#cur1 = listbox.get(selected)
#index1 = listbox.get(0, "end").index(cur1)
(listbox.bind("<Double-Button-1>", selection))
print(listbox.bbox(0))
root.mainloop()
print('answer :')
print(sel)
def attempt2(string):
for v in ("[", "]", "'"):
string = string.replace(v, "")
return string
select=sel[0]
attempt2(select)
print(select)

Tkinter not able to fill the text box to the frame using grid. Used styling to add shadow and focus

Description
I am creating a canvas with scrollbar and adding frames with a text box in the frame and to fill the entire frame with no border. This will make it look as if the frame is the textbox. I have added shadow and styling to the frame (as coded by Bryan). This is added dynamically in for loop.
When I am trying to expand the text box to the frame, it is not expanding to fill the entire frame. There are extra spaces left.
Question
How do I fill up the entire frame with the textbox using a grid?
Code
import tkinter as tk
from tkinter import ttk
focusBorderImageData = '''
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
Ry/99NIz//oGrZpUUEAAOw==
'''
borderImageData = '''
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
'''
root = tk.Tk()
style = ttk.Style()
borderImage = tk.PhotoImage("borderImage", data=borderImageData)
focusBorderImage = tk.PhotoImage("focusBorderImage", data=focusBorderImageData)
style.element_create("RoundedFrame",
"image", borderImage,
("focus", focusBorderImage),
border=16, sticky="nsew")
style.layout("RoundedFrame",
[("RoundedFrame", {"sticky": "nsew"})])
root.configure(background="white")
canvas = tk.Canvas(root)
scroll = tk.Scrollbar(root, orient='horizontal', command=canvas.xview)
canvas.configure(xscrollcommand=scroll.set)
frame = tk.Frame(canvas) # frame does not get pack() as it needs to be embedded into canvas throught canvas.
scroll.pack(side='bottom', fill='x')
canvas.pack(fill='both', expand='yes')
canvas.create_window((0,0), window=frame, anchor='nw')
frame.bind('<Configure>', lambda x: canvas.configure(scrollregion=canvas.bbox('all'))) # lambda function
for i in range(5):
frame1 = ttk.Frame(frame, style="RoundedFrame", padding=10)
journal1 = tk.Text(frame1, borderwidth=2, highlightthickness=0, width = 40, height = 38)
# journal1.configure(borderwidth="3")
journal1.configure(relief="groove")
journal1.configure(background="white")
journal1.grid(row=0, column=0, padx=(100, 10), sticky = 'nswe') # grid instead
journal1.bind("<FocusIn>", lambda event: frame.state(["focus"]))
journal1.bind("<FocusOut>", lambda event: frame.state(["!focus"]))
frame1.grid_columnconfigure(0, weight=1)
frame1.grid_rowconfigure(0, weight=1)
frame1.grid(row=0,column=i, sticky = 'nswe')
root.mainloop()
Output
I had troubles with the focus of the frame, it complained frame has no attribute 'state'. It works in Bryans original answer. I fixed it with closures.
def frameFocusCreator(frame, focusState):
def changeState(event):
frame.state([focusState])
return changeState
for i in range(5):
frame1 = ttk.Frame(frame, style="RoundedFrame", padding=10)
journal1 = tk.Text(frame1, borderwidth=0, highlightthickness=0, width = 40, height = 38)
journal1.configure(relief="groove")
journal1.configure(background="white")
journal1.pack(fill='both', expand=True)
journal1.bind("<FocusIn>", frameFocusCreator(frame1, "focus"))
journal1.bind("<FocusOut>", frameFocusCreator(frame1, "!focus"))
frame1.grid(row=0,column=i, sticky = 'nswe')

How to attach my scrollbar to my listbox widget

here is a picture of what i want to be:
scrollbar
Actual code:
lb = Listbox(self.master, width=120, height=6)
scrollbar = Scrollbar(self.master, orient="vertical",command=lb.yview)
scrollbar.pack(side="right", fill="y")
lb.config(yscrollcommand=scrollbar.set)
scrollbar.config(command=lb.yview)
lb.place(x=5,y=5)
Thanks!
You can create a new frame with listbox and scrollbar in it:
from tkinter import *
root = Tk()
root.geometry('500x300')
frame = Frame(root)
frame.place(x = 5, y = 5) # Position of where you would place your listbox
lb = Listbox(frame, width=70, height=6)
lb.pack(side = 'left',fill = 'y' )
scrollbar = Scrollbar(frame, orient="vertical",command=lb.yview)
scrollbar.pack(side="right", fill="y")
lb.config(yscrollcommand=scrollbar.set)
for i in range(10):
lb.insert(END, 'test'+str(i))
root.mainloop()
or since you're using place (which is not recommended), you can simply calculate the position of the scrollbar. grid would be the best layout manager in this case.
The problem is if you use only the 'place' positioning, the scrollbar doesn't appear.
The solution is to make two frames - one master frame with a widget scrollbar and
a second frame inside the master frame, where you can get the listbox. The frames can be positioned with place, the widget inside the frames with pack or grid.
Below is my source code, what works perfectly.
from tkinter import *
root = Tk()
root.geometry('500x300')
frame1 = Frame(root)
frame1.place(x = 10, y = 5,width=100,height=100) # Position of where you would place your listbox
frame1a=Frame(master=frame1)
frame1a.place(x=0,y=0,height=100,width=100)
lb = Listbox(frame1a, width=50, height=6)
lb.grid(row=0,column=0 )
scrollbar = Scrollbar(frame1, orient="vertical",command=lb.yview)
scrollbar.pack(side="right", fill="y")
lb.config(yscrollcommand=scrollbar.set)
for i in range(10):
lb.insert(END, 'test'+str(i))
root.mainloop()

Resources