tkinter Canvas method seemilar to Text .see() - python

I have a Canvas full of buttons, whose ID's I store in a dictionary.
The canvas is very long, with vertical scrollbars.
There is a way to automatically position the view at a given button?
When using a Text, txt.see(position) usually works,
but I see Canvas has no see.
The only possible alternative seems to be .focus(), but cv.focus(ID) doesnt seem to do what I want

There is no ready made function to do that, but you can implement one using yview_moveto(fraction), where fraction is the top fraction of the canvas that will be set off-screen. So, yview_moveto(0) displays the top of the canvas and yview_moveto(1) the bottom.
What we need is to compute the fraction y/h that will display the button identified by iid. h is the height of the content of the canvas and y the height at which the button is in the canvas. I computed them using the canvas bounding box:
def show(iid):
bbox = canvas.bbox('all')
h = bbox[3] - bbox[1]
y = canvas.coords(iid)[1] - bbox[1]
canvas.yview_moveto(y/h)
And below is a small example, type the button ID (between 1 and 20) in the entry and click on 'Show' to shift the view to see it.
import tkinter as tk
def show(iid):
bbox = canvas.bbox('all')
h = bbox[3] - bbox[1]
y = canvas.coords(iid)[1] - bbox[1]
canvas.yview_moveto(y/h)
root = tk.Tk()
canvas = tk.Canvas(root, bg='white')
canvas.pack(fill='both', expand=True)
e = tk.Entry(root)
e.pack()
tk.Button(root, text='Show', command=lambda: show(e.get())).pack()
buttons = {}
for i in range(1, 21):
b = tk.Button(canvas, text='Button %i' % i)
iid = canvas.create_window(0, 30*i, anchor='nw', width=70, height=30, window=b)
buttons[iid] = b
canvas.configure(scrollregion=canvas.bbox('all'))
root.mainloop()

Related

How to make a floating Frame in tkinter

For my tkinter app I want to make a frame that would on top of other widgets, not taking any space (like html position fixed).
The Frame will have to contain widgets, if Frame is not possible labels or buttons will do.
I have no idea how to do it so haven't tried anything yet. Please help!
Here is a demonstration of place manager.
Remarks in the code explain behaviour.
import tkinter as tk
root = tk.Tk()
root.geometry("868x131")
button = tk.Button(root, text = "A Button")
button.place(x = 2, y = 2)
Frame = tk.LabelFrame(root, text = "A LabelFrame using place", bg="cyan")
# Frame using place with x, y, width, height in absolute coordinates
Frame.place(x = 250, y = 20, width = 600, height = 70)
ButtonInFrame = tk.Button(Frame, text = "A Button IN LabelFrame", bg="white")
# Note: place x,y is referenced to the container (Frame)
# Note: place uses just x,y and allows Button to determine width and height
ButtonInFrame.place(x = 2, y = 2)
Label = tk.Label(root, text = "A Label on LabelFrame\nWith multiple lines\nOf Text.", bg="light green")
# Label sits on top of buttonFrame and Frame
# Note place uses just x,y and allows Label to determine width and height
Label.place(x = 330, y = 60)
root.mainloop()

Tkinter. Scrollbar is not working on Canvas

I am newbie in programming, don't hate me pls :)
Why scroll is not working on my canvas widget?
I added loop with 30 rows and I cannot scroll down.
Its look like it because of create_text() method or maybe not.
I've written code for example below.
from tkinter import *
root = Tk()
root.geometry('200x150')
frame = Frame(root)
yscrollbar = Scrollbar(frame, orient=VERTICAL)
yscrollbar.pack(fill=Y, side=RIGHT)
canvas = Canvas(frame,
yscrollcommand=yscrollbar.set,
bg='white')
canvas.pack(fill=BOTH)
yscrollbar.config(command=canvas.yview)
n=12
for i in range(1,31):
canvas.create_text(10,n,text=i)
n+=12
frame.pack()
root.mainloop()
Scrolling is not responsive because you need to tell the canvas to limit the scrolling to a given area.
You can use the bbox method to get a bounding box for a given object, or a group of objects.
canvas.bbox(ALL) returns the bounding box for all objects on the canvas.
Link: http://effbot.org/zone/tkinter-scrollbar-patterns.htm you can check other methods to do this in this link
Here is the working code:
from tkinter import *
root = Tk()
root.geometry('200x150')
frame = Frame(root)
yscrollbar = Scrollbar(frame, orient=VERTICAL)
yscrollbar.pack(fill=Y, side=RIGHT)
canvas = Canvas(frame,
yscrollcommand=yscrollbar.set,
bg='white')
canvas.pack(fill=BOTH)
yscrollbar.config(command=canvas.yview)
n=12
for i in range(1,31):
canvas.create_text(10,n,text=i)
n+=12
frame.pack()
# Add this line to tell the canvas the area over to scroll
canvas.config(scrollregion=canvas.bbox(ALL))
root.mainloop()

Python Tkinter GUI: Delete canvas objects defined within function at the click of a button

I am having some trouble with a basic GUI I am trying to create. The goal is to have a button that generates a certain amount of tiles based off of a number in a spin box. I have gotten it to work mostly and clicking the 'generate' button will generate a new number of tiles if the spin box value is changed to a larger number, as expected, however if I change the spin box to a smaller number, the old tile generation can be seen behind the new. I would expect that the canvas.delete('all') to take of the previous canvas objects.
I have tried to solve this problem by not redefining the canvas inside of the function which I have a feeling is causing the problem, but then I am not able to redraw the canvas boundaries so the maximum number of tiles that can be generated will be constrained by the initially drawn canvas size. Normally I would think that I could return canvas from the function, but since it is attached to the button through the command functionality and a lambda function, I am not sure how to go about doing that.
import tkinter as tk
def generate_start(window, canvas, num_tiles):
canvas.delete('all')
canvas = tk.Canvas(window, width=40*num_tiles, height=40)
canvas.grid(row=1, columnspan=num_tiles)
for i in range(num_tiles):
tile_width = 40
x1 = i * tile_width
y1 = 0
x2 = (i + 1)*tile_width - 1
y2 = tile_width - 1
canvas.create_rectangle(x1, y1, x2, y2, fill='blue')
window = tk.Tk()
window.title('GUI')
tk.Label(window, text = "Number of starting tiles:").grid(row=0)
default_start_num = 6
var = tk.IntVar(value=default_start_num)
start_num = tk.Spinbox(window, from_=1, to=100,
textvariable=var)
start_num.grid(row=0, column=1)
canvas = tk.Canvas(window, width=40*default_start_num, height=40)
canvas.grid(row=1, columnspan=default_start_num)
generate_btn = tk.Button(window, text='Generate',
command=lambda: generate_start(window, canvas, int(start_num.get())))
generate_btn.grid(row=0, column=2)
window.mainloop()
Every time you click the button you are creating a new canvas. Thus, when you call canvas.delete('all'), you are deleting them from the old canvas then creating a new canvas.
You need to create the canvas once. That, or delete the old canvas before creating the new canvas.

TKinter scrollbar for middle of screen, supporting resizeable screen with top bar and bottom bar

I am trying to make an application that displays a grid in the middle of the screen surrounded by two bars, a top bar and a bottom bar, which contain buttons for the user to press. These buttons should be able to display no matter where the user scrolls to on the grid and should not be cut off if the window is resized. I am struggling to configure the scrollbar to track the right area and to have the grid fall off the screen when the window is resized. Here is my code so far:
from tkinter import *
def add_row(event):
input_row = Entry(grid_frame, bd=1, text="", bg="white", relief="solid")
input_row.grid(row=grid_frame.rows, sticky=N+S+E+W)
Grid.rowconfigure(grid_frame, grid_frame.rows, weight=1)
grid_frame.rows = grid_frame.rows + 1
class GridFrame(Frame):
rows = 0
def __init__(self, root):
Frame.__init__(self, root, bd=1)
root = Tk(className="Main screen")
root.minsize(408, 80)
# size to quarter of screen
w, h = root.winfo_screenwidth() / 2, root.winfo_screenheight() / 2
root.geometry("%dx%d+0+0" % (w, h))
# grid_frame will resize and bars will not
Grid.rowconfigure(root, 1, weight=1)
Grid.columnconfigure(root, 0, weight=1)
myframe = Frame(root, bd=4, relief="groove")
myframe.grid(row=1, sticky=N + W + S + E)
canvas = Canvas(myframe)
grid_frame = GridFrame(canvas)
grid_frame.pack(fill=BOTH, expand=True)
grid_frame.bind("<Button-1>", add_row)
scrollbar = Scrollbar(myframe, orient="vertical", command=canvas.yview)
canvas.configure(yscrollcommand=scrollbar.set)
scrollbar.pack(side=RIGHT, fill=Y)
canvas.pack(side=LEFT, fill=BOTH, expand=True)
topBar = Frame(root, grid_frame)
label = Label(topBar, text="Top Text")
label.pack()
topBar.grid(row=0, sticky=W+N+E+S)
bottomFrame = Frame(root, grid_frame)
label = Label(bottomFrame, text="Bottom Text")
label.pack()
bottomFrame.grid(row=2, sticky=E+S+W)
mainloop()
The scrollregion I want to track is the myframe/canvas/grid_frame combination I read to use from this post. The current functionality is that the scrollbar is never in an "active" state and rows added to the grid merely shrink the grid for it to fit within the display. To add a new row, click within the grid_frame region. Any help would be greatly appreciated! Here are some images of the current UI:
UI display with only a few rows
UI display with many more rows
There are two major problems with your code.
First, for the canvas to be able to scroll the inner frame, the inner frame must be a canvas object created with create_window. You're adding it to the canvas with pack, which means the canvas cannot scroll it.
To fix that, use create_window instead of pack:
canvas.create_window(0, 0, anchor="nw", window=grid_frame)
Second, you must reset the scrollregion attribute whenever the contents inside the canvas change. Normally this is done in a <Configure> event handler on the frame, but you can just as easily call it in your add_row function.
For example, add the following line to the end of add_row:
canvas.configure(scrollregion=canvas.bbox("all"))
With those two changes, the scrollbars will start to work as soon as the inner frame is taller than the canvas.
The above solves the problem of the inner window being able to scroll when you add items. In the specific example of this test program, you also have the problem that your binding is on the frame. At startup the frame has a size of 1x1 so it's a bit hard to click on. Moving the binding to the canvas will make this specific demo program work better.

Canvas on a frame not being displayed

I have a Canvas inside a Frame in tkinter. The frame has a background color and the canvas too. But seemingly the frame background overrides the canvas color.
How can I increase the transparency of the frame background such that the canvas is visible?
import Tkinter
import tkMessageBox
from Tkinter import *
top = Tkinter.Tk()
frame = Frame(top, width=1000, height=1000, background="bisque")
frame.pack()
bottomframe = Frame(top, width=1000, height=1000, background="red")
bottomframe.pack( side = BOTTOM )
def creatLayers(no_of_layers, max_nodes_in_each_layer, frame1=bottomframe):
print 'here2'
listLayerRect=[]
listDelimiterRect=[]
#The canvas is created here.
mainCanvas=Tkinter.Canvas(frame1, bg="white", height=1000, width=1000)
frame1.pack(side=LEFT)
for i in range (0,no_of_layers):
print 'here3'
x=15*i
#rectangles that are being drawn on the canvas.
mainCanvas.create_polygon(x,0,x+10,0,x+10,1000,x,1000, outline='gray', fill='gray', width=2)
# listLayerRect.append(Tkinter.Canvas(frame1, bg="blue", height=1000, width=30))
# listDelimiterRect.append(Tkinter.Canvas(frame1, bg="yellow", height=1000, width=30))
L1 = Label(frame, text="Layers")
E1 = Entry(frame, bd =8)
L2 = Label(frame, text="Layers2")
def helloCallBack(E=E1,):
# tkMessageBox.showinfo( "Hello Python", "Hello World")
k=int(E.get())
print 'here'
print k
creatLayers(k,k)
B = Tkinter.Button(frame, text ="Enter", command = helloCallBack)
B.pack(side=LEFT)
#L1.pack(side=LEFT)
E1.pack(side=LEFT)
#L2.pack(side=LEFT)
top.mainloop()
So, basically, when you enter a number in the box and press Enter, a canvas gets created in the red part (frame) and a grid pattern should be drawn on that canvas. Essentially, there are 2 frames, the top frame contains the button and the entry box, the lower frame should be able to draw stuff inside on the canvas created within.
The reason why the canvas is not displayed is because you're not telling it to be displayed inside frame1, i.e. you forgot to pack (or grid, or place) it, so just do in the meantime:
...
mainCanvas=Tkinter.Canvas(frame1, bg="white", height=1000, width=1000)
mainCanvas.pack()
...
Now depending on what you really want to achieve from the layout point of view, you may need to think better how to use pack, grid and pack.
Here's the result after the correction above (on Mac OS X, Sierra)
Before clicking Enter
After clicking Enter
In general, just remember that a frame will have a empty body if it doesn't contain any widget with a certain specified size.

Categories