I'm making a tkinter gui and I want it to work like this:
I have a frame, inside the frame there's a canvas and inside the canvas there are multiple rectangles
I want to make it that once I hover over a rectangle it's color will change from white, to green
simple, right?
so help me figure out what's wrong
Here's the class:
class guiSong:
def __init__(self, master: tkinter.Canvas, songobject: SongFile, x, y, rect=None):
self.master = master
self.songobject = songobject
self.x = x
self.y = y
self.rect = rect
def on_enter(self, event):
self.master.itemconfig(self.rect, fill='green')
print("Should change to green rect ", str(self.rect))
def on_leave(self, enter):
self.master.itemconfig(self.rect, fill='white')
def display(self):
self.rect = self.master.create_rectangle(self.x, self.y, self.x + 1150, self.y + 150, fill='white', tags = ['playbutton',self.songobject])
print("Self Rect is "+str(self.rect)+"!!!!!!!!!!!!!!!!!!!!!!!")
self.master.tag_bind('playbutton',"<Enter>", self.on_enter)
self.master.tag_bind('playbutton',"<Leave>", self.on_leave)
self.albumimg = Image.open(BytesIO(self.songobject.albumimage))
self.albumimg = ImageOps.expand(self.albumimg,border=5)
self.albumimg = self.albumimg.resize((120, 120), Image.ANTIALIAS)
self.img = ImageTk.PhotoImage(self.albumimg)
make_image(self.img, self.x + 25, self.y + 15, self.master)
print(f"Creating image {str(self.img)} at x",self.x+25, " y ",self.y+15 )
return self.img
#self.master.create_image(self.x + 25, self.y + 15, anchor = tkinter.W,image=img)
Don't bother the whole songobject stuff that's unrelated
I made a list of those objects and displayed them all inside a canvas one after another
The expected output is that once I hover over a rectangle it'll turn green
what happens in reality is that only the last rectangle created is colored once hovering over any rectangle.
Maybe this can help you, it's a rectangle that becomes green when you hover it with mouse, red when you leave it, and blue if you click on it.
Note : for blue color, I made an example with an argument in the callback function.
import tkinter as tk
class GUI(tk.Tk):
def __init__(self):
super().__init__()
self.can = tk.Canvas(self, width=200, height=200)
self.can.pack()
self.rect = self.can.create_rectangle(50, 50, 100, 100, fill="gray")
self.can.tag_bind(self.rect, '<Enter>', self.green_rect)
self.can.tag_bind(self.rect, '<Leave>', self.red_rect)
self.can.tag_bind(self.rect, '<Button-1>', lambda x:self.color_rect("blue"))
def color_rect(self, color):
self.can.itemconfigure(self.rect, fill=color)
def green_rect(self, event=None):
self.can.itemconfigure(self.rect, fill="green")
def red_rect(self, event=None):
self.can.itemconfigure(self.rect, fill="red")
gui = GUI()
gui.mainloop()
Since each rectangle is an instance of guiSong, you can directly bind to the canvas item rather than to a tag.
Here's a simplified version of your class:
class guiSong:
def __init__(self, master, songobject, x, y):
self.master = master
tags = ("playbutton", songobject)
self.rect = master.create_rectangle(x,y,x+100, y+100, tags=tags, fill="white")
self.master.tag_bind(self.rect, "<Enter>", self.on_enter)
self.master.tag_bind(self.rect, "<Leave>", self.on_leave)
def on_enter(self, event):
self.master.itemconfigure(self.rect, fill="red")
def on_leave(self, event):
self.master.itemconfigure(self.rect, fill="white")
If you wish to bind to the tag, you can use the tag "current" to refer to the object that received the event.
class guiSong:
...
def on_enter(self, event):
self.master.itemconfigure("current", fill="red")
def on_leave(self, event):
self.master.itemconfigure("current", fill="white")
Related
I have a custom round button in tkinter
Here is the code:
class RoundedButton(tk.Canvas):
def __init__(self, parent, bg, width, height = None, command=None, color = "red", padding = 0, cornerradius = None):
height = width if height == None else height
cornerradius = min(width, height) / 2 if cornerradius == None else cornerradius
tk.Canvas.__init__(self, parent, borderwidth=0,
relief="flat", highlightthickness=0, bg=bg)
self.command = command
if cornerradius > 0.5*width:
print("Error: cornerradius is greater than width.")
return None
if cornerradius > 0.5*height:
print("Error: cornerradius is greater than height.")
return None
rad = 2*cornerradius
def shape():
self.create_polygon((padding,height-cornerradius-padding,padding,cornerradius+padding,padding+cornerradius,padding,width-padding-cornerradius,padding,width-padding,cornerradius+padding,width-padding,height-cornerradius-padding,width-padding-cornerradius,height-padding,padding+cornerradius,height-padding), fill=color, outline=color)
self.create_arc((padding,padding+rad,padding+rad,padding), start=90, extent=90, fill=color, outline=color)
self.create_arc((width-padding-rad,padding,width-padding,padding+rad), start=0, extent=90, fill=color, outline=color)
self.create_arc((width-padding,height-rad-padding,width-padding-rad,height-padding), start=270, extent=90, fill=color, outline=color)
self.create_arc((padding,height-padding-rad,padding+rad,height-padding), start=180, extent=90, fill=color, outline=color)
id = shape()
(x0,y0,x1,y1) = self.bbox("all")
width = (x1-x0)
height = (y1-y0)
self.configure(width=width, height=height)
self.bind("<ButtonPress-1>", self._on_press)
self.bind("<ButtonRelease-1>", self._on_release)
def _on_press(self, event):
# print(event)
self.configure(relief="sunken")
def _on_release(self, event):
# print(event)
self.configure(relief="raised")
if self.command is not None:
self.command()
This code works but it doesn't have the functionality to add text to it. I think that to add text to this Rounded Button I would need to create another function in the class which would take the x and y coordinates and place it in it's parent, However while placing we would need to create some label or some text that would be placed according to the x and y coordinates. However, I don't know how can I calculate the position of the text that should be placed. Can someone help me out here. I am sure there must be some calculation that can be done to find the position of the text.
You just need to put the text at the center of canvas using .create_text(...):
def __init__(self, parent, bg, width, height=None, text="", font=None, command=None, color="red", padding=0, cornerradius=None):
...
height = (x1-x0)
width = (y1-y0)
# show the text at the center of canvas
self.create_text(width/2, height/2, text=text, font=font)
...
While #acw1668 will work I suggest you bind configure event to an event handler and readjust the position. In this way, the text will always stay at the center
class RoundedButton(tk.Canvas):
def __init__(self, parent, bg, text='Hello', width=10, height = 10, command=None, color = "red", padding = 0, cornerradius = 4):
super().__init__(parent)
....
self.btn_text = self.create_text(0, 0, text=text)
self.bind('<Configure>', self.readjust)
....
def readjust(self, event):
self.coords(self.btn_text, event.width/2, event.height/2)
I'm creating two rectangles. I want to delete rectangles from the canvas by right-clicking. The code is able to delete only 1 rectangle but not the other one. I used tag_bind("Button1") function but only bottom one is getting deleted.
Tag_bind function should be able to get the id and delete any of the selected rectangles but it is not happening.
#import sys, os, string, time
import tkinter
tk = tkinter
root =tk.Tk()
root.title ("Drag-N-Drop Demo")
# A Python example of drag and drop functionality within a single Tk widget.
# The trick is in the bindings and event handler functions.
# Tom Vrankar twv at ici.net
canvas =tk.Canvas ( width =256, height =256,
relief =tk.RIDGE, background ="white", borderwidth =1)
class CanvasDnD (tk.Frame):
def __init__ (self, master):
self.master =master
self.loc =self.dragged =0
tk.Frame.__init__ (self, master)
id=canvas.create_rectangle(75,75,100,100,tags="DnD")
canvas.tag_bind(id,"<ButtonPress-1>")
id=canvas.create_rectangle(100,100,125,125,tags="DnD")
canvas.tag_bind(id,"<ButtonPress-1>")
canvas.pack (expand =1, fill =tk.BOTH)
canvas.tag_bind ("DnD", "<ButtonPress-1>", self.down)
canvas.tag_bind ("DnD", "<ButtonRelease-1>", self.chkup)
canvas.tag_bind ("DnD", "<Enter>", self.enter)
canvas.tag_bind ("DnD", "<Leave>", self.leave)
self.popup = tk.Menu(root, tearoff=0)
self.popup.add_command(label="delete",command=lambda: self.dele(id))
root.bind("<Button-3>", self.do_popup)
def do_popup(self,event):
# display the popup menu
try:
self.popup.tk_popup(event.x_root, event.y_root, 0)
finally:
# make sure to release the grab (Tk 8.0a1 only)
self.popup.grab_release()
# empirical events between dropee and target, as determined from Tk 8.0
# down.
# leave.
# up, leave, enter.
def down (self, event):
#print ("Click on %s" %event.widget.itemcget (tk.CURRENT, "text"))
self.loc =1
self.dragged =0
event.widget.bind ("<Motion>", self.motion)
def motion (self, event):
root.config (cursor ="exchange")
cnv = event.widget
cnv.itemconfigure (tk.CURRENT, fill ="blue")
x,y = cnv.canvasx(event.x), cnv.canvasy(event.y)
a,b = cnv.canvasx(event.x + 25), cnv.canvasy(event.y+25)
got = event.widget.coords (tk.CURRENT, x, y, a, b)
def leave (self, event):
self.loc =0
def enter (self, event):
self.loc =1
if self.dragged ==event.time:
self.up (event)
def chkup (self, event):
event.widget.unbind ("<Motion>")
root.config (cursor ="")
self.target =event.widget.find_withtag (tk.CURRENT)
#event.widget.itemconfigure (tk.CURRENT, fill =self.defaultcolor)
if self.loc: # is button released in same widget as pressed?
self.up (event)
else:
self.dragged =event.time
def up (self, event):
event.widget.unbind ("<Motion>")
if (self.target ==event.widget.find_withtag (tk.CURRENT)):
print("1")
# print ("Select %s" %event.widget.itemcget (tk.CURRENT, "text"))
else:
event.widget.itemconfigure (tk.CURRENT, fill ="blue")
self.master.update()
time.sleep (.1)
print ("%s Drag-N-Dropped onto %s" \
%(event.widget.itemcget (self.target, "text")),
event.widget.itemcget (tk.CURRENT, "text"))
event.widget.itemconfigure (tk.CURRENT, fill =self.defaultcolor)
def dele(self,id):
canvas.delete(id)
CanvasDnD (root).pack()
root.mainloop()
Question: first I will select that rectangle with "Button 1" and then I will right-click and delete
Create the rectangles ...
canvas.create_rectangle(75, 75, 100, 100, tags="DnD")
canvas.create_rectangle(100, 100, 125, 125, tags="DnD")
Bind event "<ButtonPress-1>" to the Canvas
canvas.bind("<ButtonPress-1>", self.on_button_1)
Prepare the popup, to delete items with tag='DELETE'
self.popup.add_command(label="delete",
command=lambda: canvas.delete(canvas.find_withtag('DELETE')))
Define the event "<ButtonPress-1>" callback.
Here, the matching item get added tags='DELETE' and outlined 'red'.
def on_button_1(self, event):
iid = canvas.find_enclosed(event.x - 26, event.y - 26, event.x + 26, event.y + 26)
canvas.itemconfigure(iid, tags='DELETE', outline='red')
My problem is that when I consult an image from D:/Folder/my_drawing.jpg after "cleaning" the canvas, the canvas is dirty with the previous drawn images. The canvas is visually clear, but accumulates the former drawn image and the new one. The goal is make a Paint like program, that allows save draws and with a button that clean all the canvas.
The behaviour is as follow:
First I draw the curved line, after I clean the canvas, and after that, I draw the line, and when I consult the image, opening the file, the image is composed as shown below:
This is the code, in Python:
import os
from tkinter import *
from PIL import Image, ImageDraw
class Paint(object):
def __init__(self):
self.root = Tk()
self.pen_button = self.use_pen
self.save_button = Button(self.root, text='Save', command=self.Save)
self.save_button.grid(row=0, column=3)
self.eraser_button = Button(self.root, text='Clean canvas', command=self.use_eraser)
self.eraser_button.grid(row=0, column=1)
self.c = Canvas(self.root, bg='white', width=600, height=600)
self.c.grid(row=1, columnspan=5)
self.setup()
self.root.mainloop()
def activate_button(self, some_button):
self.active_button = some_button
def use_pen(self):
self.activate_button(self.pen_button)
def setup(self):
self.path=''
self.old_x = None
self.old_y = None
self.image1 = Image.new("RGB",(600,600),'white')
self.draw = ImageDraw.Draw(self.image1)
self.active_button = self.pen_button
self.c.bind('<B1-Motion>', self.paint)
self.c.bind('<ButtonRelease-1>', self.reset)
def use_eraser(self):
self.c.delete(ALL)
def Save(self):
self.c.postscript(file="my_drawing.jpg", colormode='color')
filename = "my_drawing.jpg"
self.image1.save(filename)
def paint(self, event):
self.line_width = 2.0
paint_color = 'black'
if self.old_x and self.old_y:
self.c.create_line(self.old_x, self.old_y, event.x, event.y,
width=self.line_width, fill=paint_color, dash=(),capstyle=ROUND, smooth=TRUE, splinesteps=36)
self.draw.line([self.old_x, self.old_y, event.x, event.y], fill="black", width=5)
self.old_x = event.x
self.old_y = event.y
def reset(self, event):
self.old_x, self.old_y = None, None
if __name__ == '__main__':
Paint()
Yes, well..., you never actually display the image on the canvas.
In the function paint() you first draw a line segmant on the canvas, then you draw it in the image.
The function use_eraser() clears the canvas but does nothing with the image self.image1.
So, don't draw anything on the canvas but only on the image, and then display the image on the canvas. I have not worked much with PIL but I should think the image display will update automatically as you draw on it.
I've solved it with this changes. I hope it serves to another with the same problem.
import io
def Save(self):
ps=self.c.postscript(colormode='color')
img = Image.open(io.BytesIO(ps.encode('utf-8')))
img.save('D:/Folder/my_drawing.jpg')
def paint(self, event):
self.line_width = 2.0
paint_color = self.color
if self.old_x and self.old_y:
self.c.create_line(self.old_x, self.old_y, event.x, event.y,
width=self.line_width, fill=paint_color, dash=(),
capstyle=ROUND, smooth=TRUE, splinesteps=36)
self.old_x = event.x
self.old_y = event.y
I am trying to draw my own graphic within a kivy 'canvas'. For now I have a red or green rectangle which changes colour once per second, but I want to add a changing text label.
After a little searching it appears that there isn't a "Text" Instruction which can be added to the canvas. I have found a few references to using a Label() widget as well as the canvas Instructions, but this does not seem ideal, and also I can't seem to get it to render more than once.
Here's my object as it stands at the moment:
class HVObject(BoxLayout):
def __init__(self, **kwargs):
BoxLayout.__init__(self, **kwargs)
self.colour = 1
self.label = Label()
self.render()
self.add_widget(self.label)
self.bind(size=self._update_rect, pos=self._update_rect)
Clock.schedule_interval(self.callevery, 1)
def render(self):
self.canvas.clear()
self.rect = Rectangle(size=self.size, pos=self.pos)
self.canvas.add(Color(1-self.colour, self.colour, 0, 1))
self.canvas.add(self.rect)
self.label.text = "COL %d" % self.colour
self.canvas.ask_update()
def callevery(self, x):
self.colour = 1-self.colour
self.render()
def _update_rect(self, instance, value):
self.rect.pos = instance.pos
self.rect.size = instance.size
self.label.pos = instance.pos
Is there an easy way to achieve the effect I need?
Thank you
Answering my own question:
After a little look around the [kivy] garden, I found Tickline (and Tick). and the use of CoreLabel() and Rectangle(texture=...)
Here's my updated render() method which adds the text object I need.
def render(self):
self.canvas.clear()
self.canvas.add(Color(1-self.colour, self.colour, 0, 1))
self.rect = Rectangle(size=self.size, pos=self.pos)
self.canvas.add(self.rect)
label = CoreLabel(text="COL %d" % self.colour, font_size=20)
label.refresh()
text = label.texture
self.canvas.add(Color(self.colour, 1-self.colour,0, 1))
pos = list(self.pos[i] + (self.size[i] - text.size[i]) / 2 for i in range(2))
self.canvas.add(Rectangle(size=text.size, pos=pos, texture=text))
self.canvas.ask_update()
Which works for me, albeit a little clunky!
I'm trying to make a custom text widget that is double buffered (In order to avoid flicker).
However, I'd like to be able to do a few things. Yet, I'm unsure of the exact methods I should use.
The first two are easy I simply want to change the background and foreground color.
So more or less I want to be able to change the text color for self.Text in self.Draw().
Snippet:
self.Text = mdc.DrawText(self.TextString, 10, 0)
As sell as the Background (fill) color for self.MemoryDC.
Next, does anyone know how I could center self.Text? Finally, how do I configure self.Text after it has been created?
The widget thus far:
class DynamicText (wx.Panel):
def __init__(self, par):
self.Par = par
wx.Panel.__init__(self, self.Par)
self.Time = Time(self, func=self.SetTime)
self.Dim = self.Par.GetClientSize()
self.SetSize(self.Dim)
self.Bind(wx.EVT_SIZE, self.Resize)
self.Bind(wx.EVT_ERASE_BACKGROUND, self.Erase)
self.Bind(wx.EVT_PAINT, self.Paint)
def Set (self, text) :
self.TextString = text
def SetTime (self, time) :
self.Set(str(time))
self.Resize(None)
def Resize(self, event):
self.Width, self.Height = self.GetSize()
bitmap = wx.EmptyBitmap(self.Width, self.Height)
self.MemoryDC = wx.MemoryDC(bitmap)
''' Redraws **self.MemoryDC** '''
mdc = self.MemoryDC
''' Deletes everything from widget. '''
mdc.Clear()
fs = 11
font = wx.Font( fs, wx.DEFAULT, wx.NORMAL, wx.NORMAL)
mdc.SetFont(font)
self.Draw()
self.Refresh()
def Draw (self) :
mdc = self.MemoryDC
self.Text = mdc.DrawText(self.TextString, 10, 0)
def Erase(self, event):
''' Does nothing, as to avoid flicker. '''
pass
def Paint(self, event):
pdc = wx.PaintDC(self)
w, h = self.MemoryDC.GetSize()
pdc.Blit(0, 0, w, h, self.MemoryDC, 0, 0)
I don't understand what you mean by configuring self.Text after it was created. If you want to change the text after you've drawn it - you can't. Once you've drawn it to the DC it's there, and the only way to change it would be to clear the DC and repaint it. In your case, it seems all you need to do when the text is updated is to call Resize() again, forcing a redraw. Note that DrawText() retruns nothing, so the value of your self.Text would be None. You definitely can't use that to refer to the drawn text. :D
As for the rest, here's an example of a Draw() method that centers the text and paints it blue:
def Draw(self) :
mdc = self.MemoryDC
dc_width, dc_height = mdc.GetSizeTuple()
text_width, text_height, descent, externalLeading = mdc.GetFullTextExtent(self.TextString)
x = (dc_width - text_width) / 2
y = (dc_height - text_height) / 2
mdc.SetTextForeground('Blue')
mdc.DrawText(self.TextString, x, y)