Search code examples
pythontkinterpython-imaging-libraryscrollbartkinter-canvas

Why are the bottom half of my card images getting cut off in tkinter?


I am coding a digital version of the card game Dominion to play with some friends. This is only my second time using tkinter for window management and I'm not very skilled at python in general. Anyhow:

The program currently looks like this:

A hand of cards all in a line in a scroll-enabled labelframe, with the bottom half of each card cut off

The main thing I'm confused about it why the cards in the scroll-Frame inside the Canvas are (possibly) causing the visible portion of the frame to stretch to the ends of the screen as desired, but are not causing the whole ensemble to stretch tall enough for card text to be visible. I'm also confused about why there is a gap between Labelframes and the borders they are supposedly being packed on (e.g. the line of green below the hand area is confusing), but I don't know if that issue is related or not.

My code has become increasingly messy as I've tried various methods from online tutorials to fix it, for which I apologize. Here's it now:

from tkinter import *
from PIL import Image, ImageTk

from pathlib import Path
filedirectory = Path.cwd() #sets filedirectory to the current program's location


##Image handling
def ResizeImage(image,new_size=(300,500)):
    tempImage=Image.open(image)
    card=tempImage.resize(new_size)  #Don't re-use variable for somereason, and do in extra lines?  Test later.
    return card #should probably do PhotoImage conversion here



##Window Management
root=Tk() #create a window
root.title("Frederick's Custom Dominion Game") #set the title of the window, which is displayed on window bar among other places.
icon = filedirectory.joinpath('img/icon.ico') #uses relative pathing to get the icon file

root.iconbitmap(icon) #sets the icon to the desired image I made

root.geometry("1800x1000") #set window size
root.configure(background="green") #like windows solitaire

frame = Frame(root,bg="green") #Creates a frame, which is a graphic that's generally designed to be identical to the background of the window and the same size as it for some reason.
frame.pack(side=BOTTOM, fill=BOTH,expand=1) 

midframe=Frame(frame,bg="green") #use so we can pack the other way with the play and discard

player_hand=LabelFrame(frame,text='Hand',height=520,width=1520) #set up three labelFrames (which just draw a square and put a text label in the corner, but look nice)
play_area=LabelFrame(midframe,text='Play',height=520,width=1520) #height and width don't do anything without pack_propagate(0), but that's okay because we shouldn't be using them
discard=LabelFrame(midframe,text='discard',height=520,width=320)


player_hand.pack(side=BOTTOM,fill=X,expand=1) #This should be putting the hand across the entire bottom of the screen, but it's not
player_hand.update() #didn't work, didn't hurt

midframe.pack(side=BOTTOM,fill=BOTH,expand=1)
play_area.pack(side=LEFT,fill=X,expand=1)
discard.pack(side=RIGHT,expand=1)

scroll_bar = Scrollbar(player_hand,orient=HORIZONTAL) #place next to, not in, canvas.  Linking is via configure *on both*

scroll_field=Canvas(player_hand,xscrollcommand=scroll_bar.set,highlightthickness=0)
scroll_field.config(scrollregion=scroll_field.bbox('all')) #bbox=bounding box, target self not inset Frame
scroll_bar.configure(orient=HORIZONTAL, command=scroll_field.xview) #target Canvas, not Frame

inside_frame=Frame(scroll_field, bg='blue') #this should grow to fit its contents, but it doesn't.  Can't pack it, because that breaks scrolling.


###scroll code?

def updateScrollRegion():
    scroll_field.update_idletasks()
    scroll_field.config(scrollregion=scroll_field.bbox('all'))
    root.update()

scroll_bar.pack( side = BOTTOM, fill = X,expand=1 )
scroll_field.create_window(0, 0, window=inside_frame, anchor=NW)
scroll_field.pack(fill=BOTH,side=LEFT,expand=TRUE) #Fill set to BOTH but still not growing vertically


root.after(1000,updateScrollRegion)

###
#deck logic
FuckGarbageCollection=[] #Why the heck are graphics garbage collected while still used in a Label?
flist = []
for f in Path(filedirectory.joinpath('img/Cards')).iterdir():
    if f.is_file():
        print(f)
        flist.append(f)

class Card:
    def __init__(self,file):
        self.image=file
        print(file)
        FuckGarbageCollection.append(ImageTk.PhotoImage(ResizeImage(file))) #should move image processing from here to top of file
        self.label=Label(inside_frame,image=FuckGarbageCollection[-1])
        print("image complete")

AllCards=[]
for i in flist:
   AllCards.append(Card(i))

for i in AllCards[:15]:
    i.label.pack(side=LEFT,expand=1,fill=Y) #Does put cards in the box.  Doesn't make the box fit.
root.update()
scroll_field.configure(scrollregion = scroll_field.bbox("all"))
updateScrollRegion()





def logic():
    root.update()
    player_hand.update()
    scroll_field.update()
    inside_frame.update()
    updateScrollRegion()
    root.after(1000,logic)
    for i in inside_frame.children.values():  #no dice
        i.update() 





root.after(1000,logic)
root.mainloop()# spin forever?

I know I am hitting the coordinate limit when I was letting the whole card list (AllCards instead of AllCards[:15]) into the hand, but even without that the problem persists.

I've tried having various things update(), either before the root.mainloop() or within it via after(). I've tried explicitly telling various things to fill space in various directions or BOTH. I've tried explicitly forcing the Labelframes to be the correct sizes by disabling pack propagation, which works in that it changes their sizes to fill whatever I want but still has their contents (sp. the card images and scrollbar) in weird spots and with the card images cut off with extra grey background showing. Forcing sizes also seems like a bad practice to be learning so I would prefer to avoid it, although I could temporarily make everything work fine on my computer/monitor exclusively via place() (or maybe just propagation disabling, but at that point I'd just use place()).

I've tried reading through some tkinter files on the github, but it's mostly beyond my skill level so I'm pretty sure there isn't an obvious answer/syntax error/missing argument thing but I wouldn't be surprised to learn otherwise.

What I was expecting was that widgets expand to fit their contents, so the cards should all display, to the limits of the screen/window's size and coordinate limits.


Solution

  • You did not specify the height of the canvas, so the default height is less the height of those cards.

    Note that putting widget into the canvas using .create_window() does not affect the size of the canvas.

    So specify the height of the canvas to 500 will fix the issue:

    scroll_field=Canvas(player_hand,xscrollcommand=scroll_bar.set,highlightthickness=0,height=500)