Trouble with Tkinter Scrollbar - python-3.x

I am trying to attach a scrollbar to my listbox in one of my toplevels. However, instead of attaching to my listbox it is attaching itself to the toplevel instead. I am at a complete lost here. Any ideas on what I am doing wrong? I can provide the whole program code if anyone needs it.
def onNewRecipe(self):
self.top = Toplevel()
self.top.title("New Recipe")
quantity = StringVar()
#Center the window
w = 600
h = 600
sw = self.top.winfo_screenwidth()
sh = self.top.winfo_screenheight()
x = (sw - w)/2
y = (sh - h)/2
self.top.geometry('%dx%d+%d+%d' % (w, h, x, y))
#Add quantity label
addQuantity = Label(self.top, text="Add Quantity:")
addQuantity.place(x=0,y=0)
quantityAdd = Entry(self.top, textvariable=quantity)
quantityAdd.place(x=150, y=0)
#Add ingredient label
addIngredient = Label(self.top, text="Add Ingredients:")
addIngredient.place(x=0,y=30)
ingredientAdd = Entry(self.top)
ingredientAdd.place(x=150, y=30)
#Select measurements label
selectMeasurement = Label(self.top, text="Select Measurements:")
selectMeasurement.place(x=0, y=60)
measurement = StringVar()
measurement.set("ounce")
measurementSelect = OptionMenu(self.top, measurement, "ounce", "pound", "gallon", "quart", "fl oz", "pint", "cup", "table spoon", "teaspoon")
measurementSelect.place(x=150, y=60)
#Add measurements label
addMeasurement = Label(self.top, text="Amount:")
addMeasurement.place(x=0, y=100)
measurementAdd = Entry(self.top)
measurementAdd.place(x=150, y=100)
#Add the textwidget
recipeText = Text(self.top)
recipeText.place(x=0,y=200)
#Cooking direction label
cookingDirection = Label(self.top, text="Cooking Direction")
cookingDirection.place(x=0,y=175)
def onNewIngredient():
qVar = quantity.get()
print(qVar)
#Add the Add Button
addButton = Button(self.top, text="Add", command= onNewIngredient)
addButton.place(x=0, y=130)
#Add Ingredients listbox
ingredScroll = Scrollbar(self.top, orient=VERTICAL)
ingredientListbox = Listbox(self.top, yscrollcommand=ingredScroll.set)
ingredScroll.config(command=ingredientListbox.yview)
ingredScroll.pack(side=RIGHT, fill=Y)
ingredientListbox.place(x=450, y=0)

Browsing this tutorial, it looks like the usual way to do this is to create a Frame that contains exactly two widgets: the list box and the scroll bar. In your case, it would look like:
#Add Ingredients listbox
box = Frame(self.top)
ingredScroll = Scrollbar(box, orient=VERTICAL)
ingredientListbox = Listbox(box, yscrollcommand=ingredScroll.set)
ingredScroll.config(command=ingredientListbox.yview)
ingredScroll.pack(side=RIGHT, fill=Y)
ingredientListbox.pack()
box.place(x=450, y=0)

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?

Python 3: Positions of button and input text in a text box

I am new to python and learning to create a text box with two entries and one button.
I am able to set the position of my both entries, but I am not able to set the position of my button below them.
I tried to use:
b.place(anchor=S, bordermode=OUTSIDE, height=5, width=10)
but the button doesn't move at all. It stays at the lower right corner.
Following is my code:
from tkinter import *
root = Tk()
l1 = Label(root, text="Variable_1")
l1.pack( side = LEFT)
e1 = Entry(root, bd = 5)
e1.pack(side = LEFT)
l2 = Label(root, text="Variable_2")
l2.pack( side = LEFT)
e2 = Entry(root, bd = 5)
e2.pack(side = LEFT)
l = Label(root)
def callback():
x = e1.get()
y = e2.get()
print(x)
print(y)
b = Button(root, text="OK", command=callback)
for widget in (e1, e2, l, b):
widget.pack()
How can I place the button at the bottom-centre of the text box?
Also, any suggestions to change the positions of the entries?
Thanks!
The usual way to build complex layouts is to group associated widgets together with frames. In the example below I'm grouping the entrys in one frame and the button in another. This makes it easier to control the vertical positioning.
from tkinter import *
root = Tk()
top = Frame(root) # Create frame to hold entrys
top.pack() # Pack top frame
l1 = Label(top, text="Variable_1")
l1.pack(side=LEFT)
e1 = Entry(top, bd=5)
e1.pack(side=LEFT)
l2 = Label(top, text="Variable_2")
l2.pack(side=LEFT)
e2 = Entry(top, bd=5)
e2.pack(side=LEFT)
l = Label(root)
def callback():
x = e1.get()
y = e2.get()
print(x)
print(y)
bottom = Frame(root) # Create frame to hold button
bottom.pack() # Pack bottom frame
b = Button(bottom, text="OK", command=callback)
b.pack()
You can also use the grid() geometry manager. See The Tkinter Grid Geometry Manager

tkinter - grid item won't move beyond column 0 on a scrollable Canvas

I am trying to mass populate items on a scrollable Canvas. When I create a function to create image labels and buttons, everything works fine if they are on column 0.
If I set it to column 1 however, it stays in the same place. I thought it would have something to do with the frame, so I tried changing the size of the frame, the size of the buttons etc. to be bigger and make the frame bigger as well making the canvas_window bigger. Nothing worked!
def onFrameConfigure(canvas):
canvas.configure(scrollregion=canvas.bbox("all"))
root = Tk()
canvas = Canvas(root, background="black", width=230)
frame = Frame(canvas, background="white")
vsb = Scrollbar(root, orient="vertical", command=canvas.yview)
canvas.configure(yscrollcommand=vsb.set)
canvas.pack(side="left", fill="both")
vsb.pack(side="left", fill="y")
canvas.create_window((0,0), window=frame, anchor="nw")
frame.bind("<Configure>", lambda event, canvas=canvas: onFrameConfigure(canvas))
# --- Function that populates the frame
def standard_window(frame):
block_name = ["Ixalan", "Amonkhet", "Kaladesh"]
button_command = [test]
first_set_photo = ["ixalan.png", "amonkhet.png", "kaladesh.png"]
second_set_photo = ["ixalan.png", "hour.png", "aether_revolt.png"]
third_set_photo = []
set_1_count = 1
file_count = 0
count = 0
image_row = 0
while count < len(block_name):
photo = PhotoImage(file="{0}".format(first_set_photo[count]))
label = Label(frame, image=photo, fg="white", bg="black")
label.image = photo
label.grid(row=image_row, column=1)
Button(frame, text="{0} BLOCK".format(block_name[file_count]), font=("Helvetica", 8), fg="white", bg="black", command=test).grid(
row = set_1_count, column=0, columnspan=5)

Tkinter background color issue at scrollbar

I am working on a tkinter script which has a vertical and horizontal scrollbar.
A portion of the window below the vertical scrollbar is not picking up the background color I'm applying.
I have tried the following color options still the small portion is not picking up.
Image:
Sample window snapshot
Full Code:
from tkinter.tix import *
from tkinter import *
import collections
root = Tk()
root.configure(background='steel blue')
# Global variables
fname = ''
# Variables for setting the height and width of widget
# Variables to set Height
actualRootHeight = 300
rootHScale = 25
rootHOffset = 100
canvasHeight = 300
root2CanvasHMargin =65
# Variables to set Width
rootWScale = 10
rootWOffset = 200
canvasWidth = 300
root2CanvasWMargin = 20
inpWidth = 0
# Lists to save configs
inpParamList = collections.OrderedDict()
paramListRef = collections.OrderedDict()
updatedParamList = collections.OrderedDict()
entryList = []
labels = []
# All widget coding is done here
class guiList(Frame):
global root
# Constructor - Use as a control structure
def __init__(self,parent):
Frame.__init__(self,parent)
self.parent = parent
self.readParams()
self.setGeometry()
self.initUI()
self.controlUI()
def onFrameConfigure(self, event, Canvas1):
# Reset the scroll region to encompass the inner frame
Canvas1.configure(scrollregion=Canvas1.bbox("all"), background='steel blue')
# All widget edition is done here
def initUI(self):
global paramListRef
titleStr = sys.argv[1]
self.parent.title(titleStr)
self.grid(row=0, column=0)
inpConfigs = inpParamList.items()
# Add a canvas and call Frame as it's child widget
# Scrollbar can be added to Canvas
Canvas1 = Canvas(self, width=canvasWidth, height=canvasHeight, borderwidth=0, bg="light steel blue")
vsb = Scrollbar(self, orient="vertical", command=Canvas1.yview, bg="light steel blue", troughcolor="steel blue", highlightcolor="light steel blue", activebackground="light steel blue", highlightbackground="light steel blue")
Canvas1.configure(yscrollcommand=vsb.set)
vsb.grid(column=2, sticky='NS')
hsb = Scrollbar(self, orient="horizontal", command=Canvas1.xview, bg="light steel blue", troughcolor="steel blue")
Canvas1.configure(xscrollcommand=hsb.set)
hsb.grid(column=0, sticky='EW')
Canvas1.grid(row=0, column=0, sticky='NWES')
# Create new Frame for input configs
Frame1 = Frame(Canvas1, width=canvasWidth, height=canvasHeight, bg="light steel blue")
Canvas1.create_window((1,1),window=Frame1, anchor="nw", tags="Frame1")
Frame1.bind("<Configure>", lambda event, arg=Canvas1: self.onFrameConfigure(event, arg))
# Loop through the input configs
i = 0
# Add label and combobox in loop
for k,v in inpConfigs:
# Label widgets
lbl1 = Label(Frame1, text=k, bg="light steel blue", font=("Helvetica", 12, "bold"), fg="steel blue")
lbl1.grid(row = i, column = 0, padx=10, pady=5, sticky='W')
labels.append(lbl1)
# Combo-box widget for configurations
tkvar = StringVar(Frame1)
tkvar.set(v[0])
entry1 = OptionMenu(Frame1, tkvar, *v)
entry1.configure(width=20, anchor=W, bg="steel blue", fg="white", font=("Helvetica", 11, "bold"))
entry1.grid(row = i, column=1, padx=10, pady=5, sticky='E')
#entry1.grid_columnconfigure(2, weight=2)
paramListRef[k] = tkvar
i += 1
# Read the updated configs after the button click
def readUpdatedParams(self):
global updatedParamList
for k,v in paramListRef.items():
updatedParamList[k] = v.get()
root.destroy()
self.writeBack()
# Seperate Frame for buttons
# Upon clicking read updted params
def controlUI(self):
Frame2 = Frame(self, bg="steel blue")
Frame2.grid(row=2, column = 0, sticky="EW")
b = Button(Frame2, text="OK", command=self.readUpdatedParams, bg="light steel blue", fg="steel blue", font=("Helvetica", 11, "bold"))
#b.grid(row=1, column=1, pady=10)
b.pack(fill="none", expand=True, pady = 10)
# Read the file and create a key, value pair for configs
# Lines in file is split with space as delimiter
# First column is the config name and rest are all possible value
def readParams(self):
global inpParamList
global inpWidth
f = open(fname)
for line in f:
val = {}
val = line.split()
key = val.pop(0)
# Get the max width of key to adjust widget width
inpWidth = len(key) if (len(key) > inpWidth) else inpWidth
inpParamList[key] = val
# Geometry ( X-width x Y-width + X-position + Y-position)
# Based on the number of elements in the config list
# the height of the widget is set (Max is 75% of screen size)
def setGeometry(self):
global actualRootHeight
global canvasWidth
global canvasHeight
listLen = len(inpParamList)
rootWinwidth = int(inpWidth *rootWScale) + rootWOffset
rootWinheight = (listLen * rootHScale ) + rootHOffset
screenWidth = self.winfo_screenwidth()
screenHeight = int(self.winfo_screenheight() * 0.75)
if rootWinheight < screenHeight :
actualRootHeight = rootWinheight
else :
actualRootHeight = screenHeight
canvasWidth = rootWinwidth - root2CanvasWMargin
canvasHeight = actualRootHeight - root2CanvasHMargin
rootWinresolution = str(rootWinwidth)+'x'+str(actualRootHeight)+'+'+'0'+'+'+'0'
root.geometry(rootWinresolution)
# Sub routine to write back the config file.
def writeBack(self):
fr = open("bt_top.param.config",'w')
for k,v in updatedParamList.items():
print(k,v)
fr.write(k)
fr.write(" ")
fr.write(v)
fr.write("\n")
fr.close()
# Main Function
# Define Window geometry and other top level stuff here
# Do not go into widget coding here
def main():
global fname
# Get File name from command line argument
fname = sys.argv[2]
app = guiList(root)
root.mainloop()
# The __name__ variable decides what to run
# Below lines make this file run stand-alone
if __name__ == '__main__':
Any suggestions are welcome. Thanks
-Vinay
The piece of black is the background of self, your guiList class. Add a line self.config(bg="steel blue") to its __init__() function (or to initUI() I suppose) to fix it.

Finding coordinates on a scrollable Tkinter canvas

I'm trying to make it so that when you right click, the row is highlighted blue and you can then edit or delete that entry. However, since adding a scrollbar, if the page is scrolled then the selection will be offset.
I have tried to suggested thing of finding the canvasx and canvasy and then using find_closest(0, however when i do that it always returns (1,) no matter what.
Canvasx and canvasy seem local to each label, not the canvas itself
from tkinter import *
def RightClick(event):
#Find widgets on the row that was right clicked
print(canvas.canvasy(event.y))
for widget in frame.winfo_children():
mouseClickY = event.y_root - root.winfo_y() - widget.winfo_height()
widgetTopY = widget.winfo_y()
widgetBottomY = widget.winfo_y() + widget.winfo_height()
if (widget.winfo_class() == "Label") and (mouseClickY > widgetTopY) and (mouseClickY < widgetBottomY):
#Highlight that row
if widget.cget("bg") != "#338fff":
widget.config(bg = "#338fff", fg="#FFFFFF")
#Deselect all rows
elif widget.winfo_class() == "Label":
widget.config(bg = "#FFFFFF", fg="#000000")
def onFrameConfigure(event):
canvas.configure(scrollregion=canvas.bbox("all"))
root = Tk()
root.bind("<Button-3>", RightClick)
canvas = Canvas(root, width = 1080, height=500)
frame = Frame(canvas)
scrollbar = Scrollbar(root, command=canvas.yview)
canvas.config(yscrollcommand=scrollbar.set)
canvas.grid(row=1,column=0,columnspan=5)
canvas.create_window((0,0), window=frame, anchor="nw",tags="frame")
scrollbar.grid(column=5,row=1,sticky="NS")
frame.bind("<Configure>", onFrameConfigure)
for countY in range(40):
for countX in range(6):
l = Label(frame, text=countX, width = 25, height = 1, bg="#FFFFFF")
l.grid(column=countX,row=countY+1)
Solved it, turns out
mouseClickY = event.y_root - root.winfo_y() - widget.winfo_height()
widgetTopY = widget.winfo_y()
widgetBottomY = widget.winfo_y() + widget.winfo_height()
should be
mouseClickY = event.y_root - root.winfo_y()
widgetTopY = widget.winfo_rooty()
widgetBottomY = widget.winfo_rooty() + widget.winfo_height()
OR
I should have used winfo_containing, which is a lot neater

Resources