How to to fit scrollbar approriately? - python-3.x

I am new to tkinter and python3. I have worked on creating a scrollbar for a frame that is a child of a canvas which is also a child of Toplevel(). The scrollbar buttons function well but the bar/box itself stretches from top to bottom and cannot move. Furthermore, using the scroll buttons, the user can scroll way beyond the content (where there nothing to view).
Here is the code.
#! /usr/bin/env python3
from tkinter import *
from filegroups import typeGroups
app = Tk()
types_window = Toplevel(app)
types_window.wm_title('Types')
yscrollbar = Scrollbar(types_window, orient=VERTICAL)
yscrollbar.grid(row=0, column=1, sticky=N+S)
canvas = Canvas(types_window,
width = 300,
height = 500,
yscrollcommand=yscrollbar.set)
canvas.grid(row=0,column=0)
canvas.config(scrollregion=canvas.bbox("all"))
yscrollbar.config(command=canvas.yview)
frame = Frame(canvas)
canvas.create_window(0,0,anchor=NW,window=frame)
for key in sorted(typeGroups.keys()):
options_frame = LabelFrame(frame, text=key)
options_frame.grid(padx=5, pady=10)
for item in typeGroups[key]:
item_button = Checkbutton(options_frame,
text=item)
item_button.grid()
app.mainloop()

You need to update the canvas scrollregion after filling the frame with labels by adding these two lines just before app.mainloop():
canvas.update_idletasks()
canvas.config(scrollregion=canvas.bbox("all"))
The call to update_idletasks is needed to ensure that the canvas content is updated before we ask for the bounding box.

Related

Paned Window Resizing Issues

I've been trying out panedWindows for my app layout, but I'm having issues when the window resizes. This is the layout I've started out with:
The panes drag as I want them too, but I want to specify what panes expand to fill the root window, and which ones stay a specific width. For example, when I drag the window to the right or left, the pane on the expanding size extends to fill the extra space. I want only the centre pane to expand, and the right and left only when the sash is dragged:
like this:
When the panes were managed with the pack geometry manager, I could tell the edge panes to stay a specific width, and tell the centre pane to expand. How can I achieve a similar layout with paned windows?
Heres a minimal working example:
import tkinter
root = tkinter.Tk()
root.geometry("480x200+200+200")
root.title("Paned Window Example")
main = tkinter.PanedWindow()
main.pack(expand = 1, fill = "both")
pane1 = tkinter.Frame(width = 90, bg = "red")
pane2 = tkinter.Frame(width = 310, bg = "blue")
pane3 = tkinter.Frame(width = 90, bg = "red")
for pane in pane1, pane2, pane3 : main.add(pane)
root.mainloop()
You can use the stretch option when adding the panes to the PanedWindow. It takes the following values: 'always', 'first', 'last', 'middle', and 'never'.
You can get a more complete description in the tcl/tk documentation.
In your case, use 'never' for the side panes and 'always' for the middle one:
import tkinter
root = tkinter.Tk()
root.geometry("480x200+200+200")
root.title("Paned Window Example")
main = tkinter.PanedWindow()
main.pack(expand=True, fill="both")
pane1 = tkinter.Frame(width=90, bg="red")
pane2 = tkinter.Frame(width=310, bg="blue")
pane3 = tkinter.Frame(width=90, bg="red")
main.add(pane1, stretch='never')
main.add(pane2, stretch='always')
main.add(pane3, stretch='never')
root.mainloop()
Note that the above solution works only for tkinter.PanedWindow and not for ttk version of the widget. The .add() method of ttk.PanedWindow accepts only one option: weight. But we can achieve the same result by setting the side panes' weight to 0 and the middle pane's to 1:
import tkinter
from tkinter import ttk
root = tkinter.Tk()
root.geometry("480x200+200+200")
root.title("Paned Window Example")
main = ttk.PanedWindow(orient='horizontal')
main.pack(expand=True, fill="both")
pane1 = tkinter.Frame(width=90, bg="red")
pane2 = tkinter.Frame(width=310, bg="blue")
pane3 = tkinter.Frame(width=90, bg="red")
main.add(pane1, weight=0)
main.add(pane2, weight=1)
main.add(pane3, weight=0)
root.mainloop()

Why is the canvas scrollbar disabled in a tkinter window?

I have a problem with a tkinter application that is being made using python 3. I tried to create a canvas widget, without using a frame container and I tried to add two scrollbar to my canvas, one vertical and another orizontal, here is the code:
class drawCavnas(object):
def __init__(self, master, width=500, height=500):
''' build the canvas object '''
# class attributes
self.master = master
self.cWidth = width
self.cHeight = height
# creating the canvas object
self.canvas = tk.Canvas(self.master, width=self.cWidth, height=self.cHeight, bg="green")
self.canvas.grid(row=0, column=1, sticky="nwes")
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
# creating the scrolling
self.scroll_x = tk.Scrollbar(self.master, orient="horizontal", command=self.canvas.xview)
self.scroll_x.grid(row=1, column=1, sticky="ew")
self.scroll_y = tk.Scrollbar(self.master, orient="vertical", command=self.canvas.yview)
self.scroll_y.grid(row=0, column=2, sticky="ns")
self.canvas.configure(yscrollcommand=self.scroll_y.set, xscrollcommand=self.scroll_x.set)
where the master is the window created using tk.Tk(). The problem here is that once I've ran the program and once the window is created the canvas toolbars' are disabled and I can't interact with them. I tried to resize the window but the canvas resize as well. So do you have a solution?
Why is the canvas scrollbar disabled in a tkinter window?
You must tell what part of the larger virtual canvas you want to be scrollable. You've set the scroll region to be bounded by the objects in the canvas (self.canvas.configure(scrollregion=self.canvas.bbox("all"))). However, you've done that before you draw anything in the canvas so tkinter thinks there is nothing to scroll.
If you have a 500x500 canvas, but want to be able to draw on some larger area, you can hard-code the scrollregion.
For example, this will let you scroll around in a 2000x2000 area:
self.canvas.configure(scrollregion=(0,0,2000,2000)

Why a button in a scrollable canvas doesn't move? (Python3 + tkinter)

I'm tring to write a program in python3 using the tkinter module. I've created a canvas widget with a y scrollbar, but as I try to add a button in the canvas and scroll the region, the button doesn't move. Here is the code:
# defining the tool bar
class toolBar(object):
def __init__(self, master):
''' creates the toolbar object '''
self.master = master
# creating the toolbarobject
self.toolbar = tk.Canvas(self.master, width=70, height=200, bg="lightgrey")
self.toolbar.grid(row=0, column=1, sticky="nwes", rowspan=2)
self.toolbar.configure(scrollregion=(0, 0, 0, 2000))
b1 = tk.Button(self.toolbar, text="Try")
b1.grid()
# creating the y scrollling
self.scroll_y = tk.Scrollbar(self.parent.master, orient="vertical", command=self.toolbar.yview)
self.scroll_y.grid(row=0, column=0, sticky="ns", rowspan=2)
self.toolbar.configure(yscrollcommand=self.scroll_y.set)
where the master is a tk.Tk() object passed to the class. Do you have any solution for this issue?
P.S.: I have another question: when I run my program, the canvas that contains the button fits the button width, is it possible to place the button whitout changing the canvas width?
The canvas can't scroll items added with pack, place, or grid. It will only scroll widgets added with the create_window method.

Adding a background image to a root window and placing labels on the top of the background image

I'm trying to add a background image to my root window but it doesn't seem to be working for me. This is my code. I would like the background image to cover the whole window and place the labels on top of the background image.
from tkinter import *
from tkinter import messagebox
top = Tk()
textButton = Frame(top)
textButton.pack()
img = PhotoImage(file="bk.gif")
img = img.subsample(1, 1)
background = Label(top, image = img, bd=0)
background.pack()
background.image = img
name_label = Label(textButton, text="Username")
name_label.grid(row=1, sticky=W)
name_entry = Entry(textButton)## the Entry will let the user entre text inside the text box
name_entry.grid(row=1, column=1)
password_label = Label(textButton, text="Password")
password_label.grid(row=2, sticky=W)
password_entry = Entry(textButton, show="*")
password_entry.grid(row=2, column=1)
top.mainloop
You can use place to use an image as a background for other widgets. place doesn't affect the geometry of other widgets so you can use it with grid or pack.
The main think to keep in mind is that you should do this before creating other widgets so that it is lowest in the stacking order. That, or be sure to call lower on it, otherwise it will overlay the other widgets rather than underlay them.
With your code, just remove background.pack() and replace it with these two lines:
background.place(relx=.5, rely=.5, anchor="center")
background.lower()
You don't need to change anything else. The above centers the image. If you instead want the image to start in the upper-left corner you can do it like this:
background.place(x=0, y=0, anchor="nw")
You have to use background as parent for widgets to put them inside Label with background.
I remove Frame to make it simpler. And now I can use weight to automatically resize empty rows and columns around widgets so they will be in the center.
import tkinter as tk
top = tk.Tk()
top.geometry('250x250')
img = tk.PhotoImage(file="hal_9000.gif")
img = img.subsample(1, 1)
background = tk.Label(top, image=img, bd=0)
background.pack(fill='both', expand=True)
background.image = img
# resize empty rows, columns to put other elements in center
background.rowconfigure(0, weight=100)
background.rowconfigure(3, weight=100)
background.columnconfigure(0, weight=100)
background.columnconfigure(3, weight=100)
name_label = tk.Label(background, text="Username")
name_label.grid(row=1, column=1, sticky='news')
name_entry = tk.Entry(background)## the Entry will let the user entre text inside the text box
name_entry.grid(row=1, column=2)
password_label = tk.Label(background, text="Password")
password_label.grid(row=2, column=1, sticky='news')
password_entry = tk.Entry(background, show="*")
password_entry.grid(row=2, column=2)
top.mainloop()
Result:
As you see widgets have gray background which you can't remove. If you need text without gray background then you have to use Canvas with create_text() (and create_window() to put Entry)
Gif file (with HAL 9000) to test code:

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