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!
Related
i want to make a chatroom GUI where the user's messages will show up on the right side, and other users' messages will show up on the left side.
in order to do so without having the frame on the canvas shrink to the size of the labels, i have to put frame.pack_propagate(False)
however, doing so disables my scrollbar. from my understanding it is happening because the frame is now 'locked' and any widgets packed into it will simply 'overflow' out of the frame's view.
the mainly important code is as follows:
# main window
root = tk.Tk()
root.title("Chatroom")
root.config(bg="red")
root.geometry("1200x800")
root.resizable(False, False)
chat_canvas = tk.Canvas(root, bg="gray30", height=600, width=1200)
scrollbar = tk.Scrollbar(root, orient='vertical', command=chat_canvas.yview)
chat_canvas.configure(yscrollcommand=scrollbar .set)
msg_frame = tk.Frame(chat_canvas, bg="blue", height=600, width=1200)
msg_frame.pack_propagate(False) #<-- this is a must for me as i want the messages to show up
# on the right side of the frame. without it the frame will
# shrink to the size of the label and stick it to the left
# however without it the scrollbar works rather fine(?)
chat_canvas.pack(fill='both', side='left', expand=True)
scrollbar .pack(side='right', fill='y')
chat_canvas.create_window((0, 0), window=msg_frame, anchor='nw')
msg_frame.bind('<Configure>', func=lambda ev: chat_canvas.configure(scrollregion=chat_canvas.bbox('all')))
when i want to print a message into the frame, the following function executes:
def print_message(msg):
msg_label = tk.Label(msg_frame, text=msg, bg="gray30",state='disabled')
msg_label.pack(anchor='ne', padx=10)
the GUI looks like this without any option to scroll (frame is blue, canvas has white borders):
what am i missing?
Alright, so to start I'm using Python 3.7 with tkinter.
I have a canvas and I can drag a label around with that using the mouse events. My next step is to be able to drag around something on which I can put more widgets. So imagine a box which has a text box and an image on it, perhaps a combobox too. That box can then be dragged around.
I figured perhaps what I needed was a frame widget on my canvas which I could then set up in the same way as I had done with the label. But this is where it seems to fall apart - clearly I'm doing something wrong.
Here's the code I've been playing around with, to no avail:
root = tk.Tk()
root.geometry("800x600")
def ClickedCallback(event):
print(f'Clicked: coords: {event.x}, {event.y}')
def ReleaseCallback(event):
print(f'Released: coords: {event.x}, {event.y}')
def MotionCallback(event):
print(f'Motion: coords: {event.x}, {event.y}')
canvas.coords(frame_id, event.x, event.y)
canvas = tk.Canvas(root, width=1000, height=600, bg='blue')
canvas.bind("<B1-Motion>", MotionCallback)
canvas.pack()
frame = tk.Frame(canvas, bg='green')
frame.pack()
l2 = tk.Label(frame, bg='red')
l2.bind("<Button-1>", ClickedCallback)
l2.pack()
l2['text'] = "Test"
frame_id = canvas.create_window((300,300), window=frame)
label_id = canvas.create_window((100, 100), window=l2)
root.mainloop()
My thinking here is that I attach the frame to the canvas and then the label to the frame so that, f I move the frame, the label within it will be moved too.
The above won't work though, and it tells me the following:
File "C:\Users\JohnSmith\AppData\Local\Programs\Python\Python37-32\lib\tkinter__init__.py", line 2480, in _create
*(args + self._options(cnf, kw))))
_tkinter.TclError: can't use .!canvas.!frame.!label in a window item of this canvas
I may be going about this in entirely the wrong way. I had looked around for something more, but if there's something I'm doing entirely wrong or an established way I should be doing this, I would appreciate it if you could point me in the right direction.
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.
I'm making a card game (called monster master) to develop my python skills, specifically OOP.
I have a GUI that has a few static objects: Player 1's side of the table, Player 2's side, a wee line in the middle and I'm now trying to implement an 'end turn' button.
I have tried a lot of different things to try to get this button to display, but I just can't get it to appear even if there are no errors. Just saying that there are a few commented out lines that I've temporarily taken away for the sake of trying to understand the problem with this button.
Here's the code that I'm currently using to try:
def RunGame():
class App():
"""docstring for App"""
def draw():
# Setting up canvas dimensions
canvas_width = 640
canvas_height = 480
master = Toplevel()
master.title("Monster Master by Charles Cameron - Game")
master.resizable(width=False, height=False)
master.geometry("640x480")
w = Canvas(master,
width=canvas_width,
height=canvas_height)
w.pack()
# Drawing static objects
CentrePoints = [(0, canvas_height/2), (canvas_width/2, canvas_height/2),
(canvas_width, canvas_height/2)]
#Left, centre and right centres (save me from retyping them)
Player1Area = w.create_rectangle(CentrePoints[0], canvas_width,
canvas_height, fill="#303AFE") #Player1 Area
Player2Area = w.create_rectangle(0, 0, CentrePoints[2],
fill="#C31B1B") #Player2 Area
Barrier = w.create_line(CentrePoints[0], CentrePoints[2],
fill="#0090E3", width=20) # Centre barrier
# class GameBtn():
# class EndTurnBtn():
# def __init__(self, BtnName, master):
BtnName = Button(master, bg="white", command=lambda:print("Clicked!"))
image = ImageTk.PhotoImage(file="imgs\EndTurn.png")
BtnName.config(image=image, width="70", height="70")
BtnName.pack(side=RIGHT)
# ChangeTurn = GameBtn.EndTurnBtn('ChangeTurn', master)
master.mainloop()
Window = App()
App.draw()
The button code for the actual button worked fine when I tried it in its own script but stopped working when I put it inside this program.
Hope it's not too dumb a question to ask, quite an amateur at python still and honestly can't find the answer anywhere online.
Many thanks
Your button exists, but it's past the edge of the window. This is because you made your window 640x480, and then completely filled it with a 640x480 canvas. Remove the master.geometry("640x480") line and the window will stretch to contain both your canvas and your button.
You might be thinking "but I don't want the button to appear to the side of the canvas. I want the button to be on the canvas. The canvas only really exists because I wanted to color sections of the background". Embedding widgets on a canvas is possible using create_window (see How to place a widget in a Canvas widget in Tkinter?), but it may be more practical to create colored backgrounds by stacking Frame objects together and giving them individual colors. Example:
from tkinter import Tk, Frame, Button
root = Tk()
root.geometry("640x480")
top_player_frame = Frame(root, height=230, bg="red")
barrier = Frame(root, height=20, bg="green")
bottom_player_frame = Frame(root, height = 230, bg="blue")
#configure column 0 so frames can stretch to fit width of window
root.columnconfigure(0, weight=1)
top_player_frame.grid(row=0, sticky="we")
barrier.grid(row=1, sticky="we")
bottom_player_frame.grid(row=2, sticky="we")
bottom_player_end_turn_button = Button(bottom_player_frame, text="End Turn")
#use `place` here because `pack` or `grid` will collapse the frame to be only as tall as all the widgets it contains, i.e. just this button
bottom_player_end_turn_button.place(x=10,y=10)
root.mainloop()
Result:
I am using ttk.Scrollbar to scroll elements inside a Tkinter.Canvas. Unfortunately a background image of this canvas will also scroll. How can I prevent this?
Here is an example of what I use:
import Tkinter as tk
from ttk import *
# first canvas with scrollbar
root = tk.Tk()
canv1 = tk.Canvas(root, width=200, height=200, bg='green')
sbar = Scrollbar(root, orient="vertical")
sbar.config(command=canv1.yview)
sbar.pack(side="right", fill="y")
canv1.config(yscrollcommand=sbar.set)
canv1.pack(side="right", fill="y")
# background of dirst canvas
photo_content2 = tk.PhotoImage(file = './sample_image.gif')
canv1.create_image(115,300, image=photo_content2)
# scrollable second canvas insode first
canv2 = tk.Canvas(canv1, width=50, height=30, bg='red')
canv2.pack()
canv1.create_window(20,20, window=canv2)
canv1.config(scrollregion=(0,0,300,1000))
root.mainloop()
Scrolling affects everything drawn on the canvas. You can't add a background image that doesn't scroll along with everything else.
That being said, you can create a function that moves the background image after the scrolling happens, and you get the same effect as if the image didn't scroll.
You do this by connecting the scrollbar to a custom function rather than directly to the widgets yview and/or xview commands. In this function you can call the yview and xview commands as normal, and then re-position the background image.
For example, this is how you would handle vertical scrolling; horizontal scrolling would work in a similar way:
def custom_yview(*args, **kwargs):
canvas.yview(*args, **kwargs)
x = canvas.canvasx(0)
y = canvas.canvasy(0)
canvas.coords("background", x,y)
canvas = tk.Canvas(...)
vsb = tk.Scrollbar(orient="vertical", command=custom_yview)
canvas.create_image(..., tags=("background,"))