Tkinter Canvas is empty - python

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)

Related

animate text color and let other widgets waiting

I'm looking for a way to create a "custom animation" for various texts. In my case, everything is done in sequence. I'm looking for a solution where when I add a widget to my method, the widget is played first after the first widget is finished
from tkinter import *
class MainWindow(Tk):
def __init__(self):
super().__init__()
self._animate = TAnimation()
self.textLabel = Label(self, bg="black", text="FontAnimation Text1", font=("Microsoft YuHei", 30))
self.textLabel.pack(fill="both", expand="yes")
self.textLabel2 = Label(self, bg="black", text="FontAnimation Text2", font=("Microsoft YuHei", 30))
self.textLabel2.pack(fill="both", expand="yes")
self._animate.animateTextColor(self.textLabel, 0, 40, 50)
self._animate.animateTextColor(self.textLabel2, 0, 120, 100) ## want to make other widgets "waiting" maybe one sec maybe 60 secs
class TAnimation(Frame):
def __init__(self):
super().__init__()
self.r = 0
self.g = 0
self.b = 0
def animateTextColor(self, widget, start, wait, speed):
if start < wait:
widget.after(wait, lambda: self.animateTextColor(widget, start, wait, speed))
start += 5
print(start)
elif start == wait:
if self.b < 255:
widget.configure(fg=self.rgbColor((self.r, self.g, self.b)))
widget.after(speed, lambda : self.animateTextColor(widget,start, wait, speed))
self.r += 1
self.g += 1
self.b += 1
else:
self.r = 0
self.g = 0
self.b = 0
def rgbColor(self, rgb):
return "#%02x%02x%02x" % rgb
if __name__ == '__main__':
mw = MainWindow()
x1, y1 = mw.winfo_screenwidth() / 2, mw.winfo_screenheight() / 2
x2, y2 = mw.winfo_screenwidth() / 4, mw.winfo_screenheight() / 4
mw.geometry("%dx%d+%d+%d" % (x1, y1, x2, y2))
mw.mainloop()
The problem here is that "widget2" assumes the same animation, actually this animation should only start when "widget1" is finished
A simple way would be to add items to a queue(list) and once one animation is completed start another.
check the example below
from tkinter import *
class MainWindow(Tk):
def __init__(self):
super().__init__()
self._animate = TAnimation()
self.textLabel = Label(self, bg="black", text="FontAnimation Text1", font=("Microsoft YuHei", 30))
self.textLabel.pack(fill="both", expand="yes")
self.textLabel2 = Label(self, bg="black", text="FontAnimation Text2", font=("Microsoft YuHei", 30))
self.textLabel2.pack(fill="both", expand="yes")
self._animate.addToQueue(self.textLabel, 5, 20)
self._animate.addToQueue(self.textLabel2, 10, 5) ## want to make other widgets "waiting" maybe one sec maybe 60 secs
class TAnimation(Frame):
def __init__(self):
super().__init__()
self.thresh = 255
def animate(self, item, wait, rgb: list):
if any(x>=self.thresh for x in rgb):
return
rgb = [x+1 for x in rgb]
item.config(fg=self.rgbColor(rgb))
self.after(wait, self.animate, item, wait, rgb)
def addToQueue(self, widget, wait, start_after: int):
self.after(start_after, self.animate, widget, wait, [0, 0, 0])
def rgbColor(self, rgb):
return "#{0:02x}{1:02x}{2:02x}".format(*rgb)
if __name__ == '__main__':
mw = MainWindow()
x1, y1 = mw.winfo_screenwidth() / 2, mw.winfo_screenheight() / 2
x2, y2 = mw.winfo_screenwidth() / 4, mw.winfo_screenheight() / 4
mw.geometry("%dx%d+%d+%d" % (x1, y1, x2, y2))
mw.mainloop()

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()

Python Tkinter Canvas does not appear

Hello I got a problem with using the Tkinter package on python. I want to create a window containing two major widgets of which one is a canvas that will later show a grid with cells. When initializing the canvas I use "create_rectangle" to fill the canvas with the desired objects. Also when clicking on a cell (for testing reasons) the canvas should change its color in the area of the rectangle. However when initialy displaying the window at first no objects can be seen (the expected result would be only white colored rectangles) and only when a Click on the canvas is performed the area changes its color as desired.
While looking through the internet I tried several variations of the order of the pack()- and create_rectangle()-methods. This is the code:
from tkinter import *
from tkinter.ttk import *
import cells
GRID_WIDTH = 15
GRID_HEIGHT = 15
class Ui(Frame):
""" Class to represent the ui of conways game of life"""
def __init__(self, parent, grid):
self.parent = parent
self.grid = grid
Frame.__init__(self, parent)
self.__setup()
def __setup(self):
""" Method to setup the components of the ui """
self.parent.title("Conway's game of life")
self.pack()
#Setup a frame to hold control components
frame_cntrl = Frame(self)
frame_cntrl.pack(side = RIGHT, anchor="n")
self.__setup_cntrl_components(frame_cntrl)
#Setup a frame to hold the Grid
self.canvas = Canvas(self)
self.canvas.pack(side = LEFT)
self.__draw_grid()
self.canvas.bind("<Button-1>", self.__canvas_clicked)
def __setup_cntrl_components(self, parent):
""" Method to setup the control elements of the ui"""
#Setup a label for the generation
self.lb_generation = Label(parent, text="dummy")
self.lb_generation.pack(side = TOP)
#Setup a button for iteration
self.bt_iteration = Button(parent, text="Iterate")
self.bt_iteration.pack(side = TOP)
def __draw_cell(self, x, y):
""" Draws a cell on the canvas"""
width, height = self.canvas.winfo_width(), self.canvas.winfo_height()
color = "black" if self.grid.cell_alive(x, y) else "white"
x0 = width * x / self.grid.width + 1
x1 = width * (x + 1) / self.grid.width
y0 = height * y / self.grid.height + 1
y1 = height * (y + 1) / self.grid.height
self.canvas.create_rectangle(x0, y0, x1, y1, width=0, fill=color)
def __draw_grid(self):
""" Method to setup the grid itself"""
width, height = self.canvas.winfo_width(), self.canvas.winfo_height()
for i in range(0, self.grid.width):
for j in range(0, self.grid.height):
self.__draw_cell(i, j)
def __canvas_clicked(self, event):
""" Method for when the cell was clicked """
x, y, width, height = event.x, event.y, self.canvas.winfo_width(), self.canvas.winfo_height()
cell_x = int(x / width * self.grid.width)
cell_y = int(y / height * self.grid.height)
self.grid.switch_cell(cell_x, cell_y)
self.__draw_cell(cell_x, cell_y)
if __name__ == "__main__":
Ui(Tk(), cells.Grid(GRID_WIDTH, GRID_HEIGHT)).mainloop()
Problem 1:
Your main problem is that, before the canvas is actually displayed, canvas.winfo_width() and canvas.winfo_height() do not return the canvas width and height, but the value 1.
I suggest you create the canvas as follows:
# Define canvas and save dimensions
self.canvas_width = 300
self.canvas_height = 200
self.canvas = Canvas(self, width = self.canvas_width,
height = self.canvas_height)
Then, in your code, replace each instance of:
width, height = self.canvas.winfo_width(), self.canvas.winfo_height()
with
width, height = self.canvas_width, self.canvas_height
Problem 2:
When creating each cell I don't think you need to add 1 to x0 and y0. Instead, it should be:
x0 = width * x / self.grid.width
x1 = width * (x + 1) / self.grid.width
y0 = height * y / self.grid.height
y1 = height * (y + 1) / self.grid.height

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

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()

Transparent colors Tkinter

Is it possible to change the color of a frame background or any other widget to transparent light blue or any other transparent color?
Yes there is a way. Unfortunately it only works for the entire window (The window and all of the child widgets).
Here is a little demo I wrote up a while ago which does what you want, among other things.
Transparent Window Demo :
import Tkinter as Tk, re
class TransparentWin (Tk.Tk) :
''' Transparent Tk Window Class '''
def __init__ (self) :
Tk.Tk.__init__(self)
self.Drag = Drag(self)
''' Sets focus to the window. '''
self.focus_force()
''' Removes the native window boarder. '''
self.overrideredirect(True)
''' Disables resizing of the widget. '''
self.resizable(False, False)
''' Places window above all other windows in the window stack. '''
self.wm_attributes("-topmost", True)
''' This changes the alpha value (How transparent the window should
be). It ranges from 0.0 (completely transparent) to 1.0
(completely opaque). '''
self.attributes("-alpha", 0.7)
''' The windows overall position on the screen '''
self.wm_geometry('+' + str(439) + '+' + str(172))
''' Changes the window's color. '''
bg = '#3e4134'
self.config(bg=bg)
self.Frame = Tk.Frame(self, bg=bg)
self.Frame.pack()
''' Exits the application when the window is right clicked. '''
self.Frame.bind('<Button-3>', self.exit)
''' Changes the window's size indirectly. '''
self.Frame.configure(width=162, height=100)
def exit (self, event) :
self.destroy()
def position (self) :
_filter = re.compile(r"(\d+)?x?(\d+)?([+-])(\d+)([+-])(\d+)")
pos = self.winfo_geometry()
filtered = _filter.search(pos)
self.X = int(filtered.group(4))
self.Y = int(filtered.group(6))
return self.X, self.Y
class Drag:
''' Makes a window dragable. '''
def __init__ (self, par, dissable=None, releasecmd=None) :
self.Par = par
self.Dissable = dissable
self.ReleaseCMD = releasecmd
self.Par.bind('<Button-1>', self.relative_position)
self.Par.bind('<ButtonRelease-1>', self.drag_unbind)
def relative_position (self, event) :
cx, cy = self.Par.winfo_pointerxy()
x, y = self.Par.position()
self.OriX = x
self.OriY = y
self.RelX = cx - x
self.RelY = cy - y
self.Par.bind('<Motion>', self.drag_wid)
def drag_wid (self, event) :
cx, cy = self.Par.winfo_pointerxy()
d = self.Dissable
if d == 'x' :
x = self.OriX
y = cy - self.RelY
elif d == 'y' :
x = cx - self.RelX
y = self.OriY
else:
x = cx - self.RelX
y = cy - self.RelY
if x < 0 :
x = 0
if y < 0 :
y = 0
self.Par.wm_geometry('+' + str(x) + '+' + str(y))
def drag_unbind (self, event) :
self.Par.unbind('<Motion>')
if self.ReleaseCMD != None :
self.ReleaseCMD()
def dissable (self) :
self.Par.unbind('<Button-1>')
self.Par.unbind('<ButtonRelease-1>')
self.Par.unbind('<Motion>')
def __run__ () :
TransparentWin().mainloop()
if __name__ == '__main__' :
__run__()
For what it's worth, here's an arguably more readable version of the code in #rectangletangle's answer that has been re-formatted to more closely adhere to PEP 8 - Style Guide for Python Code recommendations.
Except for a trivial modification to the way the Tkinter module is imported so it will work in both Python 2 and 3, all other executable code is identical to his.
try:
import tkinter as tk
except ImportError:
import Tkinter as tk
import re
class TransparentWin(tk.Tk):
""" Transparent Tkinter Window Class. """
def __init__(self):
tk.Tk.__init__(self)
self.Drag = Drag(self)
# Sets focus to the window.
self.focus_force()
# Removes the native window boarder.
self.overrideredirect(True)
# Disables resizing of the widget.
self.resizable(False, False)
# Places window above all other windows in the window stack.
self.wm_attributes("-topmost", True)
# This changes the alpha value (How transparent the window should be).
# It ranges from 0.0 (completely transparent) to 1.0 (completely opaque).
self.attributes("-alpha", 0.7)
# The windows overall position on the screen
self.wm_geometry('+' + str(439) + '+' + str(172))
# Changes the window's color.
bg = '#3e4134'
self.config(bg=bg)
self.Frame = tk.Frame(self, bg=bg)
self.Frame.pack()
# Exits the application when the window is right clicked.
self.Frame.bind('<Button-3>', self.exit)
# Changes the window's size indirectly.
self.Frame.configure(width=162, height=100)
def exit(self, event):
self.destroy()
def position(self):
_filter = re.compile(r"(\d+)?x?(\d+)?([+-])(\d+)([+-])(\d+)")
pos = self.winfo_geometry()
filtered = _filter.search(pos)
self.X = int(filtered.group(4))
self.Y = int(filtered.group(6))
return self.X, self.Y
class Drag:
""" Makes a window draggable. """
def __init__(self, par, dissable=None, releasecmd=None):
self.Par = par
self.Dissable = dissable
self.ReleaseCMD = releasecmd
self.Par.bind('<Button-1>', self.relative_position)
self.Par.bind('<ButtonRelease-1>', self.drag_unbind)
def relative_position(self, event):
cx, cy = self.Par.winfo_pointerxy()
x, y = self.Par.position()
self.OriX = x
self.OriY = y
self.RelX = cx - x
self.RelY = cy - y
self.Par.bind('<Motion>', self.drag_wid)
def drag_wid(self, event):
cx, cy = self.Par.winfo_pointerxy()
d = self.Dissable
if d == 'x':
x = self.OriX
y = cy - self.RelY
elif d == 'y':
x = cx - self.RelX
y = self.OriY
else:
x = cx - self.RelX
y = cy - self.RelY
if x < 0:
x = 0
if y < 0:
y = 0
self.Par.wm_geometry('+' + str(x) + '+' + str(y))
def drag_unbind(self, event):
self.Par.unbind('<Motion>')
if self.ReleaseCMD != None:
self.ReleaseCMD()
def dissable(self):
self.Par.unbind('<Button-1>')
self.Par.unbind('<ButtonRelease-1>')
self.Par.unbind('<Motion>')
def __run__():
TransparentWin().mainloop()
if __name__ == '__main__':
__run__()

Categories

Resources