Related
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.
When I used anchor in place() mythod in tkinter, the button's position seems reverse to what I set.
from tkinter import *
win = Tk()
frame = Frame(win, relief=RAISED, borderwidth=1, width=400, height=300)
frame.pack(fill=BOTH, ipadx=5, ipady=5, expand=1)
button1 = Button(frame, text="Button 1")
button1.place(x=200, y=150, anchor=NW, width=200, height=150)
win.mainloop()
As u can see, I set the button anchor(I mean x=200, y=150) at the center of the frame, and the anchor's value is NW. The button should be placed in top left corner of the frame, but it turned out at bottom right corner of it, it's totally reversed.
I am so confused, and I try my best to look out in google, but it seems like no one has the same question as me, if anyone could help me out please?
The anchor specifies the part of the button that is to appear at the given coordinates. Setting the anchor to NW means that the northwest corner of the button is to appear at the given coordinates.
You've set the coordinates at 200,150, so the upper-left corner of the widget will be placed at that position, and that's exactly what is happening.
I am currently coding a program with will tie in with Discord's rich presence, and I am needing to create a GUI for it in Tkinter. The issue is, I cannot understand how to place elements correctly. It has been quite a pain. Here is what I am planning to have the app sort of look like: https://i.stack.imgur.com/K9Wox.jpg
However, with my current code, this is how abismal the GUI looks... https://i.stack.imgur.com/wGA9A.jpg
Here is my code:
root = tkinter.Tk()
root.title("Matter: A Discord Rich Presence Tool")
root.config(bg='#2C2F33')
root.geometry("560x300")
#root.overrideredirect(1)
# Load images
loadProfileImage = tkinter.PhotoImage(file="resources/loadprofile.png")
saveProfileImage = tkinter.PhotoImage(file="resources/saveprofile.png")
newProfileImage = tkinter.PhotoImage(file="resources/newprofile.png")
# GUI Hell starts here
topCanvas = tkinter.Canvas(root, width=600, height=150)
topCanvas.config(bd=0, highlightthickness=0, relief='ridge', background="#7289DA")
topTextFieldText = tkinter.StringVar(value='Sample top text')
topTextField = tkinter.Entry(root, textvariable=topTextFieldText)
topTextField.config(borderwidth=0, background="#7289DA")
bottomTextFieldText = tkinter.StringVar(value='Sample bottom text')
bottomTextField = tkinter.Entry(root, textvariable=bottomTextFieldText)
bottomTextField.config(borderwidth=0, background="#7289DA")
largeIconName = tkinter.StringVar()
largeIconNameField = tkinter.Entry(root, textvariable=largeIconName)
smallIconName = tkinter.StringVar()
smallIconNameField = tkinter.Entry(root, textvariable=smallIconName)
applicationIDFieldText = tkinter.StringVar()
applicationIDField = tkinter.Entry(root, textvariable=applicationIDFieldText)
applicationIDField.config(borderwidth=0, background="#23272A")
largeIconHoverText = tkinter.StringVar()
largeIconHoverTextField = tkinter.Entry(root, textvariable=largeIconHoverText)
largeIconHoverTextField.config(borderwidth=0, background="#23272A")
smallIconHoverText = tkinter.StringVar()
smallIconHoverTextField = tkinter.Entry(root, textvariable=smallIconHoverText)
smallIconHoverTextField.config(borderwidth=0, background="#23272A")
#greet_button = tkinter.Button(root, text="Run", command=run)
buttonFrame = tkinter.Frame(height=2, bd=0, relief=tkinter.SUNKEN)
newProfileButton = tkinter.Button(root, text="Save to profile", command=save)
newProfileButton.config(image=newProfileImage, borderwidth=0, background="#23272A")
saveButton = tkinter.Button(root, text="Save to profile", command=save)
saveButton.config(image=saveProfileImage, borderwidth=0, background="#23272A")
loadButton = tkinter.Button(root, command=load)
loadButton.config(image=loadProfileImage, borderwidth=0, background="#23272A")
# Grid stuff
topCanvas.grid(row=0, column=1)
applicationIDField.grid(row=3, column=1)
largeIconHoverTextField.grid(row=3, column=2)
smallIconHoverTextField.grid(row=3, column=3)
newProfileButton.grid(row=5, column=1, padx=(20, 5))
saveButton.grid(row=5, column=2, padx=(5, 5))
loadButton.grid(row=5, column=3, padx=(5, 20))
root.mainloop()
Any guidance would be greatly appreciated since I cannot seem to be able to figure out how to use Tkinter's grid to make a layout similar to the images above.
Do not try to put everything into a single grid. Divide your UI up into sections, and then use the right tool for each section.
Start at the root window
I see two major sections to your UI: a top section in blue that has some information, and a bottom section with a black background that has some buttons.
So, I would start by creating those two sections in the root window, and use pack to place one on top of the other:
topFrame = tk.Frame(root, background="#7289DA")
bottomFrame = tk.Frame(root, background="#2C2F33")
topFrame.pack(side="top", fill="both", expand=True)
bottomFrame.pack(side="bottom", fill="both", expand=True)
With that, you will now always have the root divided into two colored regions. The above code gives them an equal size. You may one to change the expand value to False for one or the other, depending on what you want to happen when the user resizes the window.
Don't worry too much about the size, though. It will change once you start adding widgets to each section.
Next, do the bottom section
The bottom also appears to be in two sections: one for inputs and one for buttons. You could use a single grid layout for this whole section, but to illustrate the concept of dividing the UI into sections we'll split the bottom into two. Plus, because everything isn't neatly lined up into rows and columns, this will make things a bit easier.
As I mentioned earlier, you may want to fiddle around with the expand option, depending on if you want these frames to resize equally or stay the same size when the user resizes the window.
inputFrame = tk.Frame(bottomFrame, background="#2C2F33")
buttonFrame = tk.Frame(bottomFrame, background="#2C2F33")
inputFrame.pack(side="top", fill="both", expand=True)
buttonFrame.pack(side="top", fill="both", expand=True)
Note: if you stop right here and try to run your program, you might not see these frames. During development it sometimes helps to give them distinctive colors to help you visualize. Once you get everything working, you can adjust the colors to their final values.
Add the entry widgets
Now we can add the entry widgets to the top half of the bottom section. We can use grid here, since everything is lined up neatly. An important step is to give the rows an equal weight so that they grow and shrink together, though you can make it so that only one column resizes if you wish.
I'll also point out that there's no need to use StringVar instance. You can, but it adds extra objects to keep track of, which in most cases is not necessary.
label1 = tk.Label(inputFrame, text="APPLICATION ID",
foreground="lightgray",
background="#2C2F33")
label2 = tk.Label(inputFrame, text="LARGE IMAGE HOVER",
foreground="lightgray",
background="#2C2F33")
label3 = tk.Label(inputFrame, text="SMALL IMAGE HOVER",
foreground="lightgray",
background="#2C2F33")
# columns should get extra space equally. Give any extra vertical space
# to an empty column below the entry widgets
inputFrame.grid_columnconfigure((0,1,2), weight=1)
inputFrame.grid_rowconfigure(2, weight=1)
appIdEntry = tk.Entry(inputFrame, borderwidth=0,
highlightthickness=0,
background="#23272A", bd=0)
largeImageEntry = tk.Entry(inputFrame,
highlightthickness=0,
background="#23272A", bd=0)
smallImageEntry = tk.Entry(inputFrame, borderwidth=0,
highlightthickness=0,
background="#23272A", bd=0)
label1.grid(row=0, column=0, sticky="w")
label2.grid(row=0, column=1, sticky="w", padx=10)
label3.grid(row=0, column=2, sticky="w")
appIdEntry.grid(row=1, column=0, sticky="ew")
largeImageEntry.grid(row=1, column=1, sticky="ew", padx=10)
smallImageEntry.grid(row=1, column=2, sticky="ew")
This gives us the following:
Notice that the top appears to have shrunk. That's only because it's empty. Tkinter is really good about expanding and shrinking things to fit what's inside. Don't worry too much about it. You can tweak things once you get everything working.
And so on...
I don't want to rewrite your whole program in this answer. The point here is that you should break your UI up into logical chunks, and make each chunk a frame. You are then free to use whatever geometry manager makes the most sense within that frame. Sometimes grid is best, sometimes pack. In either case, it's much easier to manage a few high level frames than it is to try to cram dozens of widgets into a single grid, especially when there are no clear cut rows and/or columns.
This solution also makes it pretty easy to create functions or classes for each section. For example, your main program might look like:
root = tkinter.Tk()
top = topSection(root)
bottom = bottomSection(root)
By doing so, if you decide to completely redesign one section of the UI, you can do so without worrying that you'll mess up the layout in the other sections, since each frame is mostly independent of any other frame.
First you have to explain to the grid geometry manager that you want the canvas to span all three columns:
topCanvas.grid(row=0, column=1, columnspan=3)
Widgets do not automatically expand to fill the entire column (or row) if you don't specify it and it will center itself in a cell if you dont specify where you want it with sticky:
newProfileButton.grid(row=5, column=1, padx=(20, 5), sticky='w')
saveButton.grid(row=5, column=2, padx=(5, 5), sticky='w')
loadButton.grid(row=5, column=3, padx=(5, 20), sticky='w')
This will hopefully give you something to play with although it's not a complete answer.
Here is a good tutorial: Getting Tkinter Grid Sizing Right the first time
It seems I'm hitting some kind of preset maximum scrollable canvas size, that I didnt know about...
I've written a simple bare-bones iTunes replacement in Tkinter.
Since I like the album cover view, an album needs at least 200x200 px size, and I have A LOT of albums (~1600), it follows that I need a lot of space.
But I discovered that above a height ~ 35000px the window is unable to show them.
Here I written a sample code of it - it needs ImageMagick's convert, and around 15sec to run on my machine.
You can see that the window only shows 163 of the 170 squares...
from Tkinter import *
import subprocess
def main():
root = Tk()
root.geometry("%dx%d+0+0" % (1800,1000))
cv = Canvas(root)
vscrollbar = Scrollbar(root, orient=VERTICAL)
vscrollbar.pack(fill=Y, side=RIGHT)
vscrollbar.config(command=cv.yview)
cv.config(yscrollcommand=vscrollbar.set)
cv.configure(scrollregion=(0,0, 4000, 50000))
cv.pack(side=LEFT, fill=BOTH, expand=TRUE)
fcv=Frame(root)
cv.create_window(0, 0, anchor = "nw", window=fcv)
memimages=[]
for row_index in range(170):
a=subprocess.Popen("convert -size 200x200 -pointsize 22 -gravity center label:%d test.gif" % row_index, shell=True,
stdout=subprocess.PIPE,stderr=subprocess.PIPE)
output, errors = a.communicate()
iconimage = PhotoImage(file="test.gif")
b=Button(fcv,image=iconimage)
memimages.append(iconimage)
b.grid(row=row_index, column=0, sticky=N+S+E+W)
mainloop()
main()
I modified your code to show an image at specific pixel height locations, e.g. one at y=0, one at y=32000 and one at y=50000. The canvas is able to traverse from 0 all the way to 50,000 pixel height, and we can see the images as expected.
This means the canvas is able to scroll all the way to y=50000 pixels and the problem lies not with pixel height limitation of canvas but I am guessing it could be with the manner the button is placed into the frame of the canvas window or the placement of frame in the canvas window or the placement of the canvas window itself into the canvas.
You can run this revised code to see what I mean. Scroll all the way to the bottom. Hope this gives you more insight to troubleshoot your code.
from Tkinter import *
def main():
root = Tk()
root.geometry("%dx%d+0+0" % (1800,1000))
cv = Canvas(root)
vscrollbar = Scrollbar(root, orient=VERTICAL)
vscrollbar.pack(fill=Y, side=RIGHT)
vscrollbar.config(command=cv.yview)
cv.configure(yscrollcommand=vscrollbar.set)
cv.configure(scrollregion=(0,0, 4000, 50000))
cv.pack(side=LEFT, fill=BOTH, expand=TRUE)
iconimage = PhotoImage(file="monkey.gif")
testimage = cv.create_image(300, 0, image=iconimage)
testimage1 = cv.create_image(300, 32000, image=iconimage)
testimage2 = cv.create_image(300, 50000, image=iconimage)
mainloop()
main()
Update: After further testing, it does seems there is a limitation on the display height of the window formed by the Canvas.create_window() method. I added the code below, just before mainloop(), which attempts to create buttons and labels with image of 100x100 pixels. The max. no. of rows of buttons that could be displayed was 316+ while max. no. of rows of labels that could be displayed was 322+. If buttons and labels were created together, the max. no. of row that could be displayed was 316+. My conclusion appears to be identical to yours.
Sorry to not have been able to answer your question. However, I hope to support you with my answer, and recommend someone more knowledgeable explains why this behaviour is the case.
fcv=Frame(cv)
cv.create_window(0, 0, anchor = "nw", window=fcv)
iconimage = PhotoImage(file="monkey100.gif") # Image dimension is 100x100 pixels
for row_index in range(340):
b=Button(fcv,image=iconimage)
b.grid(row=row_index, column=0, sticky=N+S+E+W)
lb=Label(fcv,text=str(row_index), image=iconimage, compound=LEFT)
lb.grid(row=row_index, column=1, sticky=N+S+E+W)
I found a way out the problem.
the limit seens to come from create_window iteself.
So i create multiples windows and this works fine...
from tkinter import *
from PIL import Image, ImageTk, ImageDraw
root = Tk()
vsb = Scrollbar(root, orient=VERTICAL)
vsb.grid(row=0, column=1, sticky=N+S)
hsb = Scrollbar(root, orient=HORIZONTAL)
hsb.grid(row=1, column=0, sticky=E+W)
c = Canvas(root,yscrollcommand=vsb.set, xscrollcommand=hsb.set)
c.grid(row=0, column=0, sticky="news")
vsb.config(command=c.yview)
hsb.config(command=c.xview)
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
w, h = 200,350
image={}
for i in range(0,200):
fr = Frame(c)
c.create_window(2, i*(h+2), window=fr)
image[i]=Image.new ('RGB', (w, h))
draw = ImageDraw.Draw(image[i])
draw.rectangle ((0,0,w,h), fill = (20,20,20) )
draw.text ((1,1), str(i), (255,255,255))
image[i]=ImageTk.PhotoImage(image[i])
btn=Button(fr, image=image[i])
btn.pack()
fr.update_idletasks()
c.config(scrollregion=c.bbox("all"))
root.mainloop()
quit()
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()