I've been working on a chatbot in tkinter and have been using this, https://stackoverflow.com/a/56032923/12406236 as a starting point for my code. I want to be able to add more speech bubbles (I can only make three) and then scroll through them.
How would I be able to accomplish this?
My code:
from tkinter import *
root = Tk()
frame = Frame(root,width=200,height=200)
frame.grid(row=0,column=0,columnspan=2)
canvas = Canvas(frame,bg="white",width=300,height=300,scrollregion=(0,0,500,500))
canvas.pack(side=LEFT,expand=True,fill=BOTH)
vbar = Scrollbar(frame,orient="vertical",command=canvas.yview)
vbar.pack(side=RIGHT,fill=Y)
canvas.configure(yscrollcommand=vbar.set)
bubbles = []
class BotBubble:
def __init__(self,master,message=""):
self.master = master
self.frame = Frame(master,bg="light grey")
self.i = self.master.create_window(90,160,window=self.frame)
Label(self.frame, text=message,font=("Helvetica", 9),bg="light grey").grid(row=1,column=0)
root.update_idletasks()
self.master.create_polygon(self.draw_triangle(self.i),fill="light grey",outline="light grey")
def draw_triangle(self,widget):
x1, y1, x2, y2 = self.master.bbox(widget)
return x1, y2 - 8, x1 - 10, y2 + 5, x1, y2
def send_message(event):
if bubbles:
canvas.move(ALL, 0, -65)
a = BotBubble(canvas,message=entry.get())
bubbles.append(a)
entry = Entry(root,width=100)
entry.grid(row=1,column=0)
entry.bind("<Return>",send_message)
root.mainloop()
I have modified your code:
do not set scrollregion when creating canvas
update scrollregion of canvas after new bubble is added or window is resized
do not scroll up old bubbles, just add the new bubble below last bubble
Updated code:
from tkinter import *
root = Tk()
frame = Frame(root)
frame.pack(fill=BOTH, expand=1)
canvas = Canvas(frame, bg="white")
canvas.pack(side=LEFT, expand=True, fill=BOTH)
vbar = Scrollbar(frame, orient="vertical", command=canvas.yview)
vbar.pack(side=RIGHT, fill=Y)
canvas.configure(yscrollcommand=vbar.set)
bubbles = []
LINE_GAP = 10 # gap between bubbles
class BotBubble:
def __init__(self, master, message=""):
self.master = master
self.frame = Frame(master, bg="light grey")
bbox = self.master.bbox(ALL)
y = (bbox[3] if bbox else 0) + LINE_GAP
self.i = self.master.create_window(15, y, window=self.frame, anchor='nw')
Label(self.frame, text=message, font=("Helvetica",9), bg="light grey").grid(row=1, column=0)
self.master.update()
self.master.create_polygon(self.draw_triangle(self.i), fill="light grey", outline="light grey")
def draw_triangle(self,widget):
x1, y1, x2, y2 = self.master.bbox(widget)
return x1, y2-8, x1-10, y2+5, x1, y2
def update_scrollregion(event=None):
canvas.config(scrollregion=canvas.bbox(ALL))
canvas.yview_moveto(1) # scroll to bottom
def send_message(event):
a = BotBubble(canvas, message=entry.get())
bubbles.append(a)
update_scrollregion()
entry = Entry(root, width=100)
entry.pack(fill=X)
entry.bind("<Return>", send_message)
entry.focus_force()
frame.bind('<Configure>', update_scrollregion)
root.mainloop()
Related
I want to draw a line between two labels on a canvas, but I do not know how to get the label coordinates to do this - I need to generate a large and variable number of labels each time, which I position on the canvas using pack() or grid.
An example of my code is as below:
import tkinter as tk
master = tk.Tk()
def line(x1, y1, x2, y2):
w.create_line(x1, y1, x2, y2)
label1 = tk.Label(master, text="ONE", bg="red", fg="white")
label1.pack(side="left")
label2 = tk.Label(master, text="TWO", bg="blue", fg="white")
label2.pack(side="right")
w = tk.Canvas(master, width=800, height=100)
w.pack()
line(label1.winfo_rootx(), label1.winfo_rooty(),
label2.winfo_rootx(), label2.winfo_rooty())
master.mainloop()
The above code does not generate any line, but it does display the labels. How can I get the correct coordinates for these labels so I can then use them in the line method to actually draw a line connecting the two labels?
I found I can do what I need done (i.e. draw connecting lines between labels generated on the canvas) by using place geometry instead of pack. For a large number of automatically generated labels, this means using a small method to generate the placement coordinates, but once those sets of coordinates are generated, then joining the labels is easy.
For example:
label1 = tk.Label(master, text="ONE", bg="red", fg="white")
label1.place(x=50, y=80)
label2 = tk.Label(master, text="TWO", bg="blue", fg="white")
label2.place(x=720, y=80)
line(50, 80, 720, 80)
Another way to do this is to generate canvas text objects instead of labels. So this would be like follows:
import tkinter as tk
master = tk.Tk()
def line(x1, y1, x2, y2):
print(x1, y1, x2, y2)
w.create_line(x1, y1, x2, y2, fill="green")
w = tk.Canvas(master, width=800, height=100)
w.pack()
l1 = w.create_text(50, 20, text="ONE", fill="red", tag="l1")
l2 = w.create_text(720, 20, text="TWO", fill="blue", tag="l2")
x1 = w.coords(l1)[0] + 20
y1 = w.coords(l1)[1]
x2 = w.coords(l2)[0] - 20
y2 = w.coords(l2)[1]
line(x1, y1, x2, y2)
How can I create buttons on top of this canvas for each square each with their own unique value (starting from (1,1) in the bottom left corner)? I am trying to make a program to play chess with. I need these squares on the canvas to each have a button with a defined coordinate. This also could be changed to just 64 buttons but this is for a program where squares will be highlighted if they have the option to be moved to.
import tkinter as tk
class Layout(tk.Tk):
colours = ["#563a12", "#9f9362"]#square colours dark then light
def __init__(self, n=8):
super().__init__()
self.n = n
self.leftframe = tk.Frame(self)
self.leftframe.grid(row=0, column=0, rowspan=10, padx=100)
self.middleframe = tk.Frame(self)
self.middleframe.grid(row=0, column=8, rowspan=8)
self.canvas = tk.Canvas(self, width=1200, height=768, )
self.canvas.grid(row=0, column=1, columnspan=8, rowspan=8)
self.board = [[None for row in range(n)] for col in range(n)]
self.colourindex = 0
def changecolours(self):
self.colourindex = (self.colourindex + 1) % 2
def drawboard(self):
for col in range(self.n):
self.changecolours()
for row in range(self.n):
x1 = col * 90
y1 = (7-row) * 90
x2 = x1 + 90
y2 = y1 + 90
colour = self.colours[self.colourindex]
self.board[row][col] = self.canvas.create_rectangle(x1, y1, x2, y2, fill=colour)
self.changecolours()
board = Layout()
board.drawboard()
board.mainloop()
It looks like you just want the coordinates of the board tiles. If so you can tag each tile with a unique name and then bind them using canvas.tag_bind:
import tkinter as tk
class Layout(tk.Tk):
colours = ["#563a12", "#9f9362"]#square colours dark then light
def __init__(self, n=8):
super().__init__()
self.n = n
self.leftframe = tk.Frame(self)
self.leftframe.grid(row=0, column=0, rowspan=10, padx=100)
self.middleframe = tk.Frame(self)
self.middleframe.grid(row=0, column=8, rowspan=8)
self.canvas = tk.Canvas(self, width=1200, height=768, )
self.canvas.grid(row=0, column=1, columnspan=8, rowspan=8)
self.board = [[None for row in range(n)] for col in range(n)]
def drawboard(self):
from itertools import cycle
for col in range(self.n):
color = cycle(self.colours[::-1] if not col % 2 else self.colours)
for row in range(self.n):
x1 = col * 90
y1 = (7-row) * 90
x2 = x1 + 90
y2 = y1 + 90
self.board[row][col] = self.canvas.create_rectangle(x1, y1, x2, y2, fill=next(color), tags=f"tile{col+1}{row+1}")
self.canvas.tag_bind(f"tile{col+1}{row+1}","<Button-1>", lambda e, i=col+1, j=row+1: self.get_location(e,i,j))
def get_location(self, event, i, j):
print (i, j)
board = Layout()
board.drawboard()
board.mainloop()
First I'm sorry for my bad english. I am designing a chatbot with the tkinter as an amateur. I want to add whatsapp-like balloons to the dialog window. I also want the writings to go from the bottom up in the window. I tried to do it as a canvas but it didn't happen. I tried as a label but gave an error.I'd appreciate it if you could help me with that. "TypeError: can only concatenate str (not "Canvas") to str".
The sample design I want
import tkinter as tk
from tkinter import *
import random
import re
import datetime
from tkinter import messagebox
from tkinter.font import Font
root = tk.Tk()
root.title('deneme')
root.geometry('410x600+400+100')
root.config(bg='lightblue')
buton_işlevi=StringVar(root)
buton_işlevi.set("")
#buton oluşturma ve konumu
buton = tk.Button(root, width=10, height=2, relief='raised',state='active',command=lambda :add_text(pencere,giriş,buton_işlevi))
buton.pack()
buton.place(x=310, y=550)
buton.config(text='GÖNDER', bg='lightblue', font='Verdana 8 bold')
root.bind('<Return>',lambda x:add_text(pencere,giriş,buton_işlevi))
#mesaj yazma alanı ve konumu
giriş = tk.Entry(root, textvariable=buton_işlevi,font=('NanumGothic', 12))
giriş.pack()
giriş.place(x=10, y=550, width=290, height=40)
canvas = Canvas(width=300, height=300, bg='white')
canvas.create_oval(200, 200, 300, 300, width=5, fill='red')
#pencere ve konumu
pencere = tk.Text(root, bg='white', yscrollcommand='YES', font=('NanumGothic', 12))
pencere.config(state='disabled')
pencere.pack()
pencere.place(x=10, y=10, width=390, height=530)
#bot ve user mesaj arka plan rengi
pencere.tag_config('bot_renk', background='#fccbc7', foreground='black')
pencere.tag_config('user_renk', background='yellow')
pencere.config(state='normal')
#bot karşılama mesajı
karşılama = 'Hoşgeldiniz'
soru1 = ['merhaba', 'selam']
cevap1 = ["sanada", "iyiyim"]
soru2 = ["deneme", "merhaba", "selam"]
pencere.insert('end', '\nBOT:\t')
pencere.insert('end', karşılama +'\n', 'bot_renk')
#Mesaj ayarları
def add_text(mw,st,imsg):
#mw:message window/st:state/imsg:input message
bot_mesaj=""
user_mesaj = imsg.get()
if user_mesaj in soru1:
user_mesaj='\nUSER :\t'+imsg.get()+'\n'+canvas
bot_mesaj = '\nBOT :\t'+ random.choice(cevap1)+'\n'
mw.config(state='normal')
mw.insert('end',user_mesaj, 'user_renk')
mw.insert('end',bot_mesaj, 'bot_renk')
imsg.set("")
mw.see('end')
mw.config(state='disabled')
elif user_mesaj in soru2:
user_mesaj='\nERÇİN :\t'+imsg.get()+'\n'
bot_mesaj='\nBOT :\t'+ random.choice(cevap2)+'\n'
mw.config(state='normal')
mw.insert('end',user_mesaj, 'user_renk')
mw.insert('end',bot_mesaj, 'bot_renk')
imsg.set("")
mw.see('end')
mw.config(state='disabled')
else :
user_mesaj='\nUSER:\t' +imsg.get()+'\n'
bot_mesaj='\nBOT:\t' + 'Bu kelimeyi henüz öğrenmedim' +'\n'
mw.config(state='normal')
mw.insert('end',user_mesaj, 'user_renk')
mw.insert('end',bot_mesaj, 'bot_renk')
imsg.set("")
mw.see('end')
mw.config(state='disabled')
root.mainloop()
I ran your code and there is no error. But since you mentioned about Whatsapp bubble, here is a basic version you can work on.
from tkinter import *
from datetime import datetime
root = Tk()
root.config(bg="lightblue")
canvas = Canvas(root, width=200, height=200,bg="white")
canvas.grid(row=0,column=0,columnspan=2)
bubbles = []
class BotBubble:
def __init__(self,master,message=""):
self.master = master
self.frame = Frame(master,bg="light grey")
self.i = self.master.create_window(90,160,window=self.frame)
Label(self.frame,text=datetime.now().strftime("%Y-%m-%d %H:%m"),font=("Helvetica", 7),bg="light grey").grid(row=0,column=0,sticky="w",padx=5)
Label(self.frame, text=message,font=("Helvetica", 9),bg="light grey").grid(row=1, column=0,sticky="w",padx=5,pady=3)
root.update_idletasks()
self.master.create_polygon(self.draw_triangle(self.i), fill="light grey", outline="light grey")
def draw_triangle(self,widget):
x1, y1, x2, y2 = self.master.bbox(widget)
return x1, y2 - 10, x1 - 15, y2 + 10, x1, y2
def send_message():
if bubbles:
canvas.move(ALL, 0, -65)
a = BotBubble(canvas,message=entry.get())
bubbles.append(a)
entry = Entry(root,width=26)
entry.grid(row=1,column=0)
Button(root,text="Send",command=send_message).grid(row=1,column=1)
root.mainloop()
i did this mini programme to show databases to the user , i can see buttons of databases but when i press on them borders appear on canvas
from tkinter import *
from tkinter import ttk
import mysql.connector
class mainpro():
def __init__(self):#its my database settings
self.db = mysql.connector.connect(
host="localhost",
user="root",
port=3306,
passwd="1234"
)
self.mycursor = self.db.cursor()
win2 = Toplevel()#idid top level because i did tk before
# Title
win2.title('Manipulate Database')
# geometry
sizex = 1000
sizey = 700
posx = 100
posy = 100
win2.wm_geometry("%dx%d+%d+%d" % (sizex, sizey, posx, posy))
# style
style = ttk.Style()
style.theme_use('vista')
style.configure('TLabel', font=('Calibri', 15))
style.configure('TButton', font=('Calibri', 15, 'bold'))
# menu
menu = Menu(win2)
m1 = Menu(menu, tearoff=0)
menu.add_cascade(label='File', menu=m1)
m2 = Menu(menu, tearoff=0)
menu.add_cascade(label='DLL', menu=m2)
m3 = Menu(menu, tearoff=0)
menu.add_cascade(label='DML', menu=m3)
m4 = Menu(menu, tearoff=0)
menu.add_cascade(label='Help', menu=m4)
m1.add_command(label='Show databases', command=lambda: showdata())
win2.config(menu=menu)
def showdata():
def event(event):
canvas.config(scrollregion=canvas.bbox("all"))
self.mycursor.execute('SHOW DATABASES')
list = self.mycursor.fetchall()
canvas = Canvas(win2, width=1000, height=700)
f1 = Frame(canvas)
canvas.create_window((0, 0), window=f1, anchor='nw')
scroll = Scrollbar(win2, orient="vertical", command=canvas.yview)
scroll.pack(side="right", fill="y")
canvas.configure(yscrollcommand=scroll.set)
canvas.pack()
f1.bind("<Configure>", event)
y = 0
for x in list:
y += 1
ttk.Label(f1, text=str(y) + '-').grid(column=0, row=y, padx=10, pady=10, sticky='w')
ttk.Button(f1, text=x, width=35).grid(column=1, row=y, padx=10, pady=10, sticky='w')
win2.mainloop()
mainpro()
help please
You could try adding highlightthickness=0:
canvas = Canvas(win2, width=1000, height=700, highlightthickness=0)
Try this,
Source: Check
import tkinter # assuming Python 3 for simplicity's sake
import tkinter.ttk as ttk
root = tkinter.Tk()
f = tkinter.Frame(relief='flat')
lF = ttk.LabelFrame(root, labelwidget=f, borderwidth=4)
lF.grid()
b = ttk.Button(lF, text='')
b.grid()
root.mainloop()
Or try this
Canvas=Canvas(self,width=width/2,height=height/2,bg=bgCanvasColor,borderwidth=0, highlightthickness=0)
I have the following piece of code that takes an image within a canvas and then whenever I click the paint function draws a dot over it.
Everything is working fine except that the paint function is not working as expected.
Desirable output
Click event draws a dot. No need to drag the on click event
Actual output
I have to drag the on mouse click event to see a drawing on the canvas.
I guess there might be a slight problem with the paint function. However, I haven't been able to know what it is exactly.
from tkinter import *
from PIL import Image, ImageTk
class Main(object):
def __init__(self):
self.canvas = None
def main(self):
master = Tk()
# Right side of the screen / image holder
right_frame = Frame(master, width=500, height=500, cursor="dot")
right_frame.pack(side=LEFT)
# Retrieve image
image = Image.open("./image/demo.JPG")
image = image.resize((800, 700), Image.ANTIALIAS)
photo = ImageTk.PhotoImage(image)
# Create canvas
self.canvas = Canvas(right_frame, width=800, height=700)
self.canvas.create_image(0, 0, image=photo, anchor="nw")
self.canvas.pack()
self.canvas.bind("<B1-Motion>", self.paint)
mainloop()
def paint(self, event):
python_green = "#476042"
x1, y1 = (event.x - 1), (event.y - 1)
x2, y2 = (event.x + 1), (event.y + 1)
self.canvas.create_oval(x1, y1, x2, y2, fill=python_green, outline=python_green, width=10)
if __name__ == "__main__":
Main().main()
Fix:
Added these two methods:
def on_button_press(self, event):
self.x = event.x
self.y = event.y
def on_button_release(self, event):
python_green = "#476042"
x0,y0 = (self.x, self.y)
x1,y1 = (event.x, event.y)
changed canvas to this:
self.canvas.bind("<ButtonPress-1>", self.on_button_press)
self.canvas.bind("<ButtonRelease-1>", self.on_button_release)
When you only click and don't move the mouse, B1-Motion isn't triggering.
To bind to mouse press (as well as mouse moving), you can add self.canvas.bind("<ButtonPress-1>", self.paint) before the mainloop.