I overlapped 5 Tk.Canvas objects and each will have different images. I want to bring each canvas to front of every other canvases to draw pictures in the most-front canvas.
class window_tk():
def __init__(self,main):
self.main=main
self.canvas_org = tk.Canvas(self.main, bg='white')
self.canvas_layer1 = tk.Canvas(self.main, bg='red')
self.canvas_layer2 = tk.Canvas(self.main, bg='green')
self.canvas_layer3 = tk.Canvas(self.main, bg='blue')
self.canvas_layer4 = tk.Canvas(self.main, bg='black')
self.btn_load = tk.Button(self.main,text = "Load Image",command = self.load_ct)
self.btn_layer1 = tk.Button(self.main,text = "Draw in L1",command = self.bring_1)
self.btn_layer2 = tk.Button(self.main,text = "Draw in L2",command = self.bring_2)
self.btn_layer3 = tk.Button(self.main,text = "Draw in L3",command = self.bring_3)
self.btn_layer4 = tk.Button(self.main,text = "Draw in L4",command = self.bring_4)
def bring_1(self):
self.canvas_layer1.place(x=50,y=00)
def bring_2(self):
self.canvas_layer2.place(x=50, y=00)
def bring_3(self):
self.canvas_layer3.place(x=50, y=00)
def bring_4(self):
self.canvas_layer4.place(x=50, y=00)
I thought the canvas.place() function will bring the canvas front but it was not. Which function can I use ? Or should I unpack all other canvases ?
Since Canvas has override the .tkraise() function, you need to call TCL command directly:
self.canvas.tk.call('raise', self.canvas._w)
Please see the answer given by acw1668. The lift function doesn't work for Canvas objects. His answer is correct.
All tkinter objects, Canvas included, support the following method:
w.lift(aboveThis=None)
If the argument is None, the window containing w is moved to the top of the window stacking order. To move the window just above some Toplevel window w, pass w as an argument.
This gives you full control over which widget sits on top.
https://anzeljg.github.io/rin2/book2/2405/docs/tkinter/universal.html
Now that I re-read that, I see that its language is slightly incorrect. "w" is any tkinter widget, "above_this" is another tkinter widget. The function places "w" above "above_this" in the stacking order.
You can use the following functions -
canvas.tag_raise(canvas_layer4) -> For bringing to front
canvas.tag_lower(canvas_layer4) -> For pushing back
Related
I want to update the position of a label, so I use .update() method, after I replace it with .place() method. The problem is that all my widgets, that are on my window, get updated and I do not want this, because the program is working harder, and I see lag while "moving" my label. What can I do?
...
def update_label:
l.place(relx = 0.2, rely = 0.1+0.2)
l.update()#here the program is updating every widget
l=tk.Label(root)
l.place(relx = 0.2, rely = 0.1)
b=Button(root,command(update_label()))
b.pack()
...
In fact, I want to replace more than one label in update_label function, but I wanted to make the example easier to understand.
You can use the .update() method, but there are a few things wrong with your code.
First off, you use the tk attribute with the label, but not with the button. Try to be consistent.
I reworked your code and made it cleaner. It now works:
import tkinter as tk
root = tk.Tk()
root.geometry("500x500")
x = 0.2
y = 0.1
l = tk.Label(root, text = "label")
l.place(relx = x, rely = y)
def update_label():
global x, y
y += 0.2
l.place(relx = x, rely = y)
l.update()#here the program is updating every widget
b = tk.Button(root,text = "update", command = update_label)
b.pack()
Hope this helps!
Edit:
Writing l.update() won't update or move any other widgets. If you wish to move/update all widgets, then you must put them in the update_label() function.
Hope this helps!
To update the position of a single widget, you can use the place_forget() method to temporarily remove it and then call its place() method (again) with new values to reposition it. Since it appears you want to update the position based on the where the widget currently is, information about the widget's current position is retrieved from it first using the place_info() widget method.
Here's a runnable example based on the code in your question that illustrates what I'm suggesting:
import tkinter as tk
root = tk.Tk()
root.geometry("800x600")
def update_label(lbl):
info = lbl.place_info() # Get dictionary of widget's current place options.
cur_relx = float(info['relx']) # Get current value of relative x.
cur_rely = float(info['rely']) # Get current value of relative y.
lbl_1.place_forget() # Remove widget from current manager.
lbl_1.place(relx=cur_relx, rely=cur_rely+0.2) # Add it back with updated y position.
lbl_1 = tk.Label(root, text='Label 1')
lbl_1.place(relx=0.2, rely=0.1)
lbl_2 = tk.Label(root, text='Label 2')
lbl_2.place(relx=0.2, rely=0.2)
btn_1 = tk.Button(root, text='Update', command=lambda lbl=lbl_1: update_label(lbl))
btn_1.pack()
root.mainloop()
I'm trying to make a page for my tkinter GUI and when I try to put a button to go to the previous page at the bottom left corner it never seems to work.
I have created a frame to put inside the window, packing it to the left side, then packing the button in that frame to the bottom of it. I have also tried it the other way round, so frame at the bottom and the button on the left. Changing the values of the size of the frame seems to do nothing.
previousPageF = tk.Frame(self, height = "100", width = "500")
previousPageF.pack(side = "bottom")
previousPageB = tk.Button(previousPageF, font = BASIC_FONT, text = "<--", command = lambda: controller.show_frame(LoginPage))
previousPageB.pack(side = "left")
Picture of the results of the code
I want it to be the very bottom left corner and not in the position it is currently in. I would like to use pack as it is not a very complicated GUI design and pack also puts it automatically in the centre of the window, whereas with grid I would have to do some maths to get it perfectly in the centre. Furthermore, I have done all of my other work with pack so I would prefer not to change all of it just to get this to work.
Figured it out with trial and error.
previousPageB = tk.Button(self, font = BASIC_FONT, text = "<--", command = lambda: controller.show_frame(LoginPage))
previousPageB.pack(anchor = "w", side = "bottom")
This will result in the button being in the bottom left corner:
enter image description here
It will also work if you do:
previousPageB = tk.Button(self, font = BASIC_FONT, text = "<--", command = lambda: controller.show_frame(LoginPage))
previousPageB.pack(anchor = "s", side = "left")
You don't have to use frame for this.You can directly mention fill='none' and side='left' in pack to place widget to left in window.
import tkinter as tk
root = tk.Tk()
previousPageB = tk.Button(root, text = "<--", command = lambda:
controller.show_frame(LoginPage))
previousPageB.pack(fill='none',side = "left")
For a project I need to specify a certain value for N subfiles (sets of data), and this value can either be evenly spaced (omitted here), requiring only a starting value and an increment, or unevenly spaced, which means each subfile has its own value. I've decided to use a Notebook to separate the two methods of entry.
As the number of subfiles can get into hundreds, I would need a scrollbar, and after consulting Google I've found out that to use a scrollbar in such manner I would need to use a canvas and place a frame in it with everything I would want to scroll through.
The number can vary each time, so I decided to use a dictionary, that would be iteratively filled, to contain all 'entry frames' that each contain a label, an entry field and a variable, rolled up into one custom class IterEntryField. After a class instance is created, it's packed inside one container frame. After the for loop is over, the container frame is placed on a canvas and the scrollbar is given a new scrollregion.
from tkinter import *
from tkinter.ttk import Notebook
N = 25
class IterEntryField:
def __init__(self, frame, label):
self.frame = frame
self.label = label
def pack(self):
self.valLabel = Label(self.frame, text = self.label, anchor = 'w')
self.valLabel.pack(fill = X, side = LEFT)
self.variable = StringVar()
self.variable.set('0')
self.valEntry = Entry(self.frame, textvariable = self.variable)
self.valEntry.pack(fill = X, side = RIGHT)
def notebookpopup():
zSetupWindow = Toplevel(root)
zSetupWindow.geometry('{}x{}'.format(800, 300))
notebook = Notebook(zSetupWindow)
evspace = Frame(notebook)
notebook.add(evspace, text = "Evenly spaced values")
sOverflow = Label(evspace, text = 'Ignore this')
sOverflow.pack()
uevspace = Frame(notebook)
notebook.add(uevspace, text = "Individual values")
canvas = Canvas(uevspace, width = 800, height = 400)
vsb = Scrollbar(canvas, command=canvas.yview)
canvas.config(yscrollcommand = vsb.set)
canvas.pack(side = LEFT, fill = BOTH, expand = True)
vsb.pack(side = RIGHT, fill = Y)
entryContainer = Frame(canvas)
entryContainer.pack(fill = BOTH)
frameDict = {}
for i in range(0, N):
frameDict[i] = Frame(entryContainer)
frameDict[i].pack(fill = X)
entry = IterEntryField(frameDict[i], 'Z value for subfile {}'.format(i+1))
entry.pack()
canvas.create_window(200, 0, window = entryContainer)
canvas.config(scrollregion = (0,0,100,1000))
notebook.pack(fill = X)
root = Tk()
button = Button(root, text = 'new window', command = notebookpopup)
button.pack()
root.mainloop()
I'm having three problems with this code:
The pages are incredibly short, only showing a couple lines.
I can't figure out the "proper" offset in create_window. I thought 0, 0 would place it in upper left corner of the canvas, but apparently the upper left corner of the window is taken instead. This could probably fixed by some reverse of the canvasx and canvasy methods, but I haven't been able to find any.
The entry fields and labels are cramped together instead of taking up the entire width of the canvas. This wasn't a problem when I only used the notebook page frame as the container.
Your first problem goes back to how you pack your notebook. Simply change notebook.pack(...) to below:
notebook.pack(fill="both", expand=True)
The second one can be solved by specifying the anchor position in your create_window method:
canvas.create_window(0, 0, window = entryContainer, anchor="nw")
I don't understand what the 3rd problem is - it looks exactly as expected.
I'm trying to add a nice handle to Tkinter.PanedWindow sash. To do that I place a Label with a custom grip image next to a pane. Example:
from Tkinter import *
root = Tk()
pw = PanedWindow(root, orient=HORIZONTAL)
l1 = Listbox(pw)
pw.add(l1)
l2 = Listbox(pw)
pw.add(l2)
pw.pack(fill=BOTH, expand=1)
gripimg = PhotoImage(data="R0lGODlhBAAvAPEAALetnfXz7wAAAAAAACH5BAEAAAIALAAAAAAEAC8AAAIjRBwZwmKomjsqyVdXw/XSvn1RCFlk5pUaw42saL5qip6gnBUAOw==")
griplabel = Label(pw, image=gripimg)
griplabel.place(relx=1, rely=0.5, anchor=W, in_=l1)
root.mainloop()
It looks ok. But now the Label overlaps the sash, steals mouse events and I can't resize PanedWindow by dragging the Label. How can I make griplabel ignore mouse events and redirect them all to the PanedWindow sash?
I tried bindtags, but:
griplabel.bindtags(pw.bindtags())
does not seem to do anything, i.e. I still can't drag the Label to resize PanedWindow.
Or is there a better way to create a custom handle for PanedWindow?
With a kind help of #tcl freenode channel I came up with this:
from Tkinter import *
root = Tk()
pw = PanedWindow(root, orient=HORIZONTAL)
l1 = Listbox(pw)
pw.add(l1)
l2 = Listbox(pw)
pw.add(l2)
pw.pack(fill=BOTH, expand=True)
gripimg = PhotoImage(data="R0lGODlhBAAvAPEAALetnfXz7wAAAAAAACH5BAEAAAIALAAAAAAEAC8AAAIjRBwZwmKomjsqyVdXw/XSvn1RCFlk5pUaw42saL5qip6gnBUAOw==")
griplabel = Label(pw, image=gripimg, cursor="sb_h_double_arrow")
griplabel.place(relx=1, rely=0.5, anchor=W, in_=l1)
griplabel.bind("<Button-1>", lambda e:pw.event_generate("<Button-1>",x=e.x+griplabel.winfo_x(),y=e.y+griplabel.winfo_y()))
griplabel.bind("<B1-Motion>", lambda e:pw.event_generate("<B1-Motion>",x=e.x+griplabel.winfo_x(),y=e.y+griplabel.winfo_y()))
root.mainloop()
Two griplabel.bind(...) calls forward mousedown+mousemove events from Label to PanedWindow, adjusting x and y coords. Those two events are enough to make the sash move.
And griplabel mouse "cursor" is set to sb_h_double_arrow as that's the cursor PanedWindow uses for sash by default, according to the Tk documentation:
Command-Line Name: -sashcursor
Mouse cursor to use when over a sash. If null, sb_h_double_arrow will be used for horizontal panedwindows, and sb_v_double_arrow will be used for vertical panedwindows.
And it's also one of cursor names recognized by Tk on all platforms.
TCL wiki mentions another way to set a custom sash handle bar, using ttk.PanedWindow with custom ttk.Style layout:
from Tkinter import *
import ttk
root = Tk()
gripimg = PhotoImage(data="R0lGODlhBAAvAPEAALetnfXz7wAAAAAAACH5BAEAAAIALAAAAAAEAC8AAAIjRBwZwmKomjsqyVdXw/XSvn1RCFlk5pUaw42saL5qip6gnBUAOw==")
style = ttk.Style()
style.element_create("Sash.xsash", "image", gripimg, sticky=W+E)
style.layout("MySash.TPanedWindow", [('Sash.xsash', {})])
pw = ttk.PanedWindow(root, orient=HORIZONTAL, style="MySash.TPanedWindow")
l1 = Listbox(pw)
pw.add(l1)
l2 = Listbox(pw)
pw.add(l2)
pw.pack(fill=BOTH, expand=True)
root.mainloop()
But it looks and works differently. Essentially it replaces background of ttk.PanedWindow with a tiled image, which remains static, and the sash becomes a viewport sliding over it. That looks unusual, still someone may like it.
I am quite new to Tkinter, but, nevertheless, I was asked to "create" a simple form where user could provide info about the status of their work (this is sort of a side project to my usual work).
Since I need to have quite a big number of text widget (where users are required to provide comments about status of documentation, or open issues and so far), I would like to have something "scrollable" (along the y-axis).
I browsed around looking for solutions and after some trial and error I found something that works quite fine.
Basically I create a canvas, and inside a canvas a have a scrollbar and a frame. Within the frame I have all the widgets that I need.
This is a snipet of the code (with just some of the actual widgets, in particular the text ones):
from Tkinter import *
## My frame for form
class simpleform_ap(Tk):
# constructor
def __init__(self,parent):
Tk.__init__(self,parent)
self.parent = parent
self.initialize()
#
def initialize(self):
#
self.grid_columnconfigure(0,weight=1)
self.grid_rowconfigure(0,weight=1)
#
self.canvas=Canvas(self.parent)
self.canvas.grid(row=0,column=0,sticky='nsew')
#
self.yscrollbar = Scrollbar(self,orient=VERTICAL)
self.yscrollbar.grid(column =4, sticky="ns")
#
self.yscrollbar.config(command=self.canvas.yview)
self.yscrollbar.pack(size=RIGTH,expand=FALSE)
#
self.canvas.config(yscrollcommand=self.yscrollbar.set)
self.canvas.pack(side=LEFT,expand=TRUE,fill=BOTH)
#
self.frame1 = Frame(self.canvas)
self.canvas.create_window(0,0,window=self.frame1,anchor='nw')
# Various Widget
# Block Part
# Label
self.labelVariableIP = StringVar() # Label variable
labelIP=Label(self.frame1,textvariable=self.labelVariableIP,
anchor="w",
fg="Black")
labelIP.grid(column=0,row=0,columnspan=1,sticky='EW')
self.labelVariableIP.set(u"IP: ")
# Entry: Single line of text!!!!
self.entryVariableIP =StringVar() # variable for entry field
self.entryIP =Entry(self.frame1,
textvariable=self.entryVariableIP,bg="White")
self.entryIP.grid(column = 1, row= 0, sticky='EW')
self.entryVariableIP.set(u"IP")
# Update Button or Enter
button1=Button(self.frame1, text=u"Update",
command=self.OnButtonClickIP)
button1.grid(column=2, row=0)
self.entryIP.bind("<Return>", self.OnPressEnterIP)
#...
# Other widget here
#
# Some Text
# Label
self.labelVariableText = StringVar() # Label variable
labelText=Label(self.frame1,textvariable=
self.labelVariableText,
anchor="nw",
fg="Black")
labelText.grid(column=0,row=curr_row,columnspan=1,sticky='EW')
self.labelVariableTexta.set(u"Insert some texts: ")
# Text
textState = TRUE
self.TextVar=StringVar()
self.mytext=Text(self.frame1,state=textState,
height = text_height, width = 10,
fg="black",bg="white")
#
self.mytext.grid(column=1, row=curr_row+4, columnspan=2, sticky='EW')
self.mytext.insert('1.0',"Insert your text")
#
# other text widget here
#
self.update()
self.geometry(self.geometry() )
self.frame1.update_idletasks()
self.canvas.config(scrollregion=(0,0,
self.frame1.winfo_width(),
self.frame1.winfo_height()))
#
def release_block(argv):
# Create Form
form = simpleform_ap(None)
form.title('Release Information')
#
form.mainloop()
#
if __name__ == "__main__":
release_block(sys.argv)
As I mentioned before, this scripts quite does the work, even if, it has a couple of small issue that are not "fundamental" but a little annoying.
When I launch it I got this (sorry for the bad screen-capture):
enter image description here
As it can be seen, it only shows up the first "column" of the grid, while I would like to have all them (in my case they should be 4)
To see all of the fields, I have to resize manually (with the mouse) the window.
What I would like to have is something like this (all 4 columns are there):
enter image description here
Moreover, the scrollbar does not extend all over the form, but it is just on the low, right corner of the windows.
While the latter issue (scrollbar) I can leave with it, the first one is a little more important, since I would like to have the final user to have a "picture" of what they should do without needing to resize the windows.
Does any have any idea on how I should proceed with this?
What am I missing?
Thanks in advance for your help
In the __init__ method of your class, you do not appear to have set the size of your main window. You should do that, or it will just set the window to a default size, which will only show whatever it can, and in your case, only 1 column. Therefore, in the __init__ method, try putting self.geometry(str(your_width) + "x" + str(your_height)) where your_width and your_height are whatever integers you choose that allow you to see what you need to in the window.
As for your scrollbar issue, all I had to do was change the way your scrollbar was added to the canvas to a .pack() and added the attributes fill = 'y' and side = RIGHT to it, like so:
self.yscrollbar.pack(side = 'right', fill = 'y')
Also, you don't need:
self.yscrollbar.config(command=self.canvas.yview)
self.yscrollbar.pack(size=RIGHT,expand=FALSE)
Just add the command option to the creation of the scrollbar, like so:
self.scrollbar = Scrollbar(self,orient=VERTICAL,command=self.canvas.yview)
In all, the following changes should make your code work as expected:
Add:
def __init__(self,parent):
Tk.__init__(self,parent)
self.parent = parent
self.initialize()
# Resize the window from the default size to make your widgets fit. Experiment to see what is best for you.
your_width = # An integer of your choosing
your_height = # An integer of your choosing
self.geometry(str(your_width) + "x" + str(your_height))
Add and Edit:
# Add `command=self.canvas.yview`
self.yscrollbar = Scrollbar(self,orient=VERTICAL,command=self.canvas.yview)
# Use `.pack` instead of `.grid`
self.yscrollbar.pack(side = 'right', fill = 'y')
Remove:
self.yscrollbar.config(command=self.canvas.yview)
self.yscrollbar.pack(size=RIGHT,expand=FALSE)