Adding widgets over canvas in tkinter - python

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()

Related

How to explicitly resize frames in tkinter?

so i'm currently working on a piece of code that'll be integrating into something else later to act as a settings configurator. For the time being, i want to have a window that is laid out like you see below:
where each coloured box is a frame. This window is not resizable and will always be 480x720 pixels. As such, i want the 3 frames im using, sideBar(yellow), container (blue) and static(red) to always remain the same size and fill the window as pictured above with roughly the same ratios (doesn't need to be exact).
The code for this window is below
self.window = tk.Tk()
self.windowHeight = 480
self.windowLength = 720
self.windowDimensions = str(self.windowLength)+"x"+str(self.windowHeight) #make diemnsions string; dimensions are set as a single string
self.window.geometry(self.windowDimensions)
self.window.resizable(width=False, height=False)
self.container = tk.Frame(self.window, relief="sunken", borderwidth=2) #instantiate new window
self.sideBar = tk.Frame(self.window, relief="sunken", borderwidth=2)
self.static = tk.Frame(self.window, relief="sunken", borderwidth=2)
self.sideBar.grid_propagate(False)
self.sideBar.grid(row=0, column=0)
self.container.grid(row=0,column=1)
self.static.grid(row=5, column=1)
self.configuratorObject = configuratorObject
audioButton = tk.Button(self.sideBar, text="Audio Page", command=lambda: self.raisePage("audioPage"))
colourButton = tk.Button(self.sideBar, text="Colours", command=lambda: self.raisePage("coloursPage"))
saveButton = tk.Button(self.static, text = "Save", state="disabled")
applyButton = tk.Button(self.static, text = "Apply", state="disabled")
audioButton.pack()
colourButton.pack()
saveButton.pack()
applyButton.pack()
I've attempted to change the height and width parameters of the grids, but they really don't seem to be doing anything. So how could i go about explicitly defining the layout and sizes of the frames?
Any help is appreciated
In the comments you wrote
If theres a way of getting tkinter to do it then that'd be great
That is definitely the preferred way, over forcing widgets to be a particular size.
We'll start by using pack instead of grid for the three frames. For such a basic layout it requires fewer lines of code than grid.
self.sideBar.pack(side="left", fill="y")
self.static.pack(side="bottom", fill="x")
self.container.pack(side="top", fill="both", expand=True)
Next, add the buttons on the left. This will cause the left frame to shrink in width to fit the buttons. Because we used fill="y", the height will be forced to remain the full height of the window.
audioButton.pack(side="top", fill="x")
colourButton.pack(side="top", fill="x")
Finally, add the buttons on the bottom. Your original code shows them stacked top-to-bottom but your illustration shows them in a single horizontal row. This example adheres to the illustration.
applyButton.pack(side="right", padx=10)
saveButton.pack(side="right", padx=10)
With that we end up with a window that looks like the following, and the proportions and orientation stays exactly the same when you resize the window:
Note: you can do this with grid too, but it requires a few more lines of code to apply weights to the rows and columns. I personally prefer pack when the layout doesn't naturally fit in a grid since it requires fewer lines of code.

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:

How to add a specific spacing in pixels between tkinter buttons?

I have written some code for some buttons. However, I am not sure how to add a specific number of pixels of spacing for each button. So far is the code I have written. However, I have not yet figured out a reliable way to add spacing between the buttons in pixel sizes.
import tkinter as tk
#from tkinter import PhotoImage
def banana():
print ("Sundae")
def tomato():
print ("Ketchup")
def potato():
print ("Potato chips")
root = tk.Tk()
root.geometry("960x600")
f1 = tk.Frame(root, width=70, height=30)
f1.grid(row=3, column=0, sticky="we")
button_qwer = tk.Button(f1, text="Banana", command=banana)
button_asdf = tk.Button(f1, text="Tomato", command=tomato)
button_zxcv = tk.Button(f1, text="Potato", command=potato)
button_qwer.grid(row=0, column=0)
button_asdf.grid(row=0, column=1)
button_zxcv.grid(row=0, column=2)
root.mainloop()
Adding space between widgets depends on how you are putting the widgets in the window. Since you are using grid, one simple solution is to leave empty columns between the buttons, and then give these columns a minsize equal to the space you want.
Example:
f1.grid_columnconfigure((1, 3), minsize=10, weight=0)
button_qwer.grid(row=0, column=0)
button_asdf.grid(row=0, column=2)
button_zxcv.grid(row=0, column=4)
Using a specific number of pixels of spacing between each Buttondoesn't sound to me like such as good idea because it isn't very flexible nor easily portable to devices with different resolutions.
Nevertheless I've figured-out a way of doing it—namely by putting a do-nothing invisible button between of the each real ones. This got somewhat involved, mostly because it requires putting an image on each Button used this way so its width option argument will be interpreted as number of pixels instead of number of characters (here's some documentation describing the various Button widget configuration options).
import tkinter as tk
# Inline XBM format data for a 1x1 pixel image.
BITMAP = """
#define im_width 1
#define im_height 1
static char im_bits[] = {
0x00
};
"""
root = tk.Tk()
root.geometry("960x600")
bitmap = tk.BitmapImage(data=BITMAP, maskdata=BITMAP)
f1 = tk.Frame(root, width=70, height=30)
f1.grid(row=3, column=0, sticky=tk.EW)
def banana():
print ("Sundae")
def tomato():
print ("Ketchup")
def potato():
print ("Potato chips")
def layout_buttons(parent, buttons, spacing):
if buttons:
first, *rest = buttons
first.grid(row=0, column=0) # Position first Button.
for index, button in enumerate(rest, start=1):
col = 2*index
# Dummy widget to separate each button from the one before it.
separator = tk.Button(parent, relief=tk.FLAT, state=tk.ACTIVE,
image=bitmap, borderwidth=0, highlightthickness=0,
width=spacing)
separator.grid(row=0, column=col-1)
button.grid(row=0, column=col)
buttons = (
tk.Button(f1, text="Banana", command=banana),
tk.Button(f1, text="Tomato", command=tomato),
tk.Button(f1, text="Potato", command=potato),
)
layout_buttons(f1, buttons, 30)
root.mainloop()
Result:
Here's a blow-up showing that the spacing is exactly 30 pixels (as counted in my image editor and indicated by the thin horizontal black line between the adjacent edges of the two Buttons).

Scrollbar in Tkinter inside Text widget

I have problem with set in Scrollbar inside Text widget in Tkinter. I know, that it's preferable to use grid to locate widgets but I want to set my widget in absolute location (x,y - red dot on GUI picture) with specified height and width.
My code:
from Tkinter import *
from ttk import *
class NotebookDemo(Frame):
def __init__(self):
Frame.__init__(self)
self.pack(expand=1, fill=BOTH)
self.master.title('Sample')
self.master.geometry("650x550+100+50")
self._initUI()
def _initUI(self):
self._createPanel()
def _createPanel(self):
# create frame inside top level frame
panel = Frame(self)
panel.pack(side=TOP, fill=BOTH, expand=1)
# create the notebook
nb = Notebook(panel)
nb.pack(fill=BOTH, expand=1, padx=2, pady=3)
self._FirstTab(nb)
def _FirstTab(self, nb):
# frame to hold content
frame = Frame(nb)
#textbox
txtOutput = Text(frame, wrap = NONE, height = 17, width = 70)
txtOutput.place(x=10, y=75)
#button
btnStart = Button(frame, text = 'Start', underline=0)
btnStart.place(x=220, y=380)
#scrollbar
#vscroll = Scrollbar(frame, orient=VERTICAL, command=txtOutput.yview)
#txtOutput['yscroll'] = vscroll.set
#vscroll.pack(side=RIGHT, fill=Y)
#txtOutput.pack(fill=BOTH, expand=Y)
#add to notebook (underline = index for short-cut character)
nb.add(frame, text='TAB 1', underline=0, padding=2)
if __name__ == '__main__':
app = NotebookDemo()
app.mainloop()
If I uncomment this part of code (set Scrollbar):
vscroll = Scrollbar(frame, orient=VERTICAL, command=txtOutput.yview)
txtOutput['yscroll'] = vscroll.set
vscroll.pack(side=RIGHT, fill=Y)
My Scrollbar is located inside all window, not inside Text box:
But of course I want to have the Scrollbar inside the Text box widget (black border).
If I use pack function to textbox:
txtOutput.pack(fill=BOTH, expand=Y)
text widget fill in the whole window...:
I really don't know how fix this problem.
Any help will be appreciated.
Thank you!
EDIT:
Of course I can use place method with Scrollbar too, but I can't change length of them, because it hasn't attribute length.
vscroll.place(x=573, y=75)
While I rarely recommend place, it is quite powerful when you take advantage of the configuration options. For example, you can use in_ to specify a widget that this widget is to be placed relative to. You can use relx to specify a relative x coordinate, and you can use relheight to specify a height.
In your case you can try something like this:
vscroll.place(in_=txtOutput, relx=1.0, relheight=1.0, bordermode="outside")
If you want the illusion that the scrollbar is embedded inside the text widget as is (or used to be) common on some platforms, I recommend placing the text widget and scrollbar in a frame.You can use pack to put the widgets in the frame, and continue to use place to place the combination anywhere you want.
For example:
txtFrame = Frame(frame, borderwidth=1, relief="sunken")
txtOutput = Text(txtFrame, wrap = NONE, height = 17, width = 70, borderwidth=0)
vscroll = Scrollbar(txtFrame, orient=VERTICAL, command=txtOutput.yview)
txtOutput['yscroll'] = vscroll.set
vscroll.pack(side="right", fill="y")
txtOutput.pack(side="left", fill="both", expand=True)
txtFrame.place(x=10, y=75)
Different geometry managers like place and pack don't mix so well. I see four options for you:
Use a parent frame
Create a new Frame that you place at the exact same position as you did with the text box. In this frame, you can use another geometry manager (I'd prefer pack) to make the layout appear as you want.
Use ScrolledText
Use the ScrolledText Tkinter module to have the solution above in a premade form. Note that this widget doesn't use ttk so the scrollbar style does not really adapt to the OS' look. Just use import ScrolledText and replace the Text creation in your code with ScrolledText.ScrolledText(...).
Use place for the scrollbar
If you are using place for the text widget, use place for the scrollbar too. place has options that allow you to place a widget relative to another widget both in location and size (ie: you can place the scrollbar along the right edge of the text widget, and cause it to be exactly as tall as the text widget). See Bryan's answer.
Don't use place
Simple as that. Use grid or pack instead, unless you really need to use place.

Stop a Tkinter frame from resizing according to widget sizes

root = Tk()
descriptionFrame = Frame(root)
definitionFrame = LabelFrame(descriptionFrame, text="Definition")
definitionScroll = Scrollbar(definitionFrame)
definitionCanvas = Canvas(definitionFrame, width=30, height=4, yscrollcommand=definitionScroll.set)
definitionScroll.config(command=definitionCanvas.yview)
definitionLabel = Label(definitionCanvas, text="n/a")
descriptionFrame.pack()
definitionFrame.pack()
definitionScroll.pack(side=RIGHT, fill=Y)
definitionCanvas.pack(side=LEFT, fill=BOTH, expand=True)
definitionLabel.pack(fill=BOTH, expand=True)
root.mainloop()
I have this code. The Canvas is set to have a width of 30 and height of 4, but when I run this, it ignores the width and height of the Canvas and the resulting window is sized around the Label instead. I've tried using pack_propagate(False) on every single Frame in the code, but it doesn't affect anything for the definitionFrame, but when I use it on descriptionFrame it results in an empty window. How would I create a GUI where all the frames and the window are sized to the Canvas size of width 30 and height 4?
Thanks.
To answer your specific question of how to stop a frame (or any container widget) from resizing to fit its contents, you call either pack_propagate(False) or grid_propagate(False) depending on which geometry manager you are using. If you've tried that and it wasn't working, you did it wrong. Since you didn't post that code we can't diagnose what went wrong.
When you call pack_propagate(False) you have to make sure that widget has an appropriate size. Labels and buttons will have a default size to fit their text, but a frame will have a default size of 1x1, making the contents nearly invisible. If using this on a frame, then, make sure to give it an explicit width and height.
only Listbox, Text, Canvas, and Entry are scrollable by default; Canvas could work, but is a bit overkill IMO, so here's something that seems like what you want using Text
#!/usr/bin/python
from Tkinter import *
root = Tk()
descriptionFrame = Frame(root)
definitionFrame = LabelFrame(descriptionFrame, text="Definition")
definitionScroll = Scrollbar(definitionFrame)
definitionText = Text(definitionFrame, width=30, height=4, yscrollcommand=definitionScroll.set)
definitionScroll.config(command=definitionText.yview)
definitionText.delete("1.0", END) # an example of how to delete all current text
definitionText.insert("1.0", "n/a") # an example of how to add new text to the text area
descriptionFrame.pack(fill=BOTH, expand=True)
definitionFrame.pack(fill=BOTH, expand=True)
definitionScroll.pack(side=RIGHT, fill=Y)
definitionText.pack(side=LEFT, fill=BOTH, expand=True)
root.mainloop()
What I did was set make my root un-resizable: root.resizable(False, False), then I defined width and height of the canvas: Canvas(root, height=500, width=1500)
and finally placed my frame with a relative width and height of 1: frame.place(relheight=1, relwidth=1). Then I placed all my widgets with specific y/x values and pack(), but you could do the same process if you want everything sized to the canvas; I feel like this is a simpler method.

Categories