I want to keep some text on a scrollable tkinter canvas static, or anchored. It should not move with the scrollbars.
How can I do this?
from tkinter import *
root=Tk()
frame=Frame(root,width=300,height=300)
frame.grid(row=0,column=0)
canvas=Canvas(frame,bg='#FFFFFF',width=300,height=300,scrollregion=(0,0,500,500))
hbar=Scrollbar(frame,orient=HORIZONTAL)
hbar.pack(side=BOTTOM,fill=X)
hbar.config(command=canvas.xview)
vbar=Scrollbar(frame,orient=VERTICAL)
vbar.pack(side=RIGHT,fill=Y)
vbar.config(command=canvas.yview)
canvas.config(width=300,height=300)
canvas.config(xscrollcommand=hbar.set, yscrollcommand=vbar.set)
canvas.pack(side=LEFT,expand=True,fill=BOTH)
canvas.create_text(10, 10, text='Static Text # 1', anchor = NW, fill = 'red')
canvas.create_text(10, 100, text='Static Text # 2', anchor = NW, fill = 'red')
canvas.create_text(150, 10, text='Scrollable Text # 1', anchor = NW)
canvas.create_text(150, 100, text='Scrollable Text # 2', anchor = NW)
root.mainloop()
If you do not need the text box to be transparent, the easiest thing to do is to not put the text on the canvas but to place a Label over the canvas.
import tkinter as tk
root = tk.Tk()
c = tk.Canvas(root)
c.place(x=0, y=0)
r = c.create_rectangle(100, 100, 200, 200, outline='red',
fill='yellow', width=10)
ct = c.create_text(110, 150, text='canvas text', anchor='w')
lt = tk.Label(c, text='label text')
lt.place(x=110, y=110)
If you do want the transparency, then you must intercept move commands between the canvas and scrollbars so as to also move the 'static' items to the canvas coordinates that correspond to the original screen (window) coordinates. This is complicated by the fact that Scrollbar.set expects two fractions, so a canvas calls x/yscrollcommand with two fractions. Similarly, moving the scrollbar slider causes the scrollbar to call its command (canvas x/yview) with a fraction, which I suspect is the upper or left fraction. In the other hand, clicking the scrollbar buttons calls x/yview with a number and units.
I think what I would do is create a list of tuples of static item id, and the 4 canvas.bbox coordinates of the item. Next replace canvas.xscrollcommand, canvas.yscrollcommand, hbar.set, and vbar.set with wrappers named, for instance, xscroll, yscroll, xset, and yset. In each wrapper, call the wrapped function. Then call a new static_set function that uses canvas.canvasx/y(screenx/y, gridspacing=1) to convert screen coords to the new canvas coordinates and canvas.coords(item id, *canvas coords) to place the item.
I leave you to work out the details. If you are not familiar with the NMT Tkinter reference, learn to use it. This is the source of the info above.
Code Edits: Don't use name t for both labels. Change parent of lt from root to canvas c, as suggested by mcu.
Related
I need to find out what the visible coordinates of a vertically scrollable canvas are using python and tkinter.
Let's assume I have a canvas that is 800 x 5000 pixels, and the visible, vertically scrollable window is 800x800. If I am scrolled all the way to the top of the canvas, I would like to have a function that, when run, would return something like:
x=0
y=0,
w=800
h=800
But if I were to scroll down and then run the function, I would get something like this:
x=0
y=350
w=800
h=800
And if I resized the window vertically to, say, 1000, I would get:
x=0
y=350
w=800
h=1000
I tried this code:
self.canvas.update()
print(f"X={self.canvas.canvasx(0)}")
print(f"Y={self.canvas.canvasy(0)}")
print(f"W={self.canvas.canvasx(self.canvas.winfo_width())}")
print(f"H={self.canvas.canvasy(self.canvas.winfo_height())}")
But it gives me the size of the whole canvas, not the visible window inside the canvas.
I have tried searching for the answer, but am surprised not to have found anyone else with the same question. Perhaps I just don't have the right search terms.
Context for anyone who cares:
I am writing a thumbnail browser that is a grid of thumbnails. Since there may be thousands of thumbnails, I want to update the ones that are visible first, and then use a thread to update the remaining (hidden) thumbnails as needed.
The methods canvasx and canvasy of the Canvas widget will convert screen pixels (ie: what's visible on the screen) into canvas pixels (the location in the larger virtual canvas).
You can feed it an x or y of zero to get the virtual pixel at the top-left of the visible window, and you can give it the width and height of the widget to get the pixel at the bottom-right of the visible window.
x0 = canvas.canvasx(0)
y0 = canvas.canvasy(0)
x1 = canvas.canvasx(canvas.winfo_width())
y1 = canvas.canvasy(canvas.winfo_height())
The canonical tcl/tk documentation says this about the canvasx method:
pathName canvasx screenx ?gridspacing?: Given a window x-coordinate in the canvas screenx, this command returns the canvas x-coordinate that is displayed at that location. If gridspacing is specified, then the canvas coordinate is rounded to the nearest multiple of gridspacing units.
Here is a contrived program that will print the coordinates of the top-left and bottom right visible pixels every five seconds.
import tkinter as tk
root = tk.Tk()
canvas_frame = tk.Frame(root, bd=1, relief="sunken")
statusbar = tk.Label(root, bd=1, relief="sunken")
statusbar.pack(side="bottom", fill="x")
canvas_frame.pack(fill="both", expand=True)
canvas = tk.Canvas(canvas_frame, width=800, height=800, bd=0, highlightthickness=0)
ysb = tk.Scrollbar(canvas_frame, orient="vertical", command=canvas.yview)
xsb = tk.Scrollbar(canvas_frame, orient="horizontal", command=canvas.xview)
canvas.configure(yscrollcommand=ysb.set, xscrollcommand=xsb.set)
canvas_frame.grid_rowconfigure(0, weight=1)
canvas_frame.grid_columnconfigure(0, weight=1)
canvas.grid(row=0, column=0, sticky="nsew")
ysb.grid(row=0, column=1, sticky="ns")
xsb.grid(row=1, column=0, sticky="ew")
for row in range(70):
for column in range(10):
x = column * 80
y = row * 80
canvas.create_rectangle(x, y, x+64, y+64, fill="gray")
canvas.create_text(x+32, y+32, anchor="c", text=f"{row},{column}")
canvas.configure(scrollregion=canvas.bbox("all"))
def show_coords():
x0 = int(canvas.canvasx(0))
y0 = int(canvas.canvasy(0))
x1 = int(canvas.canvasx(canvas.winfo_width()))
y1 = int(canvas.canvasy(canvas.winfo_height()))
statusbar.configure(text=f"{x0},{y0} / {x1},{y1}")
root.after(5000, show_coords)
show_coords()
root.mainloop()
I'm working on a GUI with tkinter and i have a problem.
When i add a scrollbar to my app, the frame on my canvas overlaps the outlines (see image)
Here is the code:
from tkinter import *
window = Tk()
window.geometry("400x225")
scrollbar1 = Scrollbar(window, orient=VERTICAL)
canvas1 = Canvas(window, bg="#003333", yscrollcommand=scrollbar1.set)
frame1 = Frame(canvas1, bg="#003333")
scrollbar1.config(command=canvas1.yview)
scrollbar1.pack(side=RIGHT, fill=Y)
canvas1.pack(fill=BOTH, expand=True)
canvas1.create_window((0, 0), window=frame1, anchor="nw")
for x in range(20):
string = "line " + str(x)
label1 = Label(frame1, fg="white", bg="#003333", text=string, font=("Calibri Bold", 14))
label1.pack(pady=5)
window.update()
canvas1.config(scrollregion=canvas1.bbox("all"))
window.mainloop()
I don't know if it's possible but i want the frame to fit within the canvas and keeping the outlines as well.
I hope you get my problem and can probably help me out! Thanks in advance.
The highlightthickness
Specifies a non-negative value indicating the width of the highlight rectangle to draw around the outside of the widget when it has the input focus.
So, this is not really the "border" that you want. It is a part of the drawing space within the canvas, when you use window_create to draw a window, the parent of that window is the canvas, which begins before the highlight and so the window slides over it.
A solution, as also suggested by #martineau would be to make this 0 by specifying highlightthickness=0 and as you suggested that you need the "border" around the whole thing, you can either create a container frame and specify the bd parameter, or just set the bd of the window window.config(bd=2).
root= tk.Tk()
root.title('Quick Googling')
canvas1 = tk.Canvas(root, width = 400, height = 300)
canvas1.pack()
entry1 = tk.Entry (root)
canvas1.create_window(200, 140, window=entry1)
canvas1.config(background="#8FB1CC"
button1 = tk.Button(text='Search Google', bg = "blue")
canvas1.create_window(200, 180, window=button1)
I want the colour of my window and the entry and button to utilise all the space when the window's size is changed.
If you use a frame the geometry mangers of tkinter is doing the job for you see:
import tkinter as tk
root= tk.Tk()
root.title('Quick Googling')
myframe = tk.Frame(root)
myframe.pack(fill='both',expand=True)
entry1 = tk.Entry (myframe,background="#8FB1CC")
entry1.pack(fill='x')
button1 = tk.Button(myframe,text='Search Google', bg = "blue")
button1.pack(fill='both',expand=1)
root.mainloop()
Look on a small overview I wrote over the basics.
fill stretch the slave horizontally, vertically or both expand The
slaves should be expanded to consume extra space in their master.
So what the above code does is to create a frame in its natrual size, this means it shrinks itself to the minimum space that it needs to arrange the children.
After this we use for the first time the geometry manager pack and give the options fill, which tells the frame to stretch and using the optional argument expand to consume extra space. Together they resize your frame with the window, since there is nothing else in it.
After that we create an Entry and using once again the method pack to arrange it with this geometry manager. We are using again the optional argument fill and give the value 'x', which tells the geometry manager to stretch the slave/entry vertically.
So the entry streches vertically if the frame is expandes.
At least we create a Button and using the known keywords, to let it resize with the frame.
I'm creating a GUI for my application with Tkinter, and when I create a Label, the text is always at the center of the label, is there a way to set the text to start from the top left ?
big_text_area = Label(self.master, text='Hello, World!', width=70, height=25, bg='white', foreground='black')
big_text_area.place(x=380, y=30)
Example:
I believe the position of your label is determined by the .place() function. So if you want the label to be at the top left, you should do:
big_text_area.place(x = 0, y = 0)
Tutorialspoint and effbot have a documentation for the .place() function and the arguments it takes. Most have default values so keep that in mind.
Another thing I would point out is that the width and height keyword arguments do not change the size of your text. Rather, they change the rectangular box around your text. This might be the reason why your text is still in the middle even if you set its positions to the top left. effbot again has the details on the Label widget.
Just a quick working example: (Python 3.7)
from tkinter import *
master = Tk()
master.geometry("200x200")
# to change text size, use font=("font name", size)
w = Label(master, text="Hello, world!", font=("Helvetica", 22))
# explicitly set the text to be at the top left corner
w.place(anchor = NW, x = 0, y = 0)
mainloop()
Font question is also answered in this post: How do I change the text size in a Label widget?
I have managed to create this.
I am using an oval with a different shade to create this.
def Banner():
canvas.create_oval(-300, 1600, 4000, 200, fill="gray38", outline="gray38", width=4)
banner_label = Label (canvas, width=30, height=2, font=font3, text = "FITNESS FIRST", bg="gray30", fg = "white")
canvas_banner_label = canvas.create_window(500, 200, window=banner_label)
However i was wondering if theres anyway i could get the oval to almost take priority, and overlap the Label so that the oval is in front of it, allowing the pattern to be visible all the way through
The problem with using Label() on the canvas is the label itself has its own background and will always be at the same level as the text so you cannot overlap your canvas image behind the text. However canvas has a method called create_text that will draw the text directly on the canvas instead of using a label.
Here is an example using create_text for canvas.
In the create_text method the first 2 arguments are coordinates then all you need is the text font and fill is the color.
import tkinter as tk
root = tk.Tk()
canvas = tk.Canvas(root, width=800, height=650, bg="darkgrey")
canvas.create_oval(-300, 1600, 4000, 200, fill="gray38", outline="gray38", width=4)
canvas.create_text(400,325, text="FITNESS FIRST", font=("Purisa", 60),fill="white")
canvas.pack()
root.mainloop()
Results: