I'm trying to create a GUI for a sort of chat client. I'm fetching the chat with async and I am using multiprocessing to pipe the data to TKinter. That all works fine, but the problem I am currently having is when the new messages come in, they get added to the canvas and I can't scroll and the canvas is horizontally small so I would only be able to see the first two letters of the message anyways. I am like completely new to Python GUI so this is probably a really simple solution.
Here's the code that I have right now:
import tkinter as tk
class App(tk.Tk):
def __init__(self, pipe, *args, **kw):
super().__init__(*args, **kw)
self.app_pipe, _ = pipe
self.check_interval = 250
global message_queue
message_queue = []
self.configure(bg='gray')
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
frame_main = tk.Frame(self, bg='gray')
frame_main.grid(sticky='news')
frame_main.columnconfigure(0, weight=1)
label1 = tk.Label(frame_main, text='Channel')
label1.grid(row=0, column=0, sticky='nw')
frame_canvas = tk.Frame(frame_main, bg='red')
frame_canvas.grid(row=1, column=0, sticky='nw')
canvas = tk.Canvas(frame_canvas, bg='yellow')
canvas.grid(row=0, column=0)
vsb = tk.Scrollbar(canvas, orient='vertical', command=canvas.yview)
vsb.grid(row=0, column=1, sticky='ns')
canvas.configure(yscrollcommand=vsb.set)
global names_frame
names_frame = tk.Frame(canvas)
canvas.create_window((0, 0), window=names_frame, anchor='nw')
frame_canvas.bind('<Configure>', lambda e: canvas.configure(scrollregion=canvas.bbox('all')))
self.bind('<<newMessage>>', self.add_message)
self.after(self.check_interval, self.check)
def check(self):
while self.app_pipe.poll():
msg = self.app_pipe.recv()
if len(message_queue) < 200:
message_queue.append(msg)
elif len(message_queue) == 199:
message_queue.pop(0)
message_queue.append(msg)
self.event_generate('<<newMessage>>', when='tail')
self.after(self.check_interval, self.check)
def add_message(self, *args):
if message_queue[-1]['command'] == 'message':
message = message_queue[-1]['data']
print(message.author.name, len(message_queue))
name = tk.Label(names_frame, text=message.author.name)
name.grid(row=len(message_queue)-1, column=0)
This is what the GUI ends up looking like:
CodePudding user response:
There are two issues:
You have created the scrollbar
vsb
as a child ofcanvas
. It should be a child offrame_canvas
insteadYou need to update the
scrollregion
ofcanvas
whenevernames_frame
is resized, notframe_canvas
class App(tk.Tk):
def __init__(self, pipe, *args, **kw):
super().__init__(*args, **kw)
self.app_pipe, _ = pipe
self.check_interval = 250
global message_queue
message_queue = []
self.configure(bg='gray')
self.columnconfigure(0, weight=1)
self.rowconfigure(0, weight=1)
frame_main = tk.Frame(self, bg='gray')
frame_main.grid(sticky='news')
frame_main.columnconfigure(0, weight=1)
label1 = tk.Label(frame_main, text='Channel')
label1.grid(row=0, column=0, sticky='nw')
frame_canvas = tk.Frame(frame_main, bg='red')
frame_canvas.grid(row=1, column=0, sticky='nw')
canvas = tk.Canvas(frame_canvas, bg='yellow')
canvas.grid(row=0, column=0)
# should create 'vsb' as child of 'frame_canvas' instead
vsb = tk.Scrollbar(frame_canvas, orient='vertical', command=canvas.yview)
vsb.grid(row=0, column=1, sticky='ns')
canvas.configure(yscrollcommand=vsb.set)
global names_frame
names_frame = tk.Frame(canvas)
canvas.create_window((0, 0), window=names_frame, anchor='nw')
# should bind '<Configure>' on 'names_frame' instead
names_frame.bind('<Configure>', lambda e: canvas.configure(scrollregion=canvas.bbox('all')))
self.bind('<<newMessage>>', self.add_message)
self.after(self.check_interval, self.check)
...
Note that it is better to make message_queue
and names_frame
instance variables instead of global variables.