How to implement a mouse hovering callback on canvas items in tkinter? - python

I used the code below which I found on internet to implement a mouse hovering action in python:
from tkinter import *
import numpy as np
class rect:
def __init__(self, root):
self.root = root
self.size = IntVar()
self.canvas = Canvas(self.root, width=800, height=300)
self.scale = Scale(self.root, orient=HORIZONTAL, from_=3, to=20, tickinterval=1, variable=self.size)
self.scale.bind('<ButtonRelease>', self.show)
self.canvas.bind('<Motion>', self.motion)
self.board = []
self.array = np.zeros((self.scale.get(),self.scale.get())).tolist()
self.canvas.pack()
self.scale.pack()
def motion(self,event):
if self.canvas.find_withtag(CURRENT):
current_color = self.canvas.itemcget(CURRENT, 'fill')
self.canvas.itemconfig(CURRENT, fill="cyan")
self.canvas.update_idletasks()
self.canvas.after(150)
self.canvas.itemconfig(CURRENT, fill=current_color)
def show(self,event):
self.canvas.delete('all')
x = 50
y = 50
row = []
self.board.clear()
for i in range(self.scale.get()):
row = []
for j in range(self.scale.get()):
rectangle = self.canvas.create_rectangle(x, y, x + 50, y + 50, fill='red')
x += 50
row.append(rectangle)
x -= j*50
y +=50
self.board.append(row)
print(self.board)
root = Tk()
a = rect(root)
root.mainloop()
The problem with the execution is that the color of the item changes to blue only for a limited time.
I need the color of each item in the canvas to be changed whenever I enter its zone and remain blue until the mouse is leaving the item.

You can pass the argument activefill when creating your rectangle.
From effboot.org:
Fill color to use when the mouse pointer is moved over the item, if
different from fill.
To do so, replace:
rectangle = self.canvas.create_rectangle(x, y, x + 50, y + 50, fill='red')
By:
rectangle = self.canvas.create_rectangle(x, y, x + 50, y + 50, fill='red', activefill='cyan')
This removes the need to bind Motion to your canvas, and also makes the code noticebly shorter:
from tkinter import *
import numpy as np
class rect:
def __init__(self, root):
self.root = root
self.size = IntVar()
self.canvas = Canvas(self.root, width=800, height=300)
self.scale = Scale(self.root, orient=HORIZONTAL, from_=3, to=20, tickinterval=1, variable=self.size)
self.scale.bind('<ButtonRelease>', self.show)
self.board = []
self.array = np.zeros((self.scale.get(),self.scale.get())).tolist()
self.canvas.pack()
self.scale.pack()
def show(self,event):
self.canvas.delete('all')
x = 50
y = 50
row = []
self.board.clear()
for i in range(self.scale.get()):
row = []
for j in range(self.scale.get()):
rectangle = self.canvas.create_rectangle(x, y, x + 50, y + 50, fill='red', activefill='cyan')
x += 50
row.append(rectangle)
x -= j*50
y +=50
self.board.append(row)
print(self.board)
root = Tk()
a = rect(root)
root.mainloop()

I changed motion method and added self.last = None to __init__ method:
from tkinter import *
import numpy as np
class rect:
def __init__(self, root):
self.root = root
self.size = IntVar()
self.canvas = Canvas(self.root, width=800, height=300)
self.scale = Scale(self.root, orient=HORIZONTAL, from_=3, to=20, tickinterval=1, variable=self.size)
self.scale.bind('<ButtonRelease>', self.show)
self.canvas.bind('<Motion>', self.motion)
self.board = []
self.array = np.zeros((self.scale.get(),self.scale.get())).tolist()
self.canvas.pack()
self.scale.pack()
self.last = None
def motion(self, event):
temp = self.canvas.find_withtag(CURRENT)
if temp == self.last:
self.canvas.itemconfig(CURRENT, fill="cyan")
self.canvas.update_idletasks()
else:
self.canvas.itemconfig(self.last, fill="red")
self.last = temp
def show(self,event):
self.canvas.delete('all')
x = 50
y = 50
row = []
self.board.clear()
for i in range(self.scale.get()):
row = []
for j in range(self.scale.get()):
rectangle = self.canvas.create_rectangle(x, y, x + 50, y + 50, fill='red')
x += 50
row.append(rectangle)
x -= j*50
y +=50
self.board.append(row)
print(self.board)
root = Tk()
a = rect(root)
root.mainloop()

Related

How to select a color in Tkinter?

I have a program written in Python, that makes a window where you can draw, using Tkinter. Every time you left-click your mouse, you make a point in your canvas. When you double-click, a polygon is made, filled with the color you chose. I found a way to change the colors from the boxes, when you right-click a box, but the problem is that the selected color is not saved and i cannot make it replace the previous one. Does anyone know how to solve this problem?
import tkinter as tk
from tkinter import colorchooser
class Point():
def __init__(self, canvas, x, y):
self.x = x
self.y = y
canvas.create_oval(x-2, y-2, x+2, y+2, fill='white')
class Poly():
def __init__(self, canvas, board, p_list=[] ):
self.p_list = p_list
self.canvas = canvas
self.board = board
def draw_poly(self):
points = []
for p in self.p_list:
points.extend([p.x, p.y])
points.extend(points[:2])
self.canvas.create_polygon(points, fill=self.board.current_color, outline=self.board.current_color)
def add_point(self, p):
self.p_list.append(p)
if len(self.p_list)>1:
p1 = self.p_list[-1]
p2 = self.p_list[-2]
self.canvas.create_line(p1.x, p1.y, p2.x, p2.y, fill="white", width=2)
class Palette():
def __init__(self, frame, board, colors):
self.colors = colors
self.board = board
self.allColors = []
for color in self.colors:
f = tk.Frame(frame, bg='lightgrey', bd=3)
f.pack(expand=1, fill='both', side='left')
if self.board.current_color == color: f.config(bg='red')
self.allColors.append(f)
l = tk.Label(f, bg=color)
l.pack(expand=1, fill='both', padx=2, pady=2)
l.bind("<1>", self.set_color)
l.bind("<Button-3>", self.do_popup)
def do_popup(self, event):
clsheet = tk.colorchooser.askcolor()
self.current_color = clsheet[1]
def set_color(self, e):
self.board.current_color = e.widget['bg']
self.selected_color(e.widget.master)
def selected_color(self, colorFrame):
for f in self.allColors: f.config(bg = 'lightgrey')
colorFrame.config(bg="red")
class Board():
def __init__(self, root):
self.colors = ['#B4FE98', '#77E4D4', '#F4EEA9', '#F0BB62', '#FF5F7E', "#9A0680"]
self.root = root
self.current_color = self.colors[0]
self.f1 = tk.Frame(self.root)
self.f1.pack(expand=1, fill='both', padx=5)
self.f2 = tk.Frame(self.root)
self.f2.pack(expand=1, fill='both')
self.canvas = tk.Canvas(self.f2, bg="#000D6B", height=550)
self.canvas.pack(expand=1, fill='both', padx=5, pady=5)
self.pallette = Palette(self.f1, self, self.colors )
self.canvas.bind("<1>", self.draw_point)
self.canvas.bind("<Double-Button-1>", self.draw_poly)
self.poly = None
def draw_point(self, evnt):
if self.poly: self.poly.add_point(Point(self.canvas, evnt.x, evnt.y))
else: self.poly = Poly(self.canvas, self, [Point(self.canvas, evnt.x, evnt.y)])
def draw_poly(self, evnt):
if self.poly and len(self.poly.p_list) > 2:
self.poly.add_point(Point(self.canvas, evnt.x, evnt.y))
self.poly.draw_poly()
self.poly = None
else: self.draw_point(evnt)
#main program
root = tk.Tk()
root.title('my program')
root.geometry("600x700")
root.resizable(0,0)
Board(root)
tk.mainloop()
To fix the part where right-clicking a color was not working i changed two things in your script:
You stored your frame widgets and their sub-widget labels in Palette.allColors. I handed over the index of the selected color to the do_popup event by using partial. Then you can simply iterate over all widgets in Palette.allColors and if the index from the event matches the index in the list, you access the children and further the !label key of those and change the background color to the selected color.
I matched Board.current_color and Palette.current_color
Most changes were made in Palette.do_popup(). Might not be the most elegant solution but it looks like its working like you intend. Full code:
import tkinter as tk
from tkinter import colorchooser
from functools import partial
class Point():
def __init__(self, canvas, x, y):
self.x = x
self.y = y
canvas.create_oval(x - 2, y - 2, x + 2, y + 2, fill='white')
class Poly():
def __init__(self, canvas, board, p_list=[]):
self.p_list = p_list
self.canvas = canvas
self.board = board
def draw_poly(self):
points = []
for p in self.p_list:
points.extend([p.x, p.y])
points.extend(points[:2])
self.canvas.create_polygon(points, fill=self.board.current_color, outline=self.board.current_color)
def add_point(self, p):
self.p_list.append(p)
if len(self.p_list) > 1:
p1 = self.p_list[-1]
p2 = self.p_list[-2]
self.canvas.create_line(p1.x, p1.y, p2.x, p2.y, fill="white", width=2)
class Palette():
def __init__(self, frame, board, colors):
self.colors = colors
self.board = board
self.allColors = []
for idx, color in enumerate(self.colors):
f = tk.Frame(frame, bg='lightgrey', bd=3)
f.pack(expand=1, fill='both', side='left')
if self.board.current_color == color: f.config(bg='red')
self.allColors.append(f)
l = tk.Label(f, bg=color)
l.pack(expand=1, fill='both', padx=2, pady=2)
l.bind("<1>", self.set_color)
l.bind("<Button-3>", partial(self.do_popup, idx))
def do_popup(self, idx, event):
clsheet = tk.colorchooser.askcolor()
self.current_color = clsheet[1].upper()
print(f"You chose: {self.current_color}")
self.board.current_color = self.current_color # required?
self.selected_color(event.widget.master)
for frm_idx, frm in enumerate(self.allColors):
if frm_idx == idx:
frm.children["!label"].config(bg=self.current_color)
def set_color(self, e):
self.board.current_color = e.widget['bg']
self.selected_color(e.widget.master)
def selected_color(self, colorFrame):
for f in self.allColors: f.config(bg='lightgrey')
colorFrame.config(bg="red")
class Board():
def __init__(self, root):
self.colors = ['#B4FE98', '#77E4D4', '#F4EEA9', '#F0BB62', '#FF5F7E', "#9A0680"]
self.root = root
self.current_color = self.colors[0]
self.f1 = tk.Frame(self.root)
self.f1.pack(expand=1, fill='both', padx=5)
self.f2 = tk.Frame(self.root)
self.f2.pack(expand=1, fill='both')
self.canvas = tk.Canvas(self.f2, bg="#000D6B", height=550)
self.canvas.pack(expand=1, fill='both', padx=5, pady=5)
self.pallette = Palette(self.f1, self, self.colors)
self.canvas.bind("<1>", self.draw_point)
self.canvas.bind("<Double-Button-1>", self.draw_poly)
self.poly = None
def draw_point(self, evnt):
if self.poly:
self.poly.add_point(Point(self.canvas, evnt.x, evnt.y))
else:
self.poly = Poly(self.canvas, self, [Point(self.canvas, evnt.x, evnt.y)])
def draw_poly(self, evnt):
if self.poly and len(self.poly.p_list) > 2:
self.poly.add_point(Point(self.canvas, evnt.x, evnt.y))
self.poly.draw_poly()
self.poly = None
else:
self.draw_point(evnt)
# main program
root = tk.Tk()
root.title('my program')
root.geometry("600x700")
root.resizable(0, 0)
Board(root)
tk.mainloop()

How to move a shape while retaining original size in tkinter

I am trying to have the rectangle I create spawn at the bottom of my tkinter canvas instead of spawning at the top without affecting its size. I have only found this post and this doesn't help me as it does not describe how to move the shape to the bottom of the canvas
Here's my code:
from tkinter import *
from tkinter.ttk import *
#Temporary
width = 1024
height=768
class GFG:
def __init__(self, master = None):
self.master = master
self.master.configure(bg="black") #Apart from the canvas colour
self.x = 0
self.y = 0
self.canvas = Canvas(master, background="black", highlightthickness=0, width=width, height=height) #Canvas colour
self.rectangle = self.canvas.create_rectangle(5, 5, 25, 25, fill='white')
self.canvas.pack()
self.movement()
def movement(self):
self.canvas.move(self.rectangle, self.x, self.y)
self.canvas.after(100, self.movement)
def left(self, event):
print(event.keysym)
self.x = -5
self.y = 0
def right(self, event):
print(event.keysym)
self.x = 5
self.y = 0
if __name__ == "__main__":
master = Tk()
master.title("Black Hole Settlers")
gfg = GFG(master)
master.bind("<KeyPress-Left>", lambda e: gfg.left(e))
master.bind("<KeyPress-Right>", lambda e: gfg.right(e))
mainloop()
Pastebin
Thanks,
swanserquack

Change the position of a frame in Tkinter, Involving Classes and functions

I'm trying to move a frame on my GUI, but I am unable to do so because of my classes. How can I move the frame2 with a
Slider1.SetVal(x=20, y=30)
while keeping my classes.
My Code
class Slider:
def __init__ (self,Name, x, y, Height, Width, KnobHeight, KnobWidth, LowVal, HighVal, BgColor, FgColor):
self.Name = Name
self.x = x
self.y = y
self.Height = Height
self.Width = Width
self.KnobHeight = KnobHeight
self.KnobWidth = KnobWidth
self.LowVal = LowVal
self.HighVal = HighVal
self.BgColor = BgColor
self.FgColor = FgColor
def Display(self):
frame = Frame(self.Name, width=self.Width, height=self.Height, colormap="new", bg=self.BgColor)
frame.place(x=self.x, y=self.y)
frame2 = Frame(self.Name, width=self.KnobWidth, height=self.KnobHeight, colormap="new", bg=self.FgColor)
frame2.place(x=(self.x + self.Width/2 - self.KnobWidth/2), y=(self.Height - self.KnobHeight/2 + self.y))
def SetVal(self):
Main.update()
frame2.place(x=33, y=5)
window1 = Window(Main)
Slider1 = Slider(Main,100,60,200,15,15,35,0,80,"White","Green")
Slider1.Display()
Slider1.SetVal(3,30)
Main.mainloop()

How to change button colour back to original when clicked again

I'm trying to change the button colour to black when clicked then change it back to white when clicked again. I'm trying to make Game Of Life for a school project.
I tried if statements but it doesn't change back to white, maybe I missed something simple. Here is the code:
from tkinter import *
class GUI(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
master.title("Window") #Window title
self.pack()
master.geometry("1280x720") #Window size
self.button={}#Dictionary for buttons
self.create_button()
def create_button(self):
indexList =[i for i in range(1000)]
self._button = Button(self, bg='white')
print(self._button.cget('bg'))
xPos = 0
yPos = 0
for index in indexList:
if(yPos == 40):
xPos = xPos + 20
yPos = 0
if(xPos == 10):
yPos = 8
self._button = Button(self, height=2, width=4, command = lambda
i = index: self.changecolour(i))
self.button[index] = self._button
self._button.grid(row=xPos, column =yPos)
yPos = yPos + 1
def changecolour(self,index):
aList = []
for i in range(1000):
aList.append([i,0])
for i in aList:
if index == i[0]:
if 0 == i[1]:
self.button[index].configure(bg = 'black')
i[1] = 1
else:
self.button[index].configure(bg = 'white')
i[1] = 0
root = Tk()
game_gui = GUI(master=root)
game_gui.mainloop()
As you can see it changes the button colour to black and it should change it back to white when clicked again, but it seems to just ignore the if statement.
I think this is the problem:
aList is not a global list
aList is created in changecolour() as a local list each time the subroutine is run
this means that when you do i[1] = 1 or i[1] = 0 it only changes the local list- aList. When the subroutine is run again, a new aList is created as a new local list.
to solve the problem define aList in the main program and make it a global list:
from tkinter import *
class GUI(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
master.title("Window") #Window title
self.pack()
master.geometry("1280x720") #Window size
self.button={}#Dictionary for buttons
self.create_button()
def create_button(self):
indexList =[i for i in range(1000)]
self._button = Button(self, bg='white')
print(self._button.cget('bg'))
xPos = 0
yPos = 0
for index in indexList:
if(yPos == 40):
xPos = xPos + 20
yPos = 0
if(xPos == 10):
yPos = 8
self._button = Button(self, height=2, width=4, command = lambda
i = index: self.changecolour(i))
self.button[index] = self._button
self._button.grid(row=xPos, column =yPos)
yPos = yPos + 1
def changecolour(self,index):
#aList IS NO LONGER CREATED HERE
for i in range(1000):
aList.append([i,0])
for i in aList:
if index == i[0]:
if 0 == i[1]:
self.button[index].configure(bg = 'black')
i[1] = 1
else:
self.button[index].configure(bg = 'white')
i[1] = 0
global aList #MAKE IT A GLOBAL LIST
aList = [] #CREATE THE EMPTY aList LIST
root = Tk()
game_gui = GUI(master=root)
game_gui.mainloop()

Tkinter Canvas is empty

I took this code from the first example here http://zetcode.com/tkinter/drawing/ I refit it so that it would print a map in the same file. There are no inherent errors, and it goes through the loops and even hits all the if statements properly. But in the end the canvas/frame has nothing in it. Can anyone tell me why?
from tkinter import Tk, Canvas, Frame, BOTH, NW
class Example(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.parent = parent
self.initUI()
def initUI(self):
self.parent.title("Board")
self.pack(fill=BOTH, expand=1)
canvas = Canvas(self)
#The first four parameters are the x,y coordinates of the two bounding points.
#The top-left and the bottom-right.
color = ""
for x in range(10):
for y in range(10):
if type(landMass[x][y]) is Land:
color = "grey"
if type(landMass[x][y]) is Food:
color = "green"
if type(landMass[x][y]) is Water:
color = "blue"
if type(landMass[x][y]) is Shelter:
color = "black"
rec = canvas.create_rectangle(3 + 50 * y, 3 + 50 * x, 53 + 50 * y, 53 + 50 * x , fill=color)
text = canvas.create_text(3 + 50 * y, 3 + 50 * x, anchor=NW, fill="white", text=landMass[x][y].elevation)
def main():
root = Tk()
ex = Example(root)
root.geometry("500x500+500+500")
root.mainloop()
if __name__ == '__main__':
main()
Check the link, you have omitted the call to canvas.pack(fill=BOTH, expand=1) at the end of the function.
After here:
for x in range(10):
for y in range(10):
if type(landMass[x][y]) is Land:
color = "grey"
if type(landMass[x][y]) is Food:
color = "green"
if type(landMass[x][y]) is Water:
color = "blue"
if type(landMass[x][y]) is Shelter:
color = "black"
rec = canvas.create_rectangle(3 + 50 * y, 3 + 50 * x, 53 + 50 * y, 53 + 50 * x , fill=color)
text = canvas.create_text(3 + 50 * y, 3 + 50 * x, anchor=NW, fill="white", text=landMass[x][y].elevation)
You should have:
canvas.pack(fill=BOTH, expand=1)

Categories

Resources