Tkinter Canvas Problems - python

I'm trying to change the layering of Tkinter Canvas widgets. With most widgets you can force the widget above other widgets by using the lift method. However, if I try the same on a Canvas widget I get an error.
Error :
TypeError: tag_raise() got an unexpected keyword argument 'aboveThis'
An Example of my Problem :
import Tkinter as Tk
root = Tk.Tk()
w, h = 200, 200
a = Tk.Canvas(root, bg='red', width=w, height=h)
a.grid(column=0, row=0)
b = Tk.Canvas(root, bg='blue', width=w, height=h)
b.grid(column=0, row=0)
a.lift(aboveThis=None)
root.mainloop()
If I do the same thing with Frame widgets, it works.
Example:
import Tkinter as Tk
root = Tk.Tk()
w, h = 200, 200
a = Tk.Frame(root, bg='red', width=w, height=h)
a.grid(column=0, row=0)
b = Tk.Frame(root, bg='blue', width=w, height=h)
b.grid(column=0, row=0)
a.lift(aboveThis=None)
root.mainloop()

The canvas lift() method is an alias for tag_raise(), which is used to raise not the canvas itself but entities within the canvas.
I found this comment within the Tkinter.py source code:
# lower, tkraise/lift hide Misc.lower, Misc.tkraise/lift,
# so the preferred name for them is tag_lower, tag_raise
# (similar to tag_bind, and similar to the Text widget);
# unfortunately can't delete the old ones yet (maybe in 1.6)
If you replace a.lift(aboveThis=None) with Tk.Misc.lift(a, aboveThis=None) then the canvas widget is raised correctly.

I came to this question because I was actually wanting to implement the equivalent of a tk statement like
canvas-pathName raise tagOrId ?aboveThis?
in order to raise individual canvas items to a particular z-position. For those interested in the same thing, I'll just post my realization (after a little head-scratching) that this can be done in Python quite easily:
canvasObject.tag_raise(tagOrId, tagOrId2)
The second argument here just gets incorporated into the tk command line, and is then interpreted as an "aboveThis" value.

Related

Buttons not being placed inside the correct frame in tkinter

I am a newbie trying to use tkinter to build a GUI for an application. So far, I have a frame that I'd like to put several buttons into. However, every time I attempt to position this button, it isn't placed properly, being put outside of the frame itself. I wouldn't like to use the place function because of the several buttons I have to dynamically generate coming from an excel sheet so I was hoping to use the grid function instead.
Here is what I have so far
from tkinter import *
from customtkinter import *
window = Tk()
window.geometry("1920x1080")
window.state("zoomed")
window.title("My Company's Description Printer")
main_frame = CTkFrame(window, width=1920, height=1080, fg_color="grey21")
main_frame.place(x=0, y=0)
title = Label(main_frame,
text="My Company",
bg="grey21",
fg="white",
font=("Trajan Pro", 20)).place(x=626, y=30)
button_frame = CTkCanvas(main_frame,
width=800,
height=600,
highlightthickness=3,
highlightbackground="black",
relief="ridge",
bg="grey19").place(x=60, y=110)
test_button = CTkButton(button_frame, text="test").grid(row=0, column=0)
window.mainloop()
Example of code being ran
As you can see, the button is being placed in the top left corner of the entire window rather than the top left corner of the black bordered button frame. Any help would be appreciated. Thank you so much.
Note that button_frame is None because it is the result of .place(...), so the button (test_button is None as well due to same reason) is a child of the root window instead of the instance of CTkCanvas. .place(...) should be called in separate line.
Also .create_window() is used instead of tkinter layout manager to put widget into a canvas:
...
button_frame = CTkCanvas(main_frame,
width=800,
height=600,
highlightthickness=3,
highlightbackground="black",
relief="ridge",
bg="grey19")
# call .place(...) in separate line
button_frame.place(x=60, y=110)
test_button = CTkButton(button_frame, text="test") # don't use .grid(row=0, column=0)
# use .create_window() to put widget into canvas
button_frame.create_window(0, 0, window=test_button, anchor="nw")

Tkinter: cannot use geometry manager grid inside .!frame2 which already has slaves managed by pack

This is my first time using tkinter and I already did some research on pack and grid. How do I fix this code so that the pack and grid components don't intertwine?
I want to use grid for my checkbox so that 16 checkboxes show up in a column next to the words corresponding to them. Can I do this with pack?
# tkinter will help us with the GUI
import tkinter as tk
from tkinter import filedialog, Text
import os
def data():
categoriesArray = ["16 words here"]
for i in range(16):
checkbox = tk.Checkbutton(buttonFrame, bg="white")
checkbox.grid(row=i, column=0, sticky="w")
tk.Label(canvasFrame, text=categoriesArray[i]).grid(row=i, column=1, sticky="ew")
# Define the scrolling function for the scrollbar
def scrollFunction(event):
canvas.configure(scrollregion=canvas.bbox("all"), width=200, height=500)
# The root holds the whole app structure. Always attach to root.
root = tk.Tk()
# These two lines literally make the rectangular structure of the app.
canvas = tk.Canvas(root, height = 500, width= 1300, bg="#00008B")
canvas.pack()
# These two lines make the white screen you see on the left of the buttons.
frame = tk.Frame(root, bg="white")
frame.place(relwidth=0.8, relheight=0.8, relx=0.03, rely=0.1)
# This is the frame for the buttons on the right
buttonFrame = tk.Frame(root, bg="white")
buttonFrame.place(relwidth=0.13, relheight=0.8, relx=0.85, rely=0.1)
# You need a canvas to define a scrollbar within the app.
# Resource: https://stackoverflow.com/questions/16188420/tkinter-scrollbar-for-frame
canvas=tk.Canvas(buttonFrame)
canvasFrame=tk.Frame(canvas)
scrollbar=tk.Scrollbar(buttonFrame, orient="vertical", command=canvas.yview)
canvas.configure(yscrollcommand=scrollbar.set)
scrollbar.pack(side="right", fill="y")
canvas.pack(side="left")
canvas.create_window((36,0), window=canvasFrame, anchor='nw')
canvasFrame.bind("<Configure>", scrollFunction)
# Call the data for the categories to show on the right
data()
# This runs the mainframe to work
root.mainloop()
Please let me know anything I can do to make my question better.
Places I've looked but gotten confused: fix this code 'cannot use geometry manager grid inside . which already has slaves managed by pack'
I fixed it. checkbox = tk.Checkbutton(buttonFrame, bg="white") should have canvasFrame instead of buttonFrame.

How to add a slide bar in a table?

I've been creating an app where there are Clients that I can add to a table, the problem is, I need a scrollbar to scroll through all the clients since the app Height is limited and the clients aren't.
Using tkinter I found a way to create a "table" using Entry and grid, but what if I want to create 100 rows? they would be outside of the view, so that's why the need of a scrollbar.
For those of you who know Java, I wanted to create something similar to Jtable, it has a method to create row, delete row, and it generates automatically that scrollbar as soon as the JTable runs out of space.
I've tried to use TkTable from ttk and mess around with some properties, but I preferred how Entries look.
root = Tk()
root.geometry("1200x900")
for i in range(10):
e = Entry(relief=RIDGE)
e.grid(row=i, column=2, sticky=N)
root.mainloop()
I created a root = Tk() and used root to grid them.
You'll see 10 Entries on top of the other.
When a window contains many widgets, they might not all be visible. However, neither a window (Tk or Toplevel instance) nor a Entry are scrollable.
One solution to make the window content scrollable is to put all the widgets in a Frame, and then, embed this Frame in a Canvas using the create_window method.
from tkinter import *
root = Tk()
canvas = Canvas(root)
scroll_y = Scrollbar(root, orient="vertical", command=canvas.yview)
frame = Frame(canvas)
# group of widgets
for i in range(100):
e = Entry(frame, relief=RIDGE, width = 100)
e.grid(row=i, column=2, sticky=N)
# put the frame in the canvas
canvas.create_window(0, 0, anchor='nw', window=frame)
# make sure everything is displayed before configuring the scrollregion
canvas.update_idletasks()
canvas.configure(scrollregion=canvas.bbox('all'),
yscrollcommand=scroll_y.set)
canvas.pack(fill='both', expand=True, side='left')
scroll_y.pack(fill='y', side='right')
root.mainloop()
output:

Adding widgets over canvas in tkinter

I want to put a small image and other widgets over a canvas on which an image is displayed. I've tried options such ascompound and other things.
Background picture is fine and the small image that I want to put over the background image shows fine but it's always top or bottom of the window. I want it to be placed over any area of background image. I've tried many options of all the geometry manager (pack, grid, place) but none of them works. Please help, here's my code :
from Tkinter import *
root = Tk()
root.iconbitmap('E:/a.ico')
root.title('Unick Locker')
canvas = Canvas(root, width=730, height=600)
canvas.grid()
bgImg = PhotoImage(file="E:/a.gif")
canvas.create_image(370, 330, image=bgImg)
login = PhotoImage(file="E:/login.gif")
lo = Label(root, image=login)
lo.grid()
root.mainloop()
In order to add any widgets over or the foreground of any background image or canvas, the row and column values of all the widgets must be same as of the background image. so, my above mentioned program would be like this :
from Tkinter import *
root = Tk()
root.iconbitmap('E:/a.ico')
root.title('Unick Locker')
canvas = Canvas(root, width=730, height=600)
canvas.grid(row=0, column=0)
bgImg = PhotoImage(file="E:/a.gif")
canvas.create_image(370, 330, image=bgImg)
login = PhotoImage(file="E:/login.gif")
lo = Label(root, image=login)
lo.grid(row=0, column=0)
root.mainloop()
I tried putting the same row and column values to the widgets in grid() methods which I wanted to put over the image, and it worked fine as I wanted :-)
Have you considered using the paste method, which lets you define the position of the pasted image through a box argument?
See http://effbot.org/imagingbook/imagetk.htm.
Please also take a look at this thread: Tkinter, overlay foreground image on top of a background image with transparency, which seems very similar to your issue.
You are looking to draw the widgets over the canvas, this means you must specify the canvas as the parent widget, not the root as you did. For that, modify lo = Label(root, image=login) to lo = Label(canvas, image=login)
Also, do not forget to specify the rows and columns where you want to position the different widgets. This means you need to write, for example, lo.grid(row=0, column=0) instead of lo.grid(). For the moment you do not see big problems because you have only one label widget. But if you try to add an other widget without mentioning the exact positions (rows and columns) you will get unexpected results.
This question isn't about images at all, it's just a basic layout problem. You'll have the same issues with or without images. The problem is simply that you aren't giving any options to grid, so it naturally puts things at the top. Tkinter also has the behavior that a containing widget (eg: your canvas) will shrink or expand to exactly fit its contents.
Here's a version that creates several widgets over a background image. Notice the use of options to pack and grid, and the use of grid_rowconfigure and grid_columnconfigure to specify how extra space is allocated.
from Tkinter import *
root = Tk()
canvas = Canvas(root, width=730, height=600)
canvas.pack(fill="both", expand=True)
bgImg = PhotoImage(file="E:/a.gif")
canvas.create_image(370, 330, image=bgImg)
l1 = Label(canvas, text="Hello, world")
e1 = Entry(canvas)
t1 = Text(canvas)
l1.grid(row=0, column=0, sticky="ew", padx=10)
e1.grid(row=1, column=1, sticky="ew")
t1.grid(row=2, column=2, sticky="nsew")
canvas.grid_rowconfigure(2, weight=1)
canvas.grid_columnconfigure(2, weight=1)
root.mainloop()

In tkinter, the canvas size settings seem to stop working when the canvas is used as a master for another widget. Is there a way to get around this?

So, this is a simplified form of the problem I'm facing:
The following code produces a canvas whose size is just big enough to accomidate the label, even though it was configured to be 400 by 400
import tkinter as tk
root = tk.Tk()
canvas = tk.Canvas(root, width=400, height=400)
canvas.pack()
label = tk.Label(canvas, text='foo')
label.pack()
If the last two lines are omitted, then the canvas is the size that I would expect it to be (400 by 400), but it does not contain the label, which is something that I want it to contain.
As I said, this is a simplified form of my problem, I actually want to put several widgets into the canvas, but this behavior is the cause of my problem. Is there a way to make the canvas the size that I want (in pixels) and use it as the master for other widgets?
What you can do is setting the size of the root window:
import tkinter as tk
root = tk.Tk()
root.geometry('400x400')
canvas = tk.Canvas(root)
canvas.pack()
label = tk.Label(canvas, text='foo')
label.pack()
root.mainloop()
If you can use the grid method, using row/column configure and sticky argument will do the rest of the job. I don't know pack enough.
You could also do this :
root = tk.Tk()
root.wm_minsize(400,400)
label = tk.Label(root, text='foo')
label.pack()
But you would loose the ability to resize the window under 400x400.
There is also this possibility but i'm not sure that it works:
root = tk.Tk()
root.resizable(width=False, height=False)
label = tk.Label(canvas, text='foo')
label.pack()

Categories