I'm new to python and I'm designing a simple GUI for a tile based game using Tkinter. I've recently found out about PyGame and I may consider using this, but for now I'd like to diagnose this error.
Here's my problem.
The root window holds two frames - a frame for the tiles and a frame for the history of the game.
The frame for the tiles consists of another 11x7 frames that have different coloured backgrounds and the frame for the game history will hold a simple Text widget.
I construct the frames in the root window like so:
# Create the root window
root = tk.Tk()
root.title("The Downfall of Pompeii")
root.geometry("1025x525")
root.configure(background="red")
# root.resizable(False, False)
board_frame = tk.Frame(root, width=825, height=525)
information_frame = tk.Frame(root, width=200, height=525)
board_frame.grid(row=0, column=0),
information_frame.grid(row=0, column=1)
This works as expected: game window
But, when I add the Text widget to the information frame, like this:
information_area = tk.Text(information_frame, width=200, height=525)
information_area.grid(row=0, column=0)
This happens: tiles disappear
The text widget is added and works as expected, but it's now interfered with all the frames(tiles) I added to board_frame. The frames are added piece by piece and then aligned using this for loop:
# Align all frames
for i in range(rows):
for j in range(cols):
frames[i][j].grid(row=i, column=j, columnspan=1, sticky="nsew")
As you can see, each index in frames holds a reference to a frame.
So, my question is, why has adding the Text widget interfered with the tiles when I've placed them in two completely different frames?
For a text widget width and heigth is in numbers of caracters and text lines. width=200, height=525 will be 200 characters wide and 525 lines high. The tiles disappear because the text widget "pushes" them down in the frame.
Try width=10, height=10 and the text widget will re-appear.
Related
I created a main root with two frames.
-One frame is for program toolbar.
-Other frame is for canvas where data will be displayed and a scrollbar widget.
-Inside of the canvas is a third smaller frame which will be used for scrolling trough data.
However, when I try to define new widgets and place them on that third smaller frame, nothing happens. I'm defining new widgets inside of a function call of a button command. I have also tried declaring everything as global variables but without luck.
Hint: I tried placing the code from the function to the top level of the code and it works. Also, if I try to mount these widgets on the toolbar frame it also works. It seems that the only thing I can't do is to mount these new widgets on the small frame that is inside the canvas.
I used a simple for loop to create labels just for testing.
Could anyone tell what I am doing wrong?
from tkinter import *
from tkinter import ttk
#Creating main window
root = Tk()
root.resizable(width=False, height=False)
#Defining Background
toolbar = Frame(root, width=613, height=114)
toolbar.grid(row=0, column=0)
background_frame = Frame(root, width=615, height=560)
background_frame.grid(row=1, column=0)
background = Canvas(background_frame, width=615, height=560)
background.pack(side=LEFT, fill=BOTH, expand=1)
scroll_bar = ttk.Scrollbar(background_frame, orient=VERTICAL, command=background.yview)
scroll_bar.pack(side=RIGHT, fill=Y)
background.configure(yscrollcommand=scroll_bar.set)
background.bind('<Configure>', lambda e:background.configure(scrollregion = background.bbox('all')))
second_frame = Frame(background)
background.create_window(150,100, window=second_frame, anchor='nw')
def confirm1():
for x in range(100):
Label(second_frame, text = x ).grid(row=x, column=1)
show_labels = Button(toolbar, text= "Show labels", fg="black", command=confirm1)
show_labels.grid(row=0, column=2)
root.mainloop()
Picture of the app so far
I surely can't reproduce the issue with your current code, but looking at the previous edit it is pretty clear what your problem is.
(taken from your previous edit)
def confirm1():
global background_image1
background.delete('all') # <--- this line of code
for x in range(100):
Label(second_frame, text = x ).grid(row=x, column=1)
Here you delete all your items from your canvas:
background.delete('all')
hence no item appears.
You should instead delete only those items that you want to remove by passing the id or tags to delete method. You can delete multiple items together at once by giving the same tags.
Another option would be to recreate the frame item again on canvas using create_window (Do note: your frame is not deleted/destroyed, it's only removed from canvas)
I am trying this so I can place buttons in the medFrame, but they appear in the topFrame. When using topFrame, the button is against the top of the screen, which looks bad, so I figured this could be solved by using a third frame.
from tkinter import *
root = Tk()
root.title('BulletHead')
root.attributes('-fullscreen', True)
root.resizable(width = NO, height=NO)
topFrame=Frame(root)
topFrame.pack(side=TOP)
medFrame=Frame(root)
medFrame.pack()
botFrame = Frame(root)
botFrame.pack(side=BOTTOM)
botonJugar = Button(medFrame, text = 'Jugar')
botonJugar.bind("<Button-1>",jugar)
botonJugar.pack()
botonTabla = Button(medFrame, text = 'Tabla de puntajes')
botonTabla.bind("<Button-1>",tabla)
botonTabla.pack()
root.mainloop()
The reason the widgets appear at the top is because topFrame is empty. Since it has no height or width, and no children, it is only one pixel in size. If you give it a width and height or put some widgets in it, you'll see that the button is in fact in the middle frame.
Here is the image I get when I give the frames an artificial width and height, and force the frames to fill in the "x" direction (and also shrink the window down for illustrative purposes):
Here is what I changed (note that I also added color to make the frames easier to see):
topFrame = Frame(root, bg="pink", height=100)
medFrame = Frame(root, bg="bisque")
botFrame = Frame(root, bg="yellow", height=100)
topFrame.pack(side=TOP, fill="x")
medFrame.pack(fill="x")
botFrame.pack(side=BOTTOM, fill="x")
If you want the medium frame to take up all of the extra space, add the expand option and have it fill in both directions:
medFrame.pack(fill="both", expand=True)
Once you add widgets to the top and bottom frames, they will shrink in height to fit their contents, making it really easy top create tool bars and status bars.
The elements have successfully been added to the middle frame however it appears as though they are in the top frame because the topFrame and botFrame have no dimensions so they don't appear (unless you can perceive one pixel). To get some spacing for the middle frame you want to supply some dimensions to the other frames. try this to give the middle frame some spacing:
topFrame=Frame(root, height=200, width=200)
botFrame = Frame(root, height=200, width=200)
An alternative to this is to check out some other options for using the pack geometry manager with only one frame to get the desired results. Particularly the expand= option is helpful for centering objects in a window.
from tkinter import *
root = Tk()
root.title('BulletHead')
root.attributes('-fullscreen', True)
root.resizable(width = NO, height=NO)
medFrame=Frame(root)
medFrame.pack(expand=True)
botonJugar = Button(medFrame, text = 'Jugar')
botonJugar.bind("<Button-1>",jugar)
botonJugar.pack()
botonTabla = Button(medFrame, text = 'Tabla de puntajes')
botonTabla.bind("<Button-1>",tabla)
botonTabla.pack()
root.mainloop()
I'm making a GUI and I'm stuck trying to resize a listbox. The listbox is supposed to expand to fill the frame but instead, the frame shrinks to fit the listbox.
Thanks, in advance, for any help.
I have tried many variations of the code, but none seem to work, so I simplified the code (it still does't work) to put it on here.
import tkinter as tk
w = tk.Tk() # New window
f = tk.Frame(w, width=300, height=500, bg='red') # New frame with specific size
f.grid_propagate(0)
f.grid(row=0, column=0)
lb = tk.Listbox(f, bg='blue') # New listbox
lb.pack(fill=tk.BOTH, expand=True)
I ran these sequentially in IDLE and the frame appears (in red) at the correct size, however, when I pack the listbox, the whole window shrinks to the size of the listbox (and turns completely blue, which is expected).
In my experience, turning off geometry propagation is almost never the right solution. With a couple of decades of using tk and tkinter I've done that only two or three times for very specific edge cases.
Tkinter is very good at making the widget the best size based on the set of widgets you're using. Turning off propagation means you are responsible for doing all calculations to get the window to look right, and your calculations may not be correct when your program runs on a machine with different fonts or a different resolution. Tkinter can handle all of that for you.
Unfortunately, with such a small code example and without knowing your end goal it's hard to solve your layout problems. If your goal is to have a window that is 300x500, the best solution is to make the window that size and then have the frame fill the window, which is easier to do with pack than grid:
import tkinter as tk
w = tk.Tk() # New window
w.geometry("300x500")
f = tk.Frame(w, bg='red') # New frame with specific size
f.pack(fill="both", expand=True)
lb = tk.Listbox(f, bg='blue') # New listbox
lb.pack(fill=tk.BOTH, expand=True)
w.mainloop()
Thank you so much to Tls Chris for your answer and explanation. What I didn't realize is that grid_propagate() and pack_propagate() also affect a widget's children.
Therefore, with the help of Tls Chris, I have fixed my code and it now expands the listbox and doesn't shrink the frame.
Fixed code:
import tkinter as tk
w = tk.Tk() # New window
f = tk.Frame(w, width=300, height=500, bg='red') # New frame
f.grid_propagate(False) # Stopping things (that use grid) resizing the frame
f.pack_propagate(False) # Stopping things (that use pack) resizing the frame
f.grid(row=0, column=0)
lb = tk.Listbox(f, bg='blue') # New listbox
lb.pack(fill=tk.BOTH, expand=True) # Packing the listbox and making it expand and fill the frame
Edit: I think you actually want the listbox to expand to fit the frame. Amended the code to do that as an option
I haven't used pack much but I suspect that grid_propagate changes the behaviour of the grid geometry manager but not of the pack manager.
The below lets app() run with or without propagate set. It uses the grid geometry manager throughout.
import tkinter as tk
def app(propagate = False, expand = False ):
w = tk.Tk() # New window
tk.Label( w, text = 'Propagate: {} \nExpand: {}'.format(propagate, expand) ).grid()
f = tk.Frame(w, width=300, height=500, bg='red') # New frame with specific size
f.grid_propagate(propagate)
f.grid( row=1, column=0 )
lb = tk.Listbox(f, bg='blue') # New listbox
if expand:
f.columnconfigure(0, weight = 1 )
f.rowconfigure(0, weight = 1 )
# lb.pack(fill=tk.BOTH, expand=True)
lb.grid( row=0, column=0, sticky = 'nsew' )
# My guess is that grid_propagate has changed the behaviour of grid, not of pack.
lb.insert(tk.END, 'Test 1', 'Test 2', 'Test 3')
w.mainloop()
This changes the listbox geometry manager to grid. Run app() as below.
app(True, True) # propagate and Expand
app(False, True) # no propagate but expand
app(True, False) # propagate without expand
app() # no propagate or expand
I want to make my buttons a 2 x 2 grid on the bottom of the window with a canvas above them but the buttons always seem to stack weird when I use .pack(side = whatever).
A major thing I also want the buttons and canvas to have relative size i.e. % so that whenever the window is resized the buttons still make up the right area.
I am not sure how to do this being new to tkinter and any help is appreciated.
import tkinter
from tkinter import *
code = Tk()
def LAttack():
print(something);
def HAttack():
print(something);
def FField():
print(something);
def Heal():
print(something);
def Restart():
print(something);
Attack1 = tkinter.Button(code,text = ("Light Attack"), command = LAttack)
Attack1.pack(side = LEFT)
Attack2 = tkinter.Button(code,text = ("Heavy Attack"), command = HAttack)
Attack2.pack(side = RIGHT)
Defense1 = tkinter.Button(code,text = ("Forcefield"), command = FField)
Defense1.pack(side = LEFT)
Defense2 = tkinter.Button(code,text = ("Heal"), command = Heal)
Defense2.pack(side = RIGHT)
Restart1 = tkinter.Button(code,text = ("Restart"), command = Restart)
Restart1.pack(side = TOP)
code.mainloop()
But I want it to look like this:
Mock up for GUI
I want to make my buttons a 2 x 2 grid on the bottom of the window with a canvas above them but the buttons always seem to stack weird when I use .pack(side = whatever).
To me this means that you clearly have two separate areas to be concerned with: a top area with a canvas, and a bottom area with buttons. The first step is to create those two areas. For the top just use the canvas, and for the bottom use a frame.
I'm going to assume you want the canvas to take up as much space as possible, with the buttons always on the bottom. For that sort of arrangement, pack makes the most sense.
The following gives us a program that has a canvas at the top and a frame at the bottom to hold the buttons. When you resize the window, the button frame remains at the bottom and the canvas fills the rest of the space:
import tkinter as tk
root = tk.Tk()
canvas = tk.Canvas(root, background="white")
button_frame = tk.Frame(root)
button_frame.pack(side="bottom", fill="x", expand=False)
canvas.pack(side="top", fill="both", expand=True)
# <button code will be added here...>
root.mainloop()
Now we can focus on the buttons. You want the buttons in a 2x2 grid (though you have 5 buttons...?), so the natural choice is to use grid rather than pack. We want these buttons to be in the bottom frame, so we give that frame as the parent or master of the buttons.
You also somewhat curiously wrote "whenever the window is resized the buttons still make up the right area" even though earlier you said you wanted them on the bottom. I'm going to assume you mean that you want them in the bottom-right corner.
To accomplish this, we are going to create a grid with two rows and three columns. The column on the left will be empty, and it will take up any extra space to force the buttons to be on the right (of course, you can put things in this column if you wish)
This creates four buttons:
attack1 = tk.Button(button_frame, text="Light Attack")
attack2 = tk.Button(button_frame, text="Heavy Attack")
defense1 = tk.Button(button_frame, text="Forcefield")
defense2 = tk.Button(button_frame, text="Heal")
... this causes the first column to expand to fill any extra space:
button_frame.grid_columnconfigure(0, weight=1)
... and this lays them out in a grid (it's always good to separate widget creation from widget layout, because it makes it easier to visualize the layout in the code)
attack1.grid(row=0, column=1, sticky="ew")
attack2.grid(row=0, column=2, sticky="ew")
defense1.grid(row=1, column=1, sticky="ew")
defense2.grid(row=1, column=2, sticky="ew")
The end result is this:
When resized, the buttons retain their relative position:
Summary
The point of this is to show that you need to spend a few minutes organizing the items on the screen into logical groups. There are different layout tools for solving different problems (grid, pack, place), and most windows will benefit from using the right tool for each type of layout problem you're trying to solve (pack is good for horizontal and vertical layouts, grid is good for grids, and place covers a few edge cases where pixel-perfect control is needed).
I am making a text based adventure that prints labels of game text to the screen. I need to make the window scrollable so I am attempting to implement a canvas, containing a frame, which contains the labels.
For some reason when I do this:
display = Tk()
display.geometry('1000x800')
canvas = Canvas(display)
frame = Frame(canvas)
canvas.pack()
frame.pack()
The labels are packed properly, but obviously can't be scrolled.
However when I do this (Create a frame window in the canvas):
canvas = Canvas(display)
frame = Frame(canvas, width=1000, height=700)
canvas.pack()
canvas.create_window((0,0), window=frame, anchor='nw')
The first label does not fit in the frame, and after the second one is printed they all just disappear?
Labels of game text are printed as this function is called throughout the main game loop:
def output(text):
l = Label(frame,text=text,justify=LEFT,font=font)
l.pack()
display.update()
I've been fiddling with container dimensions and some pack() attributes, however nothing seems to be working. The window's dimensions don't seem to change based on the frame's.
I should be able to add a scrollbar if i can just get these to draw properly.
Thanks in advance!