How to change the coordinates of a shape (i.e. oval) in tkinter? - python

I am trying to make a program which when you press the S button it moves shape to the square below it on the grid. I have managed to get the shape to move the first time but after that it just keeps getting bigger.
Here is my code:
from tkinter import *
root = Tk()
global y
y = 0
x = 0
def down(event):
global y
global x
y = y+100
x = x+ 100
global pirate
canvas.delete(pirate)
pirate = canvas.create_oval((x,y), (100,100), fill = 'red')
print(y)
canvas = Canvas(root, width = 1000, height = 1000)
canvas.pack()
for a in range (10):
i = a*100
canvas.create_line((i,0), (i,1000))
for a in range (10):
i = a*100
canvas.create_line((0,i), (1000,i))
pirate = canvas.create_oval((x, y),(100, 100), fill = 'red')
root.bind('<Key - S>', down)
root.mainloop()

As ArtOfWarfare mentioned in comments, instead of creating new ovals everytime, create one and move that thing around.
def down(event):
canvas.move(pirate, 0, 100)
Above code is sufficient to move your oval one square down in your code.
Assuming you'll need to move oval other than just down, instead of binding only S to canvas, I think you should get all key events and do stuff depending on pressed char.
def keyPressed(event):
if event.char.lower() == 's': #move it down if it's S or s
canvas.move(pirate, 0, 100)
root.bind('<Key>', keyPressed) #get all key pressed events

You have a problem that in python-tk the oval is NOT specified create_oval(x,y,w,h), but create_oval(x1,y1,x2,y2). Hope that's helpful!

Related

tkinter - multiple variables at once

I have a tkinter program.
I want to get the effect where, after clicking on three places in the image, the program prints appropriate coordinates: initial (x1, y1), height (y2-y1) and width (x3-x1), so the first click - x and y, the second - height and the third - width.
At the moment I have a defined function "printcoords". When I click on the image, the function "bind" runs, which call "printcoords" function and the x and y coordinates are printed
#function to be called when mouse is clicked
def printcoords(event):
#outputting x and y coords to console
print (event.x, event.y)
#mouseclick event
canvas.bind("<Button 1>",printcoords)
The following code roughly does what you are looking for.
import tkinter as tk
coords = []
def printcoords(event):
global coords
#print(event.x,event.y)
coords.append((event.x,event.y))
if len(coords) >= 3:
print(coords)
print(f"Initial: {coords[0][0]},{coords[0][1]}")
print(f"Height: {coords[1][1]-coords[0][1]}")
print(f"Width: {coords[2][0]-coords[0][0]}")
coords = []
root = tk.Tk()
c = tk.Canvas(root,width=200,height=200,bg="lightyellow")
c.bind("<Button-1>",printcoords)
c.grid()
root.mainloop()
It stores the x/y coordinates in a list until the list contains 3 values, it will then print out the first set of coordinates followed by the height and width as per the algorithm in your question.
Using a global here isn't best practice but it works for such a simple example. In a more complex program, I'd store the coordinates in a class.
from itertools import cycle
steps = cycle([1,2,3])
class Point:
def __init__(self):
self.x = 0
self.y = 0
points = [Point() for _ in range(3)]
#function to be called when mouse is clicked
def printcoords(event):
step = next(steps)
p = points[step-1]
p.x = event.x
p.y = event.y
if step==3:
#outputting x and y coords to console
print(f"Initial: {points[0].x},{points[0].y}")
print(f"Height: {points[1].y-points[0].y}")
print(f"Width: {points[2].x-points[0].x}")
#mouseclick event
canvas.bind("<Button 1>",printcoords)

How to enlarge and reduce an already drawn lines, using certain buttons?

Suppose I have the following program made with Tkinter in Python, in which you can click to draw any shape.
import tkinter
canvas = tkinter.Canvas(width=500, height=500, bg='white')
canvas.pack()
line = ()
def draw():
canvas.delete('all')
if len(line) >= 4:
canvas.create_line(line, fill='red', width=2)
def newline(coordinates):
global line
line = line + (coordinates.x, coordinates.y)
draw()
canvas.bind('<Button-1>', newline)
I have tried this for smaller image, but it didn't work.
def reduced():
line_reduced = ()
for i in newline:
line_reduced += (i/2,)
canvas.delete('all')
canvas.create_line(line_reduced, fill='red', width=4, tags='foto1')
I would need to add it so that this shape can then be reduced or enlarged using two keys. The image would therefore remain the same (shape), it would only be reduced / enlarged.
I will be grateful for any advice.
If you are trying to scale a bunch of lines on a canvas you could use scale, but then everything new that you add to the canvas would also have to be scaled or it starts getting weird. What you likely really need is math. Specifically this equation N = Scale * (N - Pn) + Pn. Below is a simple drawing interface with the buttons you requested. Primarily it's just a bunch of storing points and creating lines. The part of interest to you is this line:
line[n] = scale * (point - center) + center.
Lines are stored as [x1, y1, x2, y2] in my example code (the beginning and end of a line). line would refer to such a list. line[n] refers to each x and y value in that list as n is incremented. That being said:
line[n] = scale * (point - center) + center is really saying to subtract the center from the point, multiply it by the scale number, and then add the center back in so the scaled point stays it's scaled distance from the center.
Let's run through this one time so you definitely understand.
Chalkboard:
x1 = 100
center = 200
scale = 2
formula: x1 = scale * (x1 - center) + center
1 : x1 = 2 * (100 - 200) + 200
2 : x1 = 2 * (-100) + 200
3 : x1 = -200 + 200
4 : x1 = 0
The old x1 was 100 away from the center
now x1 is 200 away from the center
x1 is scale times further from the center than it was before
Code:
import tkinter as tk
root = tk.Tk()
canvas = tk.Canvas(root, width=800, height=600, bg='white')
canvas.pack()
#for storing line canvas ids and line coordinates
line_ids, lines = [], []
#line properties ~ width and fill
global w, f
w, f = 2, 'red'
#draw a line
def draw(event):
if event.type is tk.EventType.ButtonPress:
#store line start coordinate
lines.append([event.x, event.y])
#create a dummy line
line_ids.append(canvas.create_line(*lines[-1], *lines[-1], fill=f, width=w))
elif event.type is tk.EventType.Motion:
#keep deleting and redrawing the current line til you release the mouse
canvas.delete(line_ids.pop())
line_ids.append(canvas.create_line(*lines[-1], event.x, event.y, fill=f, width=w))
elif event.type is tk.EventType.ButtonRelease:
#append the end coordinate to the last line
lines[-1] += [event.x, event.y]
#add mouse events to canvas for drawing functionality
for event in ['<B1-Motion>', '<Button-1>', '<ButtonRelease-1>']:
canvas.bind(event, draw)
#scale lines
def scale_lines(scale):
#remove all canvas references to lines
canvas.delete("all")
line_ids = []
#traverse every line
for i, line in enumerate(lines):
#traverse every point in THIS line
for n, point in enumerate(line):
#toggle between center x and center y depending if point is an x or y
center = canvas.winfo_width()/2 if not n%2 else canvas.winfo_height()/2
#scale this point
line[n] = scale * (point - center) + center
#create a new line with scaled points
line_ids.append(canvas.create_line(*line, fill=f, width=w))
#increase/decrease buttons
tk.Button(root, command=lambda: scale_lines(.5), text='decrease').pack(side='left')
tk.Button(root, command=lambda: scale_lines(2), text='increase').pack(side='left')
root.mainloop()

Unable to bind tkinter slider properly to functions

I am trying to make a naive city generator. I have been successful at making the blocks of specified heights.
Also, I have been able to write a script that changes the height of a block using the slider.
Problem
I am not able to do both things together. (I think I can solve this because the error is only about referencing before assignment)
Using a horizontal slider, I want to increase the number of buildings (visualize as increasing the number of bars on the x-axis by reducing the width). But this code just does not work, I mean, it is not even raising errors.
Code so far
from tkinter import *
import random
bars = 5
margin = 30
heights= [5, 2, 1, 8, 4]
blocks = []
length = 600
breadth = 360
def draw():
aligner = margin
width = (breadth - margin*2)/bars
for height in heights :
block = {"height" : height }
block['rect'] = canvas.create_rectangle(aligner,550-50*height,aligner+width,550, fill="green")
block['color'] = 'green'
blocks.append(block)
aligner += width
root.update()
def wid(e):
bars = int(e)
blocks = []
heights = []
for i in range(bars):
h = random.random()*10
heights.append(h)
draw()
def h_rand(e):
factor = e
heights = [i * factor for i in heights]
draw()
root = Tk()
width = 60
aligner = 30
slider_y = Scale(root, from_=1 , to=8, bg="blue",command = h_rand)
slider_y.pack(side=LEFT)
canvas = Canvas(root,height=length,width=breadth)
canvas.pack()
for height in heights :
block = {"height" : height }
block['rect'] = canvas.create_rectangle(aligner,550-50*height,aligner+width,550, fill="green")
block['color'] = 'green'
blocks.append(block)
aligner += width
slider_x = Scale(root, from_=50 , to=200, orient = HORIZONTAL, bg="blue",command = wid)
slider_x.pack(side=TOP)
root.mainloop()
I am sorry for the "not so descriptive formatting" of the question (happy to give further details in the comments). It is bothering me for a while now and I am just frustrated.
The problem is that heights in wid is a local variable. You're changing the value inside the function, but the global variable is left unchanged.
You either need to declare heights as global inside the function, or use the global variable without replacing it with a new list.
To declear it as global, add global heights at the top of the function where it's being modified:
def wid(e):
global heights, blocks
...
You can use the clear method of a list to remove the contents without resetting the variable itself. To do that, replace heights = [] with heights.clear() and the same for blocks:
def wid(e):
bars = int(e)
print("bars:", bars)
blocks.clear()
heights.clear()
...
You also have the problem that you're not deleting the old canvas items before creating the new. Your draw function needs to destroy the old elements. You can do that with canvas.delete("all").

Cutting the codes in the program and making it neater

I'm having a hard time cutting the code and making it into a loop so that it would make the code of the program, neater.
Although my code works as it suppose to be, I think there is a right way of creating it, adding a for loop rather than writing all of these codes, I know there is an easy way to do this, I just couldn't figure how to do it properly. I know I'm suppose to create a for loop.
squares
from graphics import *
def main():
win = GraphWin("Squares", 500, 500)
rect = Rectangle(Point(0,500), Point(500,0))
rect.setFill("Red")
rect.draw(win)
rect2 = Rectangle(Point(20,480), Point(480,20))
rect2.setFill("white")
rect2.draw(win)
rect3 = Rectangle(Point(40,460), Point(460,40))
rect3.setFill("red")
rect3.draw(win)
rect4 = Rectangle(Point(60,440), Point(440,60))
rect4.setFill("white")
rect4.draw(win)
rect5 = Rectangle(Point(80,420), Point(420,80))
rect5.setFill("red")
rect5.draw(win)
rect6 = Rectangle(Point(100,400), Point(400,100))
rect6.setFill("white")
rect6.draw(win)
rect7 = Rectangle(Point(120,380), Point(380,120))
rect7.setFill("red")
rect7.draw(win)
rect8 = Rectangle(Point(140,360), Point(360,140))
rect8.setFill("white")
rect8.draw(win)
rect9 = Rectangle(Point(160,340), Point(340,160))
rect9.setFill("red")
rect9.draw(win)
rect10 = Rectangle(Point(180,320), Point(320,180))
rect10.setFill("white")
rect10.draw(win)
rect11 = Rectangle(Point(200,300), Point(300,200))
rect11.setFill("red")
rect11.draw(win)
rect12 = Rectangle(Point(220,280), Point(280,220))
rect12.setFill("white")
rect12.draw(win)
The results shows squares into some sort of a patchwork
Try the following:
from graphics import *
def main():
win = GraphWin("Squares", 500, 500)
# create all rects
rects = [Rectangle(Point(0 + 20*i,500 - 20*i), Point(500 - 20*i, 0 + 20*i)) for i in range(12)]
# draw all rects
for idx, rect in enumerate(rects):
rect.fill("red" if idx % 2 == 0 else "white")
rect.draw(win)
If the patchwork is just a background and you don't plan on modifying it you could use this:
from graphics import *
def main():
win = GraphWin("Squares", 500, 500)
i = 1
for x in range(0, 221, 20):
rect = Rectangle(Point(x, 500 - x), Point(500 - x,x))
rect.setFill("red" if i % 2 else "white")
rect.draw(win)
i += 1
An alternate approach that only needs to draw half as many rectangles due to using the rectangle's outline as the other color:
SQUARE, WIDTH = 500, 20
def main():
win = GraphWin("Squares", SQUARE, SQUARE)
save_config = dict(DEFAULT_CONFIG)
DEFAULT_CONFIG.update(dict(outline='red', fill='white', width=WIDTH))
for xy in range(WIDTH//2, SQUARE//2, WIDTH*2):
Rectangle(Point(xy, SQUARE - xy), Point(SQUARE - xy, xy)).draw(win)
DEFAULT_CONFIG.update(save_config)
It's fully parameterized so you can fit it to a different size square or have different width stripes by adjusting the SQUARE and WIDTH parameters. Rather than draw 12 rectangles in alternating colors, with the parameters as currently set, it draws 6 white rectangles with red outlines:

Creating a Button (from image) in Zelle's Graphics

I have a start button image that I am trying to turn into a button in my program. However, I believe I am doing the math wrong or something wrong obviously because it's not working. Basically, what I am trying to do is if the person clicks on the button, it will initiate an if statement. Any ideas? Thanks in advance!
#Assigning Mouse x,y Values
mousePt = win.getMouse()
xValue = startImage.getHeight()
yValue = startImage.getWidth()
#Assigning Buttons
if mousePt <= xValue and mousePt <= yValue:
hour = 2
startImage is the image I want to make a button. hour is a variable stated in other code.
You're comparing apples to oranges. This line:
if mousePt <= xValue and mousePt <= yValue:
is roughly the same as saying:
if Point(123, 45) <= 64 and Point(123, 45) <= 64:
It makes no sense to compare Points to widths and heights. You need to combine the widths and heights with the center position of the image and extract the X & Y values from the mouse position:
from graphics import *
win = GraphWin("Image Button", 400, 400)
imageCenter = Point(200, 200)
# 64 x 64 GIF image from http://www.iconsdb.com/icon-sets/web-2-green-icons/video-play-icon.html
startImage = Image(imageCenter, "video-play-64.gif")
startImage.draw(win)
imageWidth = startImage.getWidth()
imageHeight = startImage.getHeight()
imageLeft, imageRight = imageCenter.getX() - imageWidth/2, imageCenter.getX() + imageWidth/2
imageBottom, imageTop = imageCenter.getY() - imageHeight/2, imageCenter.getY() + imageHeight/2
start = False
while not start:
# Obtain mouse Point(x, y) value
mousePt = win.getMouse()
# Test if x,y is inside image
x, y = mousePt.getX(), mousePt.getY()
if imageLeft < x < imageRight and imageBottom < y < imageTop:
print("Bullseye!")
break
win.close()
This particular icon shows up as a circle, the area you can click includes its rectangular bounding box, some of which is outside the circle. It's possible to limit the clicks to exactly the visible image but that takes more work.

Categories