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

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.

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 use tkinter.place()?

I want to have an entry and I want to have a listbox of fixed size under it which is fixed. and I want to have another listbox of dynamic height. That will appear and disappear in time and also change in size. I want the second listbox (which is actually a dropdown) to be shown over the other listbox which I want it to be fixed. My code for changing the size etc is correct and works perfectly with pack() but then it will move the other listbox up and down as it's size changes. And when I change pack() to place(...) it's not shown at all anymore.
Here is my code:
from tkinter import *
root = Tk()
entry = Entry(
root,
width=50
)
frame = Frame(
root,
height=10,
width=50,
background="#caeaa9"
)
dropdown = Listbox(
frame,
background="#11FF11",
height=5,
width=50
)
listbox = Listbox(
frame,
background="#FF1111",
height=10,
width=50
)
entry.pack()
listbox.pack()
dropdown.place()
frame.pack()
mainloop()
But the dropdown does not appear when I run it. What am I missing?
By the way, I want the top border of the dropdown to be exactly on the top border of the listbox and both of them right under the entry.
I highly recommend you don't use the place geometry manager. Ever. If you want to create larger or more complex interfaces, having to place widgets is terrible. I suggest using grid instead:
import tkinter as tk
# Avoid wildcard imports!
root = tk.Tk()
entry = tk.Entry(
root,
width=50
)
frame = tk.Frame(
root,
background="#caeaa9"
)
listbox = tk.Listbox(
frame,
background="#FF1111",
height=10,
width=50
)
dropdown = tk.Listbox(
frame,
background="#11FF11",
height=5,
width=50
)
entry.pack()
listbox.grid(column=0, row=0, sticky=N)
dropdown.grid(column=0, row=0, sticky=N)
frame.pack()
root.mainloop()
You have two problems:
you aren't telling place where to place the widget
the stacking order (z-index) of the dropdown is behind (lower) than the other listbox, so it will appear under the listbox.
give explicit coordinates to place
I'm not entirely clear where you want the dropdown to appear. Since you say "over" the other listbox, I suggest you use the in_ parameter to make the coordinates relative to the other listbox, and then use other place arguments to place it exactly where you want.
Example:
In the following example I make the dropdown exactly the same width, but half the height of the other listbox.
dropdown.place(in_=listbox, x=0, y=0, anchor="nw", relwidth=1.0, relheight=.5)
Fix the stacking order
All widgets have a stacking order. Some people call this a z-index. By default the order is the order in which the widgets are created. Since you create the dropdown before the other listbox, the other listbox has a higher stacking order. That means that it will appear above the dropdown.
A simple solution is to create the dropdown last. If you don't want to do that, you can call the lift method of the widget to raise its stacking order. The argument for lift is the name of a widget you want to be above.
Example:
dropdown.lift(listbox)
Tkinter place is one of the three geometry manager in tkinter. it allows us to specify the position of tkinter widgets in terms of x and y coordinate.
Here is the code example :
from tkinter import *
window = Tk()
Button(window,text = "Click Me").place(x = 50,y = 50)
window.mainloop()
For more info kindly refer this tutorial on tkinter place

Tkinter - Grid elements next to each other

I'm trying to make some UI in python with tkinter.
This is a sample of the code I'm using:
root = Tk()
root.geometry("1000x700x0x0")
canvas = Canvas(root, width = 700, height = 700, bg ='white').grid(row = 0, column = 0)
button1 = Button(root, text = "w/e", command = w/e).grid(row = 0, column = 1)
button2 = Button(root, text = "w/e", command = w/e).grid(row = 1, column = 1)
This is what i'm getting:
and this is what I want:
Any help on how can I do it?
Thanks!
Since your GUI seems to have two logical groups of widgets, I would organize it as such. Start by placing the canvas on the left and a frame on the right. You can use pack, place, grid, or a paned window to manage them. For a left-to-right orientation, pack is a good choice due to its simplicity
Note that you don't have to do it this way, but experience has taught me it makes layout problems much easier to solve.
In the following example I set expand to False for the button frame, which means that the canvas will grow and shrink when the user resizes (because it has expand=True), but the buttons will only take up exactly as much space as they need.
canvas = Canvas(root, ...)
buttonframe = Frame(root, ...)
canvas.pack(side="left", fill="both", expand=True)
buttonframe.pack(side="right", fill="both", expand=False)
Next, you can put all of the buttons in the right side without having to worry how their placement might affect objects on the left.
The important thing to remember when using grid is that you should designate at least one row and at least one column to be given any extra space. This can be a row and/or column that contains widgets, or it can be an empty row and column on an edge.
button1 = Button(buttonframe, ...)
button2 = Button(buttonframe, ...)
button3 = Button(buttonframe, ...)
...
button1.grid(row=0, column=0)
button2.grid(row=0, column=1)
button3.grid(row=1, column=0)
...
buttonframe.grid_rowconfigure(100, weight=1)
buttonframe.grid_columnconfigure(2, weight=1)
note: if you need to keep a reference to a widget, you must create the widget and call grid (or pack or place) on two separate lines. This is because Button(...).grid(...) returns the value of the last function call, and grid(...) returns None

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

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