How can I put 2 buttons with images Vertically in a frame - python

Im using tkinter and trying to create a toolbar located on the left side going vertically, I already have a toolbar on the top of the frame filled in going horizontaly however can't figure out how to make a second one on the left, with all the buttons.
This is the code that I have:
infobar = Frame(master, bg="#ecf0f1", bd=1, relief=GROOVE)
infobar.pack(side=LEFT, fill=BOTH, expand=None)
infobarr = Label(toolbar, bg="#ecf0f1", text=' ')
infobarr.pack(side=LEFT, fill=Y)
poundToKgButton = Button(infobar, highlightbackground="#ecf0f1", image=eimg20, relief=FLAT, command=self.scale)
poundToKgButton.image = eimg20
createToolTip(poundToKgButton, "Conversion - Pound To KG")
poundToKgButton.pack(side=LEFT)
calculatorButton = Button(infobar, highlightbackground="#ecf0f1", image=eimg19, bd=1, relief=FLAT, command=self.calc)
calculatorButton.image = eimg19
createToolTip(calculatorButton, "Calculator")
calculatorButton.pack(side=LEFT, anchor="sw")

Use side=TOP if you want things stacked vertically. The containing widget has empty space. When you use pack, you're telling tkinter which side of that empty space to put the widget.
Here is a good example of exactly what happens when you call pack: http://tcl.tk/man/tcl8.5/TkCmd/pack.htm#M26

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.

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.

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

Tkinter: grid or pack inside a grid?

I am building a GUI for a software and want to achieve this:
######################################
# | some title #
# menu upper |----------------------#
# | #
# | CANVAS #
# menu lower | #
# | #
#------------------------------------#
# statusbar #
######################################
Menu upper has some high level functionality, menu lower is changing in dependency of user input. Statusbar changes its contents often.
Unfortunately, Tkinter refuses to work.
Using the grid layout manager I were unable to create a stable design and adding content like labels and buttons to the menu on the left side:
self.root = tk.Tk()
self.root.resizable(width=0, height=0)
self.root.title("some application")
# menu left
self.menu_left = tk.Frame(self.root, width=150, bg="#ababab")
self.menu_left.grid(row=0, column=0, rowspan=2, sticky="ns")
self.menu_left_upper = tk.Frame(self.menu_left, width=150, height=150, bg="red")
self.menu_left_upper.grid(row=0, column=0)
# this label breaks the design
#self.test = tk.Label(self.menu_left_upper, text="test")
#self.test.pack()
self.menu_left_lower = tk.Frame(self.menu_left, width=150, bg="blue")
self.menu_left_lower.grid(row=1, column=0)
# right area
self.some_title_frame = tk.Frame(self.root, bg="#dfdfdf")
self.some_title_frame.grid(row=0, column=1, sticky="we")
self.some_title = tk.Label(self.some_title_frame, text="some title", bg="#dfdfdf")
self.some_title.pack()
self.canvas_area = tk.Canvas(self.root, width=500, height=400, background="#ffffff")
self.canvas_area.grid(row=1, column=1)
self.root.mainloop()
This design worked without contents in the menu on the left side. Whenever I added something in self.menu_left_upper or self.menu_left_lower, like the test label, my design got broke: the menu frames disappeared.
Additionally, even with columnspan, I had to remove the statusbar, because when it was added the menus on the left disappeared, again.
Using pack layout manager I got this:
######################################
# | some title #
# |----------------------#
# menu upper | #
# | CANVAS #
# | #
# menu lower | #
# |----------------------#
# | statusbar #
######################################
Since I wanted the menu frame on the left to consume the full y-space I made it grow with pack(side="left", expand=True, fill="both"), but this setup always denies the statusbar to go for the full width.
Besides, the pure pack manager code looks "ugly". I think a design with a grid manager is "clearer". Therefore I thought a grid or a pack layout inside a grid layout would be nice?
Can anyone help? I am stuck in the GUI-hell :/
The key to doing layout is to be methodical, and to use the right tool
for the job. It also means that sometimes you need to be creative.
That means using grid when laying things out in a
grid, and using pack when laying things out top-to-bottom or
left-to-right.
The other key is to group your layout code together. It's much, much
easier to visualize and modify the layout when it's all in one block
of code.
In your case you seem to have three or four distinct areas, depending
on how you count. If you want to use grid, it will be easiest to
combine "menu upper" and "menu lower" into a frame, and treat that
whole frame as a table cell. It looks like you're already doing that,
which is good.
So, let's start with those main areas:
self.menu_left.grid(row=0, column=0, rowspan=2, sticky="nsew")
self.some_title_frame.grid(row=0, column=1, sticky="ew")
self.canvas_area.grid(row=1, column=1, sticky="nsew")
# you don't have a status bar in the example code, but if you
# did, this is where you would put it
# self.status_frame.grid(row=2, column=0, columnspan=2, sticky="ew")
Any time you use grid, you need to give at least one row and one
column a positive weight so that tkinter knows where to use any
unallocated space. Usually there is one widget that is the "main"
widget. In this case it's the canvas. You want to make sure that the
row and column for the canvas has a weight of 1 (one):
self.root.grid_rowconfigure(1, weight=1)
self.root.grid_columnconfigure(1, weight=1)
note: using pack instead of grid would save you two lines of code, since pack doesn't require you to set weights the way grid does.
Next, we need to solve the problem of the menu areas. By default,
frames shrink to fit their contents, which is why adding the label
broke your layout. You weren't telling tkinter what to do with extra space, so the frames shrunk to fit, and extra space went unused.
Since you want "menu_upper" and "menu_lower" to
each share 50% of that area, pack is the simplest solution. You can
use grid, but it requires more lines of code to add the row and column weights.
self.menu_left_upper.pack(side="top", fill="both", expand=True)
self.menu_left_lower.pack(side="top", fill="both", expand=True)
Here is a functioning version, with statusbar. Notice how it behaves exactly as it should when you resize the window:
import Tkinter as tk
class Example():
def __init__(self):
self.root = tk.Tk()
self.root.title("some application")
# menu left
self.menu_left = tk.Frame(self.root, width=150, bg="#ababab")
self.menu_left_upper = tk.Frame(self.menu_left, width=150, height=150, bg="red")
self.menu_left_lower = tk.Frame(self.menu_left, width=150, bg="blue")
self.test = tk.Label(self.menu_left_upper, text="test")
self.test.pack()
self.menu_left_upper.pack(side="top", fill="both", expand=True)
self.menu_left_lower.pack(side="top", fill="both", expand=True)
# right area
self.some_title_frame = tk.Frame(self.root, bg="#dfdfdf")
self.some_title = tk.Label(self.some_title_frame, text="some title", bg="#dfdfdf")
self.some_title.pack()
self.canvas_area = tk.Canvas(self.root, width=500, height=400, background="#ffffff")
self.canvas_area.grid(row=1, column=1)
# status bar
self.status_frame = tk.Frame(self.root)
self.status = tk.Label(self.status_frame, text="this is the status bar")
self.status.pack(fill="both", expand=True)
self.menu_left.grid(row=0, column=0, rowspan=2, sticky="nsew")
self.some_title_frame.grid(row=0, column=1, sticky="ew")
self.canvas_area.grid(row=1, column=1, sticky="nsew")
self.status_frame.grid(row=2, column=0, columnspan=2, sticky="ew")
self.root.grid_rowconfigure(1, weight=1)
self.root.grid_columnconfigure(1, weight=1)
self.root.mainloop()
Example()
On an unrelated note: I would strongly encourage you to not remove the ability for the user to resize the window. They know better than you what their requirements are. If you use grid and pack properly, the GUI will resize perfectly.
Adding the following code right before self.root.mainloop() achieves what you're looking for
self.some_status = tk.Label(self.root, text="status bar", bg="#dfdfdf")
self.some_status.grid(row=3, column=0, columnspan=2, sticky="we")
By putting in the line:
menu_left_upper.grid_propagate(False)
In between your menu_left_upper Frame and menu_left_upper.mainloop()
This works as:
By default, a container widget expands or collapses to be just big enough to hold its contents. Thus, when you call pack, it causes the frame to shrink. This feature is called geometry propagation.
For the vast majority of applications, this is the behavior you want. For those rare times when you want to explicitly set the size of a container you can turn this feature off. To turn it off, call either pack_propagate or grid_propagate on the container (depending on whether you're using grid or pack on that container), giving it a value of False.
See link to another question where this came from
To get your status bar just implement another frame and grid method:
status_bar_frame = Frame(root, bg="#dfdfdf")
status_bar_frame.grid(row=3, column=0, columnspan=2, sticky="we")
status_bar = Label(status_bar_frame, text="status bar", bg="#dfdfdf")
status_bar.pack()
Then your plan works.
Hope it helps :)
PS. Also why all the self attributes?
EDIT:
TO work you need to do:
menu_left_upper = Frame(menu_left, width=225, height=225, bg="red")
menu_left_upper.grid_propagate(False)
menu_left_upper.grid(row=0, column=0)
# this label breaks the design
test = Label(menu_left_upper, text="test", bg='red')
test.grid()
My result

How can I stack buttons rather than queue them in Tkinter?

At the moment I have 3 basic buttons being displayed:
from tkinter import *
root = Tk()
topFrame = Frame(root)
topFrame.pack(side=TOP)
leftFrame = Frame(root)
leftFrame.pack(side=LEFT)
botFrame = Frame(root)
botFrame.pack(side=BOTTOM)
button1 = Button(leftFrame, text="Button 1", fg="Black")
button2 = Button(leftFrame, text="Button 2", fg="Black")
button3 = Button(leftFrame, text="Button 3", fg="Black")
button1.pack(side=LEFT)
button2.pack(side=LEFT)
button3.pack(side=LEFT)
root.mainloop()
The 3 buttons at the moment will stick to the left frame on the window, however then will queue next to each other rather than stack one on top of the other, how do i fix this?
You're explicitly telling them to be side-by-side with side=LEFT. You want side=TOP so that they are placed at the top of the empty space in the frame.
button1.pack(side=TOP)
button2.pack(side=TOP)
button3.pack(side=TOP)
When you use pack, the values TOP, LEFT, RIGHT and BOTTOM tell the widget which side of the remaining space they should occupy. The first time you use LEFT it will reserve the left side of the whole frame for the widget. The next time you use LEFT, that refers to the space remaining in the widget excluding the left edge since that already has a widget in it. The net effect is that LEFT causes widgets to be arranged left-to-right, RIGHT arranges them right-to-left, and so on.
Explore the grid function. Change your pack statements to
button1.grid(row=0,column=0)
button2.grid(row=1,column=0)
button3.grid(row=2,column=0)

Categories