I am trying to change dashed lines into filled lines when you click on them. Instead, I get this error when clicking on a line:
Traceback (most recent call last):
File "/Users/dan/Documents/pyCatan/path_engine.py", line 106, in <module>
root.mainloop()
File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-tk/Tkinter.py", line 1017, in mainloop
self.tk.mainloop(n)
File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-tk/Tkinter.py", line 1414, in __call__
self.widget._report_exception()
File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/lib-tk/Tkinter.py", line 1175, in _report_exception
root = self._root()
AttributeError: Frame instance has no __call__ method
Common causes in similar questions were polluting variables with multiple uses, but I am not doing that here. Also, I have declared the method that is being called, which was the error in another, similar question.
Here is my noobie code:
from map_gen import MapGen
from gen_board import CatanApp
from Tkinter import *
class PathEngine(object):
'''Use the configuration options to show or hide certain attributes.'''
# show the edges explicitly
SHOW_EDGES = False
# show road intersections as nodes, explicitly
SHOW_NODES = True
# color the hexes to their resource color
COLOR_HEXES = False
CLICK_ADD_EDGES = True
# dimensions
HEIGHT = 600
WIDTH = 800
def __init__(self, root):
self._model = MapGen()
self._model.gen()
CatanApp.set_vertices(self._model.get_map())
self._model.prepare()
frame = Frame(root, height=PathEngine.HEIGHT, width=PathEngine.WIDTH)
frame.pack()
self._canvas = MapDrawer(frame)
self.render()
self._canvas.config(height=PathEngine.HEIGHT, width=PathEngine.WIDTH)
self._canvas.pack()
def render(self):
if PathEngine.SHOW_NODES:
for node in self._model.get_nodes():
self._canvas.draw_node(*node)
self.add_edges()
def add_edges(self):
for edge in self._model.get_roads():
if PathEngine.CLICK_ADD_EDGES:
self._canvas.draw_dashed_edge(edge[0][0], edge[0][1], edge[1][0], edge[1][1])
class MapDrawer(Canvas):
NODE_RADIUS = 20
def __init__(self, master):
Canvas.__init__(self, master)
self._root = master
def draw_dashed_edge(self, x1, y1, x2, y2, color=None):
if color is None:color = "black"
t = "road_%s_%s" % (str((x1, y1)), str((x2, y2)))
self.create_line(
x1,
y1,
x2,
y2,
fill=color,
dash=(1, 1),
width=3,
tags=("road", t)
)
f = lambda event: self.solidify_dashed_edge(t)
self.tag_bind(t, "<Button-1>", f)
def solidify_dashed_edge(self, tag):
self.itemconfigure(tag, dash=(0, 1))
def draw_node(self, x, y, color=None):
if color is None: color = "white"
self.create_oval(
x - MapDrawer.NODE_RADIUS / 2,
y - MapDrawer.NODE_RADIUS / 2,
x + MapDrawer.NODE_RADIUS / 2,
y + MapDrawer.NODE_RADIUS / 2,
fill=color,
outline="black"
)
if __name__ == "__main__":
root = Tk()
engine = PathEngine(root)
root.mainloop()
It looks like you've hit a name conflict with adding an attribute _root to your Canvas:
>>> import Tkinter as tk
>>> a = tk.Canvas()
>>> print a._root
<bound method Canvas._root of <Tkinter.Canvas instance at 0xee1c0>>
It's one of the hazards of working in python where there is no private data :-). Notice that _root is a method of Canvas objects. You override that method with an instance attribute:
class MapDrawer(Canvas):
NODE_RADIUS = 20
def __init__(self, master):
Canvas.__init__(self, master)
self._root = master
Where master is a Frame object.
Related
I want to create a class of Listbox object so that it can be used anywhere it is needed but it throws TypeError error, here is my code:
import tkinter as tk
class cls_list(tk.Listbox):
def __init__(self, master, callfrom):
tk.Listbox.__init__(self, master, callfrom)
callfrom.bind("<KeyRelease>", self.show_list)
def show_list(self, event):
x = callfrom.winfo_x()
y = callfrom.winfo_y()
h = callfrom.winfo_height()
self.place(x = x, y = y+h)
self.insert('end',*('Banana','Orange','Apple','Mango'))
if __name__ == '__main__':
root = tk.Tk()
ent = tk.Entry(root, font = (None, 20))
lst = cls_list(root, ent)
ent.pack()
root.mainloop()
Can someone correct me what I am doing wrong?
Here is complete Exception occurred:
File "/home/waheed/env_pinpoint/lib/python3.9/site-packages/test_listbox.py", line 5, in __init__
tk.Listbox.__init__(self, master, callfrom)
File "/usr/lib/python3.9/tkinter/__init__.py", line 3162, in __init__
Widget.__init__(self, master, 'listbox', cnf, kw)
File "/usr/lib/python3.9/tkinter/__init__.py", line 2566, in __init__
BaseWidget._setup(self, master, cnf)
File "/usr/lib/python3.9/tkinter/__init__.py", line 2537, in _setup
if 'name' in cnf:
File "/usr/lib/python3.9/tkinter/__init__.py", line 1652, in cget
return self.tk.call(self._w, 'cget', '-' + key)
TypeError: can only concatenate str (not "int") to str
This is what I have achieved so far.
import tkinter as tk
if __name__ == '__main__':
root = tk.Tk()
root.geometry('400x400')
ent = tk.Entry(root, font = (None, 20))
lst = tk.Listbox(root, font = (None, 20))
ent.pack()
ent.focus_set()
def hide_list(event):
index = int(lst.curselection()[0])
val = lst.get(index)
ent.delete(0,'end')
ent.insert(0,val)
lst.place_forget()
def show_list(event):
ent.update_idletasks()
x = ent.winfo_x()
y = ent.winfo_y()
h = ent.winfo_height()
lst.place(x = x, y = y+h)
items = ['Banana','Orange','Apple','Mango']
searchkey = ent.get()
lst.delete(0, "end")
for item in items:
if searchkey.lower() in item.lower():
lst.insert("end", item)
n = lst.size()
if n < 15:
lst.config(height = 0)
else:
lst.config(height = 15)
ent.bind("<KeyRelease>", show_list)
lst.bind("<<ListboxSelect>>", hide_list)
root.mainloop()
And now I want to make it an object (separate class) for using in multiple scripts.
I hope now I have explained my question properly.
The specific error you're asking about is caused because you're passing callfrom here: tk.Listbox.__init__(self, master, callfrom).
The listbox widget doesn't know what to do with callfrom, it expects the second positional argument to be a dictionary.
The proper way to initialize the class is without callfrom. You will need to save callfrom as an instance attribute if you need to use it outside of the __init__.
def __init__(self, master, callfrom):
tk.Listbox.__init__(self, master)
self.callfrom = callfrom
...
def show_list(self, event):
x = self.callfrom.winfo_x()
y = self.callfrom.winfo_y()
h = self.callfrom.winfo_height()
...
Trying to make a flappy bird clone and am having troubles with spawning in the object that will be the bird. I have tried to fix it by myself but I cannot think of why it isn't working. Tried to find if there was any fix online and I came up empty. I don't understand what the issue is and why it isn't spawning. I can't think of how to fix it. my code is here.
import tkinter
class Menu:
_BUTTON_HEIGHT = 2
_BUTTON_WIDTH = 10
_PLAY_BUTTON = "Start"
_EXIT_BUTTON = "Exit"
def __init__(self):
""""""
self.canvas = tkinter.Tk()
self.play_button = None
self.exit_button = None
self.starting_game_new = None
def canvas_create(self):
"""Creates a window for the frame to be displayed"""
self.canvas.geometry("500x700")
def menu_buttons(self):
""""""
self.play_button = tkinter.Button(self.canvas,
command=self.start_game,
text=Menu._PLAY_BUTTON,
width=Menu._BUTTON_WIDTH,
height=Menu._BUTTON_HEIGHT)
self.play_button.pack()
self.exit_button = tkinter.Button(self.canvas,
command=self.exit_game,
text=Menu._EXIT_BUTTON,
width=Menu._BUTTON_WIDTH,
height=Menu._BUTTON_HEIGHT)
self.exit_button.pack()
def start_game(self):
self.play_button.pack_forget()
self.exit_button.pack_forget()
self.starting_game_new = GameFrame(canvas=self.canvas)
self.starting_game_new.create_frame()
self.starting_game_new.create_bird()
def exit_game(self):
self.canvas.destroy()
class GameFrame:
_FRAME_HEIGHT = 700
_FRAME_WIDTH = 500
_FRAME_COLOUR = "dodger blue"
_X_BIRD = 200
_X_BIRD_2 = 250
_Y_BIRD = 150
_Y_BIRD_2 = 200
_BIRD_COLOUR = "gold2"
def __init__(self, canvas):
""""""
self.game_frame = None
self.canvas = canvas
self.bird = None
def create_frame(self):
"""Creates the game canvas and background"""
self.game_frame = tkinter.Canvas(self.canvas,
width=GameFrame._FRAME_WIDTH,
height=GameFrame._FRAME_HEIGHT,
background=GameFrame._FRAME_COLOUR)
self.game_frame.pack()
def create_bird(self):
self.bird = self.canvas.create_rectangle(GameFrame._X_BIRD,
GameFrame._Y_BIRD,
GameFrame._X_BIRD_2,
GameFrame._Y_BIRD_2,
fill=GameFrame._BIRD_COLOUR)
def main():
start = Menu()
start.canvas_create()
start.menu_buttons()
start.canvas.mainloop()
if __name__ == '__main__':
main()
Error code here:
Exception in Tkinter callback Traceback (most recent call last): File "(file)init.py", line 1883, in call return self.func(*args) File "", line 50, in start_game self.starting_game_new.create_bird() File "D:/Downloads/flappybirdgame-main/aewf.py", line 82, in create_bird self.bird = self.canvas.create_rectangle(GameFrame._X_BIRD, File "", line 2345, in getattr return getattr(self.tk, attr) AttributeError: '_tkinter.tkapp' object has no attribute 'create_rectangle'
The method create_rectangle is a method on your canvas. Your variable self.canvas is not a canvas, it's the root window.
You need to create a tkinter Canvas widget in order to draw a rectangle. It looks like you're doing so in this line:
self.game_frame = tkinter.Canvas(self.canvas, ...)
So, you need to call create_rectangle on this widget instead of self.canvas:
self.bird = self.game_frame.create_rectangle(...)
I'm new to python and can't get a test program functioning. The goal was to keep rotating an image until it reaches the desired angle using tkinter GUI. The program runs fine but after reaching to the desired angle the console shows the following exception.
Traceback (most recent call last):
File "/usr/lib/python3.8/tkinter/__init__.py", line 1883, in __call__
return self.func(*args)
File "/usr/lib/python3.8/tkinter/__init__.py", line 804, in callit
func(*args)
StopIteration
And the code is :
import tkinter as tk
from PIL import ImageTk
from PIL import Image
import time
class SimpleApp(object):
def __init__(self, master, filename, **kwargs):
self.master = master
self.filename = filename
self.canvas = tk.Canvas(master, width=500, height=500)
self.canvas.pack()
self.update = self.draw().__next__
master.after(100, self.update)
def draw(self):
image = Image.open(self.filename)
angle = 0
while (angle<90):
tkimage = ImageTk.PhotoImage(image.rotate(angle))
canvas_obj = self.canvas.create_image(
250, 250, image=tkimage)
self.master.after_idle(self.update)
yield
angle += 5
time.sleep(0.1)
root = tk.Tk()
app = SimpleApp(root, 'cat.jpg')
root.mainloop()
Thanks in advance.
This is happening because when you reach the desired angle, the code doesn't enter your while loop, meaning no yield statement is encountered. Thus when you call self.draw().__next__, there is no next element to retrieve.
If you want to keep the current code, simply adding an else statement with a yield afterwards would solve your error
def draw(self):
image = Image.open(self.filename)
angle = 0
while (angle<90):
tkimage = ImageTk.PhotoImage(image.rotate(angle))
canvas_obj = self.canvas.create_image(
250, 250, image=tkimage)
self.master.after_idle(self.update)
yield
angle += 5
time.sleep(0.1)
else:
yield
However I see no point in using the next and yield statements in the function you've made, and would probably have done something like the below instead
import tkinter as tk
from PIL import ImageTk
from PIL import Image
import time
class SimpleApp(object):
def __init__(self, master, filename, **kwargs):
self.master = master
self.filename = filename
self.canvas = tk.Canvas(master, width=500, height=500)
self.canvas.pack()
self.image = Image.open(self.filename)
self.angle = 0
self.master.after(100, self.draw)
def draw(self):
while self.angle < 90:
tkimage = ImageTk.PhotoImage(self.image.rotate(angle))
canvas_obj = self.canvas.create_image(
250, 250, image=tkimage)
self.angle += 5
self.master.after(100, self.draw)
root = tk.Tk()
app = SimpleApp(root, 'cat.jpg')
root.mainloop()
I am a total beginner here. I would like to know the coordinates of x1 while it is moving, so it will be keep updating.
Here is my code.
from tkinter import *
import tkinter as tk
import time
import random
class Example(tk.Frame):
def __init__(self,parent):
tk.Frame.__init__(self)`
#create a canvas
self.canvas = tk.Canvas(width=600, height=250)
self.canvas.pack()
self.road()
self.crossing()
def road(self):
Line1 = self.canvas.create_line(50, 50, 450, 50)
Line2 = self.canvas.create_line(50, 100, 450, 100)
def crossing(self):
CLine1 = self.canvas.create_line(350, 50, 350, 100)
CLine2 = self.canvas.create_line(375, 50, 375, 100)
class Car:
def __init__(self,x1,y1,x2,y2,vx,vy,color,Example):
self.x1 = x1
self.y1 = y1
self.x2 = x2
self.y2 = y2
self.vx = vx
self.vy = vy
self.color=color
self.Example = Example
def drawit(self):
self.Example.canvas.create_rectangle(x1,y1,x2,y2,color)
def moveit(self,vx,vy):
self.Example.canvas.move(vx,vy)
if __name__ == "__main__":
root = tk.Tk()
my_canvas = Example(root).pack(fill="both", expand=True)
mycar = Car(60, 60, 125, 90,3,0,"red",Example)
mycar.drawit()
mycar.moveit()
print (mycar.x1)
root.mainloop()
Here is the error message:
AttributeError: type object 'Example' has no attribute 'canvas'
Process finished with exit code 1
Any help would be much appreciated.
You have some basic misunderstandings of how classes and objects work. Instead of doing this:
my_canvas = Example(root)
my_canvas.pack(fill="both", expand=True)
mycar = Car(60, 60, 125, 90,3,0,"red",Example)
(note: you also need to call pack on a separate line from where the widget is created and assigned to a variable. See https://stackoverflow.com/a/1101765/7432)
You need to do this:
my_canvas = Example(root).pack(fill="both", expand=True)
mycar = Car(60, 60, 125, 90,3,0,"red", my_canvas)
You need to pass in the instance of Example (eg: my_canvas), not the class (eg: Example). Also, Car needs to use the example like this:
class Car:
def __init__(self,x1,y1,x2,y2,vx,vy,color,example):
...
self.example=example
def drawit(self):
self.example.canvas.create_rectangle(x1,y1,x2,y2,color)
def moveit(self,vx,vy):
self.example.canvas.move(vx,vy)
Your Car doesn't have access to the Example object. I would pass my Example to my Car on init, so that it can access its context. e.g.
class Car:
def __init__(self, x, y, example):
...
self.example=example
def do_stuff(self):
self.example.canvas.draw(whatever)
example = Example(args)
car = Car(3,4,example)
car.do_stuff()
In this example, Car objects have access to the canvas that you create in your Example class, so they can draw themselves, etc.
Please run the following example.
I have created a progress bar for my application, and by pressing the "Open" button a progress bar pops up. However, the progress bar does not fill up and it appears that the script is halt at
bar.set(i)
when function ProgressBarLoop is called.
from Tkinter import Tk, Frame, BOTH, Label, Toplevel, Canvas, Button
import thread
import time
class ProgressBar:
def __init__(self, parent, width, height):
master = Toplevel(parent)
master.protocol('WM_DELETE_WINDOW', self.hide )
self.master = master
self.master.overrideredirect(True)
ws = self.master.winfo_screenwidth()
hs = self.master.winfo_screenheight()
w = (True and ws*0.2) or 0.2
h = (True and ws*0.15) or 0.15
x = (ws/2) - (w/2)
y = (hs/2) - (h/2)
self.master.geometry('%dx%d+%d+%d' % (width, height * 2.5, x, y))
self.mode = 'percent'
self.ONOFF = 'on'
self.width = width
self.height = height
self.frame = None
self.canvas = None
self.progressBar = None
self.backgroundBar = None
self.progressformat = 'percent'
self.label = None
self.progress = 0
self.createWidget()
self.frame.pack()
self.set(0.0) # initialize to 0%
def createWidget(self):
self.frame = Frame(self.master, borderwidth = 1, relief = 'sunken')
self.canvas = Canvas(self.frame)
self.backgroundBar = self.canvas.create_rectangle(0, 0, self.width, self.height, fill = 'darkblue')
self.progressBar = self.canvas.create_rectangle(0, 0, self.width, self.height, fill = 'blue')
self.setWidth()
self.setHeight()
self.label = Label(self.frame, text = 'Loading...', width = 20)
self.label.pack(side = 'top') # where text label should be packed
self.canvas.pack()
def setWidth(self, width = None):
if width is not None:
self.width = width
self.canvas.configure(width = self.width)
self.canvas.coords(self.backgroundBar, 0, 0, self.width, self.height)
self.setBar() # update progress bar
def setHeight(self, height = None):
if height is not None:
self.height = height
self.canvas.configure(height = self.height)
self.canvas.coords(self.backgroundBar, 0, 0, self.width, self.height)
self.setBar() # update progress bar
def set(self, value):
if self.ONOFF == 'off': # no need to set and redraw if hidden
return
if self.mode == 'percent':
self.progress = value
self.setBar()
return
def setBar(self):
self.canvas.coords(self.progressBar,0, 0, self.width * self.progress/100.0, self.height)
self.canvas.update_idletasks()
def hide(self):
if isinstance(self.master, Toplevel):
self.master.withdraw()
else:
self.frame.forget()
self.ONOFF = 'off'
def configure(self, **kw):
mode = None
for key,value in kw.items():
if key=='mode':
mode = value
elif key=='progressformat':
self.progressformat = value
if mode:
self.mode = mode
def ProgressBarLoop(window, bar):
bar.configure(mode = 'percent', progressformat = 'ratio')
while(True):
if not window.loading:
break
for i in range(101):
bar.set(i)
print "refreshed bar"
time.sleep(0.001)
bar.hide()
class Application(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.pack(fill = BOTH, expand = True)
parent.geometry('%dx%d+%d+%d' % (100, 100, 0, 0))
Button(parent, text = "Open", command = self.onOpen).pack()
def onOpen(self, event = None):
self.loading = True
bar = ProgressBar(self, width=150, height=18)
thread.start_new_thread(ProgressBarLoop, (self, bar))
while(True):
pass
root = Tk()
Application(root)
root.mainloop()
EDIT:
After trying dano's answer, it works but I get the following error:
Exception in thread Thread-1:
Traceback (most recent call last):
File "/mnt/sdev/tools/lib/python2.7/threading.py", line 551, in __bootstrap_inner
self.run()
File "/mnt/sdev/tools/lib/python2.7/threading.py", line 504, in run
self.__target(*self.__args, **self.__kwargs)
File "/home/jun/eclipse/connScript/src/root/nested/test.py", line 88, in ProgressBarLoop
bar.set(i)
File "/home/jun/eclipse/connScript/src/root/nested/test.py", line 61, in set
self.setBar()
File "/home/jun/eclipse/connScript/src/root/nested/test.py", line 64, in setBar
self.canvas.coords(self.progressBar,0, 0, self.width * self.progress/100.0, self.height)
File "/mnt/sdev/tools/lib/python2.7/lib-tk/Tkinter.py", line 2178, in coords
self.tk.call((self._w, 'coords') + args)))
RuntimeError: main thread is not in main loop
The problem is that you're running an infinite loop immediately after you start the thread:
def onOpen(self, event = None):
self.loading = True
bar = ProgressBar(self, width=150, height=18)
thread.start_new_thread(ProgressBarLoop, (self, bar))
while(True): # Infinite loop
pass
Because of this, control never returns to the Tk event loop, so the progress bar can never be updated. The code should work fine if you remove the loop.
Also, you should use the threading module instead of the thread module, as suggested in the Python docs:
Note The thread module has been renamed to _thread in Python 3. The
2to3 tool will automatically adapt imports when converting your
sources to Python 3; however, you should consider using the high-level
threading module instead.
Putting it altogether, here's what onOpen should look like:
def onOpen(self, event = None):
self.loading = True
bar = ProgressBar(self, width=150, height=18)
t = threading.Thread(target=ProgressBarLoop, args=(self, bar))
t.start()
Edit:
Trying to update tkinter widgets from multiple threads is somewhat finicky. When I tried this code on three different systems, I ended up getting three different results. Avoiding loops in the threaded method helped avoid any errors:
def ProgressBarLoop(window, bar, i=0):
bar.configure(mode = 'percent', progressformat = 'ratio')
if not window.loading:
bar.hide()
return
bar.set(i)
print "refreshed bar"
i+=1
if i == 101:
# Reset the counter
i = 0
window.root.after(1, ProgressBarLoop, window, bar, i)
But really, if we're not using an infinite loop in ProgressBarLoop anyway, there's really no need to use a separate thread at all. This version of ProgressBarLoop can be called in the main thread, and the GUI won't be blocked for any noticeable amount of time.