Namespaces and Accessing Tkinter - python

I have a question, though I'm not sure what language to use. I'm a little confused about how to access the canvas defined in the main driver module from other modules without using globals. For instance, I have instantiated the canvas in driver.py, but am trying to draw from Level.py. For context, it is eventually going to read a text file and draw a map based on the data it extracts from it for an RPG-ish game. Here is my code:
Driver.py:
import tkinter
import Level
HEIGHT = 1024
WIDTH = 800
TILE_SIZE = 32
VERTICAL_TILES = HEIGHT//TILE_SIZE
HORIZONTAL_TILES = WIDTH//TILE_SIZE
root = tkinter.Tk()
root.title("RPG Land")
window = tkinter.Canvas(root,width= WIDTH, height=HEIGHT )
lev = Level.LevelMgr()
lev.loadLevel()
lev.drawLevel()
window.pack()
root.mainloop()
Annnd Level.py:
import tkinter
from driver import window
class LevelMgr:
def __init__(self):
self.levelData = []
self.visibleLevel = []
self.tileColors = {
0 : 'empty',
1 : 'wall',
2 : 'bush' }
def loadLevel(self):
fyle = open('levels/level1','r')
count = 0
for lyne in fyle:
self.levelData.append(lyne)
count += 1
def drawLevel(self):
currentY = 0
currentX = 0
for col in self.levelData:
currentY += 32
for t in col:
window.create_rectangle(currentX, currentY, 32, 32, fill="blue")
currentX += 32
Any advice on how to structure the program better would be also appreciated. When accessing other namespaces, what is the proper way to do so? Do I need to have "import Level" on driver.py, as well as "import driver" in Level.py? I'm a little confused as to the fundamental structure of such a program.

LevelMgr depends on window, so name it explicitly in __init__:
class LevelMgr:
def __init__(self, window):
self.window = window
def drawLevel(self):
...
for t in col:
self.window.create_rectangle(currentX, currentY, 32, 32, fill="blue")
Remove the import statement:
from driver import window
Then, in Driver.py:
lev = Level.LevelMgr(window)
Another possibility is to simply define window in Level.py instead of Driver.py.

Related

Passing a tkinter canvas between classes without calling the child from within the parent

I am somewhat of a beginner when it comes to Python, but i decided i want to write a basic 2-d physics playground. Unfortionetly i ran straigt into trouble when trying to setup the basic structure.
My plan is to create a GUI with a canvas in a parent function named mainWindow, then i figured i would create a child class (Hero) which creates a circle the user can manipulate on the canvas. This seems to work fairly well.
The problem occurs when i try to do anything with the Hero class, like call a function to delete the circle so i can redraw it in some direction. I can't seem to pass the canvas from the mainWindow to the Hero class. Any help would be greatly appreciated, including telling me that this is the wrong way to do things.
Im adding the two documents im working with since my rambling is probably hard to follow.
I run the program from the phesics.py document, resulting in the GUI poping up with my canvas and a red circle. When i close the window i get the following error:
classes.py", line 29, in moveHeroBody
canvas.delete(heroBody)
NameError: name 'canvas' is not defined
Unfortionetly i dont know how to get the "world" into the child
classes.py
from tkinter import *
class mainWindow():
def __init__(self):
#Setup the GUI
root = Tk()
root.geometry('800x600')
# Setup the canvas within the GUI (master)
world = Canvas(root, height = 600, width = 800, bg = "#FFFFFF")
world.place(relx = 0.5, rely = 0.5, anchor = CENTER)
Hero(world)
root.mainloop()
class Hero(mainWindow):
def __init__(self,world):
#Initial creation of hero at coordinates
x1 = 10
y1 = 10
x2 = 70
y2 = 70
heroBody = world.create_oval(x1,y1,x2,y2, fill = "#FF0000", outline = "#FF0000")
#Move the hero
def moveHeroBody():
print("moveHeroBody")
world.delete(heroBody)
phesics.py
from tkinter import *
from classes import *
mainWindow1 = mainWindow()
moveHero = Hero.moveHeroBody()
You're passing it ok, but you're throwing the value away. Also, Hero shouldn’t inherit from mainWindow.
You need to save world as an attribute so that you can reference it later.
class Hero():
def __init__(self,world):
self.world = world
...
Then, you can use self.world to reference the canvas:
def moveHeroBody():
print("moveHeroBody")
self.world.delete(heroBody)
Though, the above code will fail because heroBody is a variable local to the __init__ - you need to do the same with it:
class Hero():
def __init__(self,world):
self.world = world
...
self.heroBody = world.create_oval(...)
#Move the hero
def moveHeroBody():
print("moveHeroBody")
self.world.delete(self.heroBody)
I think you need to initialize the class Hero in your mainWindow class. The modifications needed to do in the code are:
classes.py
from tkinter import *
from time import sleep
class mainWindow():
def __init__(self):
#Setup the GUI
self.jump_gap = 25
root = Tk()
root.geometry('800x600')
# Setup the canvas within the GUI (master)
self.world = Canvas(root, height = 600, width = 800, bg = "#FFFFFF")
self.world.place(relx = 0.5, rely = 0.5, anchor = CENTER)
self.hero = Hero(self.world)
self.world.pack()
root.bind("<space>",self.jump) # -> [1] Binds the SPACE BAR Key to the function jump
root.mainloop()
def jump(self,event):
gaps = list(range(self.jump_gap))
for i in gaps:
self.world.after(1,self.hero.moveHeroJump(h=i)) # [2] -> Binds the moveHeroJump method with the window action to a queue of updates
self.world.update() #[2] updates the canvas
sleep(0.01*i) # Added some linear wait time to add some look to it
gaps.reverse()
for i in gaps:
self.world.after(1,self.hero.moveHeroJump(h=-i))
self.world.update()
sleep(0.01*i)
class Hero():
def __init__(self,world):
#Initial creation of hero at coordinates
self.world = world
self.x1 = 10
self.y1 = 410
self.x2 = 70
self.y2 = 470
self.heroBody = self.world.create_oval(self.x1,self.y1,self.x2,self.y2, fill = "#FF0000", outline = "#FF0000")
#Move the hero
def moveHeroJump(self,h):
print("moveHeroBody")
self.y1 -= h
self.y2 -= h
self.world.delete(self.heroBody)
self.heroBody = self.world.create_oval(self.x1,self.y1,self.x2,self.y2, fill = "#FF0000", outline = "#FF0000")
physics.py
from tkinter import *
from classes import *
mainWindow1 = mainWindow()
Edit
So this got me playing some minutes ago, and I researched some sources from stack in order to complete this question. Here are the sources (referenced in the code as well):
How to bind spacebar key to a certain method in tkinter python
Moving Tkinter Canvas
The solution edited above is capable to perform a simple animation of a ball jumping. self.jump_gap is a fixed quantity that tells the ball how much does it needs to jump. The jump parses a certain height h to the moveHeroJump method to make the ball change its position, after the change of position is queued into the Canvas an update is called to see the changes on the ball.

Variables in custom python module

I am making my own custom python module called zoro, and I want to enable people making a variable, and the variable is equal to a function in my module, but how do I make that?
I've already tried looking in to the code of other modules like turtle, and turtle used the self argument, so I tried to use that, but it said TypeError: win() missing 1 required positional argument: 'self'.
Code of my program to test the module:
import zoro
test = zoro.win("test","black",500,500)
test.zoro.winTitle("test2")
Code of my module:
from tkinter import *
def win(title,bg,w,h):
root = Tk()
root.title(title)
root.config(bg=bg)
root.geometry(str(w) + "x" + str(h))
return root
def winTitle(title):
root.title(title)
I want to do like:
test = zoro.win("test","black",500,500)
test.zoro.winTitle("test2")
Question:
What you want to do is called inheritance.
For example:
zoro.py
import tkinter as tk
class App(tk.Tk):
def __init__(self, title, bg, width, height):
super().__init__()
self.title(title)
self.geometry('{}x{}'format(width, height)
self.config(bg=bg)
Usage
import zoro
class MyApp(zoro.App):
def __init__(self):
super().__init__("test","black",500,500)
# Change title
self.title('my new title')
# Add further widgets
if __name__ == '__main__':
MyApp().mainloop()
Assuming you want your driver to work with the module as currently defined, you need a global variable named root for winTitle to use. In addition, the object returned by win doesn't have an attribute named zoro.
import zoro
zoro.root = zoro.win("test", "black", 500, 500)
zoro.winTitle("test2")
That said, your module should be fixed to avoid global variables in the first place.
from tkinter import *
def win(title, bg, w, h):
root = Tk()
root.title(title)
root.config(bg=bg)
root.geometry(str(w) + "x" + str(h))
return root
def winTitle(root, title):
root.title(title)
Then your driver will look like
import zoro
test = zoro.win("test", "black", 500, 500)
zoro.winTitle(test, "test2")

Tkinter canvas move leaves a pixel trail

I'm working on a game in a Tkinter canvas where points move around the screen. I place each point at a location with tkinter.Canvas.create_oval(...) and subsequently move the points with tkinter.Canvas.move(pointID,delta_x,delta_y).
My problem is that the points seem to leave a trail behind when they are moved. I made a simplified example that demonstrates my problem.
from tkinter import Canvas,mainloop,Tk
import numpy as np
import random
import traceback
import threading
import time
from queue import Queue
class Point:
def __init__(self,the_canvas,uID):
self.uID = uID
self.location = np.ones((2)) * 200
self.color = "#"+"".join([random.choice('0123456789ABCDEF') for j in range(6)])
self.the_canvas = the_canvas
self.the_canvas.create_oval(200,200,200,200,
fill=self.color,outline=self.color,width=6,
tags=('runner'+str(self.uID),'runner'))
def move(self):
delta = (np.random.random((2))-.5)*20
self.the_canvas.move('runner'+str(self.uID),delta[0],delta[1])
def queue_func():
while True:
time.sleep(.25)
try:
next_action = the_queue.get(False)
next_action()
except Exception as e:
if str(e) != "":
print(traceback.format_exc())
the_queue = Queue()
the_thread = threading.Thread(target=queue_func)
the_thread.daemon = True
the_thread.start()
window = Tk()
window.geometry('400x400')
the_canvas = Canvas(window,width=400,height=400,background='black')
the_canvas.grid(row=0,column=0)
points = {}
for i in range(100):
points[i] = Point(the_canvas,i)
def random_movement():
while True:
for point in points.values():
point.move()
the_queue.put(random_movement)
mainloop()
And the result is something like this:
I need to be able to move Points around cleanly, without leaving anything behind.
I tried changing the move() function so that each point is deleted according to its tag and redrawn at the new location, but that results in the same problem.
I have tried fill='' and also outline='' in the Canvas.oval config, but this does not help.
The behavior of these pixel trials seem erratic, like they will disappear over time, only leaving a limited amount of footprints behind.
I have tried removing the time.sleep(.2) from the movement loop, and that seems to make the problem a lot more pronounced.
I've found that the only way to cleanup these rogue colored pixels is to run canvas.delete("all"), so as of now, my only solution is to delete everything and redraw everything constantly. That doesn't seem like a great solution to me.
What's a good way to avoid these "pixel trails"? It really just seems like a bug to me, but maybe I'm making a blunder somewhere.
After some digging I found this post here: Python3 tkinter.Canvas.move() method makes artifacts on screen
The problem there was the borders of the oval. So what I did was remove the borders and make the oval slightly larger to compensate and it looks like that did the trick.
If you change this line:
self.the_canvas.create_oval(200, 200, 200, 200,
fill=self.color, outline=self.color, width=6,
tags=('runner' + str(self.uID), 'runner'))
To this:
self.the_canvas.create_oval(200,200,206,206,
fill=self.color,outline='', width=0,
tags=('runner'+str(self.uID),'runner'))
the problem should go away with or without threading.
If you would like to see what your code would look like without threading here is an example:
import tkinter as tk
import numpy as np
import random
class Point:
def __init__(self, the_canvas, uID):
self.uID = uID
self.location = np.ones((2)) * 200
self.color = "#"+"".join([random.choice('0123456789ABCDEF') for j in range(6)])
self.the_canvas = the_canvas
self.the_canvas.create_oval(200, 200, 206, 206,
fill=self.color, outline='', width=0,
tags=('runner'+str(self.uID), 'runner'))
def move(self):
delta = (np.random.random((2))-.5)*20
self.the_canvas.move('runner'+str(self.uID), delta[0], delta[1])
window = tk.Tk()
window.geometry('400x400')
the_canvas = tk.Canvas(window, width=400, height=400, background='black')
the_canvas.grid(row=0, column=0)
points = {}
for i in range(100):
points[i] = Point(the_canvas, i)
def random_movement():
for point in points.values():
point.move()
window.after(50, random_movement)
random_movement()
window.mainloop()
Results:
Weirdly enough the issue seems to be coming from
width=6 in create_oval()
getting rid of it seems to resolve the issue.
The way you've originally setup your create_oval() is making an oval of zero area (i.e. non-existent) and border width of 6... which is obviously a troubelsom combination.
So here's the new code, with some extra modfications:
from tkinter import Canvas,mainloop,Tk
import numpy as np
import random
import traceback
import time
class Point:
def __init__(self,uID):
self.uID = uID
self.location = np.ones((2)) * 200
self.color = "#" + "".join([random.choice('0123456789ABCDEF') for j in range(6)])
def move(self):
delta = (np.random.random((2)) - .5) * 20
self.location += delta
def draw(self,canv):
x0, y0 = self.location
canv.create_oval(
x0, y0, x0 + 2, y0 + 2,
fill=self.color, outline=self.color,
tags=('runner'+str(self.uID),'runner')
)
def repeater(window):
the_canvas.delete('all')
for i in points:
i.move()
i.draw(the_canvas)
window.after(25, repeater, window)
window = Tk()
window.geometry('400x400')
the_canvas = Canvas(window,width=400,height=400,background='black')
the_canvas.grid(row=0,column=0)
points = []
for i in range(100):
points.append(Point(i))
repeater(window)
window.mainloop()
Also, btw, deleting all elements on the canvas is the way to clear it. it's not wasteful since you're updating all elements on it anyway.

Dynamically Created Button Appears Over Another One - Python Tkinter

I have a problem. I dynamically created buttons using a class. Each button is stored in a list, so I can use them later by indexing them. I am having trouble placing/displaying the buttons, though. When I create one button, it shows up perfectly. When I create another one, for some reason it appears over the first one. Help to fix this would be appreciated. Thanks!
Here is the code:
import tkinter as tk
window = tk.Tk()
window.geometry('800x600')
placeX = 20
placeY = 20
bl = []
def direction(type_):
pass
class SideBar():
def __init__(self, name):
global window
global placeX
global placeY
global bl
self.name = name
self.placeX = placeX
self.placeY = placeY
self.bl = bl
self.bl.append(self.name)
print(self.bl)
def create_folder(self, index):
self.bl[index] = tk.Button(window, text = self.name, command = lambda: direction(self.name))
self.bl[index].config(height = 3, width = 6)
self.bl[index].place(x = self.placeX, y = self.placeY)
self.placeY += 100
Computer = SideBar('Computer')
Documents = SideBar('Documents')
Computer.create_folder(0)
Documents.create_folder(1)
window.mainloop()
I think the problem is somewhere in the create_folder function.
You probably meant to use a class variable as opposed to an instance attribute. A class variable holds the data shared among all instances of a class, in fact, it can have a value as long as there's a class definition. Whereas an instance attribute can have values specific to a singular instance of a class, typically in the format self.attribute.
The way you are trying to use self.placeY fits the typical use of class variable. Remove:
self.placeY = placeY
add:
class SideBar():
...
placeY = placeY #assign global placeY's value to Sidebar.placeY
...
finally, replace:
self.placeY += 100
with:
SideBar.placeY += 100
You are creating two different instances of a class. Both have their own local variable. Create one instance and use something like this:
import tkinter as tk
window = tk.Tk()
window.geometry('800x600')
placeX = 20
placeY = 20
bl = []
def direction(type_):
pass
class SideBar():
def __init__(self):
global window
global placeX
global placeY
global bl
self.name = []
self.placeX = placeX
self.placeY = placeY
self.bl = []
self.bl.append(self.name)
def create_folder(self, index, name):
self.name.append(name)
self.bl.append(tk.Button(window, text = self.name[-1], command = lambda: direction(self.name)))
self.bl[-1].config(height = 3, width = 6)
self.bl[-1].place(x = self.placeX, y = self.placeY)
self.placeY += 100
side_bar = SideBar()
#Documents = SideBar('Documents')
side_bar.create_folder(0, 'Computer')
side_bar.create_folder(1, 'Documents')
window.mainloop()

How to get a value from another file function? python

Does anyone can help here?
I have two files called game.py and settings.py, I just want to get one value from settings to use in game, but I dont know what I am doing wrong.
the value I want it is in the function bbbbb...
THIS IS MY SETTINGS
from tkinter import*
import game
class Application(Frame):
def __init__ (self, master):
Frame.__init__(self,master)
self.grid()
self.create_widgets()
def bbbbb(self):
self.xr = self.ball_numbers.get()
print("printing...", self.xr)
return self.xr
def create_widgets(self):
self.ball_numbers = IntVar()
Label(self,text = "Select how many balls you wish to play:").grid()
Radiobutton(self, text = "1 Ball", variable = self.ball_numbers, value = 1, command = self.bbbbb).grid ()
Radiobutton(self, text = "2 Balls", variable = self.ball_numbers, value = 2, command = self.bbbbb).grid ()
Radiobutton(self, text = "3 Balls", variable = self.ball_numbers, value = 3, command = self.bbbbb).grid ()
settings_window = Tk()
settings_window.title(" THE BOUNCER - Settings")
settings_window.geometry("600x600")
app = Application(settings_window)
settings_window.mainloop()
I need that value in the class handling_settings, in the function create_ball_numbers
AND THIS IS MY game.py
from livewires import games, color
from tkinter import*
import settings
import random
games.init(screen_width = 735, screen_height = 350, fps = 35)
class Bounce(games.Sprite):
def update(self):
if self.right > games.screen.width or self.left < 0:
self.dx = -self.dx
if self.top < 0:
self.dy = -self.dy
if self.bottom == 315 and self.overlapping_sprites:
self.dy = -self.dy
class Bar_moving(games.Sprite):
def update(self):
self.x = games.mouse.x
self.y = 315
class handling_settings():
self.yr = bbbbb()
print("printing number from settings ", self.yr)
def create_ball_numbers(self):
print("inside def", self.yr)
def main():
background = games.load_image("BG.jpg", transparent = False)
games.screen.background = background
call = handling_settings()
call.create_ball_numbers()
bar_small = games.load_image("bar_small.jpg", transparent = False)
the_bar_small = Bar_moving(image = bar_small, x = games.mouse.x)
games.screen.add(the_bar_small)
games.mouse.is_visible = False
games.screen.event_grab = True
games.screen.mainloop()
main()
I think I am not using on the right way IMPORT on top of the file.... keeping appearing this msg...
File "C:\Users\Bruno\Desktop\DIT\Object Oriented Programming\GAME - Assignment\game.py", line 3, in <module>
from settings import bbbbb
ImportError: cannot import name bbbbb
If I run both files individually... its ok... but when I try to get the value in bbbbb function in settings, I get stuck...
You have a circular import; settings imports game, which imports settings. At that time, neither module is fully done initializing (anything beyond the import lines has not yet run).
You don't actually use the game module in settings, so just remove the import game line from settings.py.
In game.py, you imported the settings name; bbbbb is an attribute on the Application class in that module. The line:
self.yr = bbbbb()
will never work here.
You should definitely not create a new Tk() root window in settings, however; you can only ever have one main loop in a Tk application. Make settings a dialog window triggered by the main application in game.
So to get the bbbbb() result, you need to instead spawn the settings dialog box, let the user interact with it, and then retrieve the ball_numbers setting when the user closes the dialog again.
First don't do any circular import, in settings.py there is no need to import game module.
As bbbbb is a function of class Application, therefore it cannot be called directly, to call it we need an object of the same class, which is already created as
app = Application(settings_window)
so, in module game.py, just use app object to call bbbbb()
self.yr = settings.app.bbbbb()

Categories