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

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

Related

TKinter grid layout shows expected behavior only with label image

I want to create a grid of pictures and comments like a detail view in a folder. However, when I don't load the image (the "magic two lines" after XXX), the layout collapses and I see only the borders, not even Label widgets. But of course it should work in both cases. Here is some simplified version:
import os.path as osp
import tkinter as tk
from PIL import Image, ImageTk
IMG_FILE = '/your/magic/image/path/name.png'
class Comparator:
def __init__(self):
self.root = tk.Tk()
self.root.geometry('1350x730+0+0')
self.ctrl = ctrl = tk.Frame(self.root, padx=10, pady=10)
ctrl.grid(row=0, column=0)
lframe = tk.Frame(self.root)
lframe.grid(row=0, column=1, rowspan=2)
inner_frame = tk.Frame(lframe, bg='#444', padx=7, pady=7)
inner_frame.grid(row=0, column=0)
# group of widgets
tk_img = ImageTk.PhotoImage(Image.open(IMG_FILE))
for num in range(23):
yard = tk.Frame(inner_frame, bg='#222', padx=6, pady=6)
ya_col = num % 4
ya_row = num // 4
yard.grid(column = ya_col, row = ya_row)
stone = tk.Label(yard, text='Img name', width=179, height=179)
stone.grid(row=0, column=0)
# XXX magic two lines that generate the right layout:
stone.image = tk_img
stone.config(image=stone.image)
# end magic two lines
tk.Label(yard, text='%d : %d %d' % (num, ya_row, ya_col)).grid(row=1, column=0)
self.root.mainloop()
Comparator()
There are a few redundant tk.Frame instances because it's just a simplified version; for instance ctrl is supposed to be filled with a lot of buttons etc. Actually, I also wanted the inner_frame to be in a scrollable tk.Canvas, but let's say that's another story...
I don't understand what happens. Any suggestions would be very nice :-)
[edit] Maybe it depends on the fact that width is interpreted either in pixels or in number of chars? How do I fix that?

Tkinter Text Widget to be inside Notebook as resizable/streched/font changable

Why I cannot stretch Text widget inside Notebook widget(Tab) with sticky?
How to get fixed Text widget size while changing font, while grid_propagate doesn't give results.
How that same window can again be resizable (weight) altogether?
Thanks
import tkinter as tk
from tkinter import *
from tkinter import ttk, font
class TextInsideNotebook:
def __init__(self, main):
self.main = main
self.fontSizePx = -20
# Font
self.fontspecs = font.Font(family="consolas", size=self.fontSizePx)
# Notebook
self.tab = ttk.Notebook(main, width=800, height=600)
self.tab.grid(row=0, column=0, sticky="nsew")
# Tab
self.tab_frame = Frame(self.tab)
self.tab.grid(row=0, column=0, sticky="nsew")
self.tab.add(self.tab_frame, text=' NEW FILE ')
# Text Area
self.textarea = tk.Text(self.tab_frame, font=self.fontspecs)
self.textarea.grid(row=0, column=0, sticky="nsew")
self.tab_frame.grid_propagate(False)
# weights
main.columnconfigure(0, weight=1)
main.rowconfigure(0, weight=1)
# Bind
self.main.bind('<Control-MouseWheel>', self.new_font_size)
def new_font_size(self, event):
if event.delta > 0:
self.fontSizePx = self.fontSizePx - 2
else:
self.fontSizePx = self.fontSizePx + 2
self.fontspecs.config(size=self.fontSizePx)
if __name__ == "__main__":
main = tk.Tk()
agc = TextInsideNotebook(main)
main.mainloop()
Why I cannot stretch Text widget inside Notebook widget(Tab) with sticky?
The text does stick to the edges of the area you've allocated to it. However, you haven't given any rows or columns inside self.tab_frame a weight so that row and column is only as wide and tall as the text widget.
If you're only putting the text widget in the frame, it's much easier to use pack than grid since it takes only one line of code rather than three:
self.textarea.pack(fill="both", expand=True)
If you wish to stick to using grid for some reason, you must give the row and column that contains the text widget a non-zero weight
self.tab_frame.grid_rowconfigure(0, weight=1)
self.tab_frame.grid_columnconfigure(0, weight=1)
self.textarea.grid(row=0, column=0, sticky="nsew")
As a rule of thumb you should always give at least one row and one column a positive non-zero weight in any widget that has children managed by grid.
How to get fixed Text widget size while changing font
What I recommend is to give the widget a small size, such as 1x1, and then let the geometry manager (pack, place, or grid) stretch it to fit the space allocated to it.
self.textarea = tk.Text(..., width=1, height=1)
How that same window can again be resizable (weight) altogether?
I don't understand that question. It's never not resizable.

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.

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

Keeping the background colour when adding buttons to a grid/frame

The problem I'm having is keeping my background colour when adding buttons to a frame, as soon as I run the module the background colour disappears, any help will be appreciated, thanks.
Heres me code:
import tkinter
import tkinter as tk
root = tk.Tk()
root.geometry('1000x600')
var=tk.StringVar()
Frame1 = tk.Frame(root)
Frame1.configure(background='light blue',height='300',width='500')
Frame1.grid(row='0',column='0')
Frame2 = tk.Frame(root)
Frame2.configure(background='grey',height='300',width='500')
Frame2.grid(row='0',column='1')
Frame3 = tk.Frame(root)
Frame3.configure(background='grey',height='300',width='500')
Frame3.grid(row='1',column='0')
Frame4 = tk.Frame(root)
Frame4.configure(background='light blue',height='300',width='500')
Frame4.grid(row='1',column='1')
def PrintOrder():
LabelOrder = tk.Label(Frame3,text="DONUT ORDER")
LabelOrder.grid(row='0',column='0')
return
Button1 = tk.Button(Frame1,text="Apple Cinnamon",height='2',width='15',command=PrintOrder).grid(row='0',column='0')
Button2 = tk.Button(Frame1,text="Strawberry",height='2',width='15',command=PrintOrder).grid(row='0',column='1')
Button3 = tk.Button(Frame1,text="Custard",height='2',width='15',command=PrintOrder).grid(row='0',column='2')
Button4 = tk.Button(Frame1,text="Sugar Ring",height='2',width='15',command=PrintOrder).grid(row='1',column='0')
Button5 = tk.Button(Frame1,text="Chocolate Caramel",height='2',width='15',command=PrintOrder).grid(row='1',column='1')
Button6 = tk.Button(Frame1,text="Lemon Circle",height='2',width='15',command=PrintOrder).grid(row='1',column='2')
Button7 = tk.Button(Frame1,text="Blueberry Blaster",height='2',width='15',command=PrintOrder).grid(row='2',column='0')
Button8 = tk.Button(Frame1,text="Strawberry Surprise",height='2',width='15',command=PrintOrder).grid(row='2',column='1')
Button9 = tk.Button(Frame1,text="Simple Sugar",height='2',width='15',command=PrintOrder).grid(row='2',column='2')
Label1 = tk.Label(Frame2,text="Donut special 6 for the price of 5").grid(row='0',column='0')
Button10 = tk.Button(Frame2,text="SPECIAL",height='5',width='20').grid(row='1',column='0')
root.mainloop()
Your frame still has its background color. You can see this pretty easily if you give it a distinct color so that it will show (eg: "red"), and add padding between the buttons (eg: tk.Button(...).grid(..., padx=10, pady=10). I think the only thing that is happening is that there is no space between the buttons for the color to show through, and the default behavior is for the frame to shrink (or grow) to fit its contents.
Other problems include the fact that you aren't giving any rows or columns a weight, so they won't grow or shrink as the main window grows an shrinks. Also, you don't have the sticky attribute set for the frames, so they won't fill the grid cell that they occupy. Add sticky="nsew" to where you grid the frames and you'll likely see more color.
A rule of thumb when using grid is to always set the sticky attribute for each item, and to give at least one row and one column a weight of 1 (one).
You can use grid_propagate(0) on your frames. With this, the frame's size is not adjusted to the widgets' size.
On your code, I used the next line to keep the size of Frame1:
Frame1.grid_propagate(0)
You can check this:
http://effbot.org/tkinterbook/grid.htm#Tkinter.Grid.grid_propagate-method
grid_propagate(flag) [#]
Enables or disables geometry propagation. When enabled, a grid manager connected to this widget attempts to change the size of the widget whenever a child widget changes size. Propagation is always enabled by default.
flag
True to enable propagation.

Categories