Getting current value when dragging canvas - python

I am trying to make a simple card game, something like Solitaire.
I am not experienced in coding, so forgive me if it's a simple question.
I want to move some canvas objects. New objects have the right value, but when i am dragging an already existing card it shows the wrong value (waarde in Dutch). I would like to bind the value (waarde) to a card, but don't know how to do that...
Thought about tags, binding, ID....
from tkinter import *
from random import randint
window = Tk()
deck = [1,2,3,4,5,6]
def pakkaart():
rand_card = randint(0,len(deck)-1)
global waarde
waarde = deck[rand_card]
deck.pop(rand_card)
global kaart
kaart = Canvas(window, width = 40, height = 40, bg='yellow')
kaart.place(x=50, y=50, anchor=CENTER)
kaart.create_text(20,20,text=(waarde))
kaart.bind("<B1-Motion>", drag)
def drag(event):
event.widget.place(x=event.x_root, y=event.y_root,anchor=CENTER)
print(waarde)
button1 = Button(window, text="Nieuwe Kaart", command=pakkaart)
button1.pack()
window.mainloop()
So essentially looking for a way to bind a value to a canvas.

Your above code works fine, it shows the correct value, but if you want you can try this
from tkinter import *
from random import randint
window = Tk()
ws = window.winfo_screenwidth()
hs = window.winfo_screenheight()
w = 500 # width for the Tk root
h = 300 # height for the Tk root
x = (ws / 2) - (w / 2)
y = (hs / 2) - (h / 2)
window.geometry('%dx%d+%d+%d' % (w, h, x, y))
deck = [1, 2, 3, 4, 5, 6]
def pick_card():
global waarde, kaart
rand_card = randint(0, len(deck)-1)
card_number = deck[rand_card]
deck.remove(card_number)
card = Canvas(window, width=40, height=40, bg='yellow')
card.place(x=50, y=50, anchor=CENTER)
card_number_text = card.create_text(20, 20, text=card_number, tags=card_number)
card.bind("<Button-1>", lambda event: get_number(event, card_number_text)) # or you can use: card.bind("<Button-1>", lambda event: print(card_number))
card.bind("<B1-Motion>", drag)
def drag(event):
# This is better for move a widget
cx = window.winfo_pointerx() - window.winfo_rootx()
cy = window.winfo_pointery() - window.winfo_rooty()
event.widget.place(x=cx, y=cy)
def get_number(event, number):
print(event.widget.itemcget(number, "text"))
button1 = Button(window, text="Generate Card", command=pick_card)
button1.pack()
window.mainloop()
I've modified the drag(event) function and wrote two ways for get the current card value, for store it you can use some global varibles or create a class, the second would be better

Related

How to animate a "movie style" credit reel with tkinter and canvas?

I am trying to create a "movie" style credit reel using tkinter's canvas. I'd like to iterate through a list of names and have them scroll across the window. While I've successfully gotten a single name to scroll, I'm struggling to iterate over several names.
I appreciate your help and apologies for any gross oversights on my part.
from tkinter import *
import time
window = Tk()
window.geometry("1920x1080")
window.title("Window 1")
canvas1 = Canvas(window, width=1920, height=1080, bg="green", bd=0, highlightthickness=0, relief='ridge')
canvas1.pack()
class CreditList:
def __init__(self, text):
self.text = text
self.location = 1080
def credit_roll_function(self):
text = canvas1.create_text(960,self.location, text=self.text, anchor=S, fill="black",
font="Time 40")
while True:
canvas1.move(text, 0, -3)
window.update()
time.sleep(.03)
credit_list = ["First Name", "Second Name", "Third Name"]
for credit in credit_list:
item = CreditList(credit)
item.credit_roll_function()
window.mainloop()
Here's a simple way to do it that uses the universal widget after() method instead of calling time.sleep() which interferes with tkinter's mainloop(). Each time the class' roll_credits() method is called, it moves the text upward a little and schedules another call to itself if the text isn't at the top of the window yet.
import tkinter as tk
from tkinter.constants import *
class CreditList:
def __init__(self, lines):
self.location = HEIGHT
self.text = canvas.create_text(0, 0, text='\n'.join(lines), justify=CENTER,
anchor=NW, fill='black', font='Time 40')
xl, yt, xr, yb = canvas.bbox(self.text)
txtwidth = xr - xl
xpos = (WIDTH-txtwidth) // 2 # To center text horizontally.
canvas.move(self.text, xpos, self.location)
def roll_credits(self):
xl, yt, xr, yb = canvas.bbox(self.text)
if yb <= 0: # Completely off top of screen?
canvas.pack_forget()
tk.Button(text='Done', font=('Courier New', 20), relief=GROOVE, bg='orange',
command=window.quit).place(x=WIDTH/2, y=HEIGHT/2)
return # Stop.
canvas.move(self.text, 0, -3)
window.after(DELAY, self.roll_credits) # Keep going.
DELAY = 25 # Millisecs.
window = tk.Tk()
window.attributes('-fullscreen', True)
window.update_idletasks()
WIDTH, HEIGHT = window.winfo_width(), window.winfo_height() # Get screen size.
window.geometry(f'{WIDTH}x{HEIGHT}+0+0')
window.title('Window 1')
canvas = tk.Canvas(window, width=WIDTH, height=HEIGHT, bg='green', bd=0,
highlightthickness=0, relief='ridge')
canvas.pack()
credits = 'First Name', 'Second Name', 'Third Name'
cl = CreditList(credits)
window.after(DELAY, cl.roll_credits) # Start rolling credits "loop".
window.mainloop()
If you give all of your text items the same tag, you can move them all at the same time. Another solution is to create a single text item that is created by joining all of your strings with a newline.
For example, the following code illustrates how to create all of the items with a given tag, then moves them all one pixel at a time every 33 ms until they no longer are visible.
import tkinter as tk
class CreditList(tk.Canvas):
def __init__(self, master, **kwargs):
super().__init__(master, **kwargs)
def roll_credits(self, credit_list):
x = self.winfo_width() // 2
y = self.winfo_height()
temp = self.create_text(0,0,text="Hello")
bbox = self.bbox(temp)
self.delete(temp)
lineheight = bbox[3]-bbox[1]
linespacing = 4
for credit in credit_list:
self.create_text(x, y, text=credit, anchor="n", tags=("credit",))
y += lineheight + linespacing
self._animate()
def _animate(self):
self.move("credit", 0, -1)
x0, y0, x1, y1 = self.bbox("credit")
if y1 > 0:
self.after(33, self._animate)
root = tk.Tk()
credits = CreditList(root)
credits.pack(side="top", fill="both", expand=True)
credit_list = 'First Name', 'Second Name', 'Third Name'
root.after(1000, credits.roll_credits, credit_list)
root.mainloop()

Refresh tkinter label automatically not on button press

Hi there i am building a short game in tkinter and would like to be able to output to the user via a label within my tkinter window. I have looked at past questions and found no help apart from getting it to refresh using a button which is not what i want it to do. In short i need to have it refresh everytime a variable is changed.
My code :
import tkinter as tk
import time
root = tk.Tk()
root.resizable(width=False, height=False)
w = 800 # width for the Tk root
h = 500 # height for the Tk root
ws = root.winfo_screenwidth() # width of the screen
hs = root.winfo_screenheight() # height of the screen
x = (ws/2) - (w/2)
y = (hs/2) - (h/2)
root.geometry('%dx%d+%d+%d' % (w, h, x, y))
wheat=10
money=0
title=tk.Label(root, text="The Farm Game")
title.config(font=('times', 20, 'bold'))
title.place(height=30, width=300, x = 250 , y = 10)
def advance():
moneyguidisplay = tk.StringVar()
moneyshowed = ("£", money)
moneyguidisplay.set(moneyshowed)
moneygui = tk.Label(root, wraplength=200, textvariable=moneyguidisplay)
moneygui.config(bg='lightgreen', font=('times', 15, 'bold'))
moneygui.place(height=30, width=200, x=600, y=60)
Usershow = tk.StringVar()
shownow = ("Welcome to The farm game")
Usershow.set(shownow)
USER = tk.Label(root, wraplength=200, textvariable=Usershow)
USER.config(bg='lightpink', font=('times', 15, 'bold'))
USER.place(height=200, width=400, x=200, y=100)
wheatguidisplay = tk.StringVar()
wheatshowed = ("Wheat:", wheat)
wheatguidisplay.set(wheatshowed)
Wheatgui = tk.Label(root, wraplength=200, textvariable=wheatguidisplay)
Wheatgui.config(bg='lightblue', font=('times', 15, 'bold'))
Wheatgui.place(height=30, width=200, x=0, y=60)
root.after(100, advance)
root.after(100, advance)
root.mainloop()
Your question is a little unclear, but what I can understand is that you want to be able to change the text of a Label, depending on the value of another variable(correct me if I'm wrong). You can use the config method to do so. I have written a small function for it, you can put it in your program.
from tkinter import*
root=Tk()
L=Label(text="Label text changing after 5 sec")
L.grid()
# Call this function where the value of your variable/number changes
def ChangeValue(num):
L.config(text=str(num))
print("Value Changed")
root.update()
root.after(5000,lambda :ChangeValue("Text Changed!"))
root.mainloop()

Python RawTurtle not following goto() command

I am trying to create a program where you click on a Tkinter canvas and a RawTurtle moves to the mouse, but my code is not working. The canvas has a Button-1 event binded to it to tell the program the coordinates of the mouse.
But, when you click on the canvas, instead of the turtle going to the mouse, it kind of mirrors what you would expect it to do (moves away from the mosue as if another mosue is being mirrored). Both the event and the turtle position coordinates are the same when they are printed out.
Code:
import turtle
from tkinter import *
def move(event):
global t
x = event.x
y = event.y
t.setpos(x,y)
print(t.pos())
print(event)
def penState(event):
global penDown,t
if penDown == True:
t.penup()
penDown = False
else:
t.pendown()
penDown = True
def changeWidth(w):
t.pensize(w)
def changeColour(e=None):
global colourBox
t.color(colourBox.get())
colourBox.configure(fg=colourBox.get())
def doCircle():
global checkFillIsTrue,circleSizeBox
if checkFillIsTrue.get() == 1:
begin_fill()
t.circle(int(circleSizeBox.get()))
end_fill()
else:
circle(int(circleSizeBox.get()))
window = Tk('Paint')
window.title('onionPaint')
root = Frame(window)
root.pack(side=LEFT)
cv = Canvas(window,width=500,height=500)
t = turtle.RawTurtle(cv)
t.resizemode('user')
cv.bind('<Button-1>',move)
cv.bind('<Button-2>',penState)
cv.pack(side=RIGHT)
checkFillIsTrue=IntVar()
penDown = True
#Pen width box
sizeLabel = Label(root, text="Pen Width")
sizeLabel.grid()
sizeScale = Scale( root, variable = \
'var',orient=HORIZONTAL,command=changeWidth )
sizeScale.grid()
#Colour box
colourLabel = Label(root, text="Color(HEX or name):")
colourLabel.grid()
colourFrame = Frame(root)
colourFrame.grid()
colourBox = Entry(colourFrame, bd=1)
colourBox.pack(side=LEFT)
colourSubmit = Button(colourFrame,text="OK",command=changeColour)
colourSubmit.pack(side=RIGHT)
#Fill
fillLabel = Label(root,text='Fill')
fillLabel.grid()
fillFrame = Frame(root)
fillFrame.grid()
beginFill = Button(fillFrame,text='Begin Fill',command=t.begin_fill)
endFill = Button(fillFrame,text='End Fill',command=t.end_fill)
beginFill.pack(side=LEFT)
endFill.pack(side=RIGHT)
#Mmore shapes
Label(root,text='Shapes').grid()
#Circle form
Label(root,text='Circle',font=('Heveltica',8)).grid()
circleSize = Frame(root)
circleSize.grid()
circleSizeBox = Entry(circleSize,bd=1)
circleSizeBox.insert(0,'Radius')
circleSizeBox.pack(side=LEFT)
fillCheck =
Checkbutton(circleSize,text='Fill',variable=checkFillIsTrue).pack(side=LEFT)
circleSizeSubmit =
Button(circleSize,text='Draw',command=doCircle).pack(side=RIGHT)
#Text form
Label(root,text='Text',font=('Heveltica',8)).grid()
textFrame = Frame(root)
textFrame.grid()
window.mainloop()
Any help with this problem would be greatly appreciated.
Your code is a disaster. You should specifically read up on the 'global' keyword in Python and when it must be used. And Python code style in general. I believe the key fix to your program is to introduce a TurtleScreen overlay on the canvas and then switch to turtle event handing rather than tkinter event handing.
I've reworked your code below, fixing as many problems as I could:
import turtle
from tkinter import *
FONT = ('Helvetica', 8)
def move(x, y):
terrapin.setpos(x, y)
print(terrapin.pos())
def penState(x, y):
global penDown
if penDown:
terrapin.penup()
penDown = False
else:
terrapin.pendown()
penDown = True
def changeWidth(w):
terrapin.pensize(w)
def changeColour():
color = colourBox.get()
terrapin.color(color)
colourBox.configure(fg=color)
def doCircle():
radius = int(circleSizeBox.get())
if checkFillIsTrue.get():
terrapin.begin_fill()
terrapin.circle(radius)
terrapin.end_fill()
else:
terrapin.circle(radius)
window = Tk('Paint')
window.title('onionPaint')
root = Frame(window)
root.pack(side=LEFT)
canvas = Canvas(window, width=500, height=500)
screen = turtle.TurtleScreen(canvas)
terrapin = turtle.RawTurtle(screen)
screen.onclick(move, btn=1)
screen.onclick(penState, btn=2)
canvas.pack(side=RIGHT)
checkFillIsTrue = BooleanVar()
penDown = True
# Pen width box
sizeLabel = Label(root, text="Pen Width")
sizeLabel.grid()
sizeScale = Scale(root, variable='var', orient=HORIZONTAL, command=changeWidth)
sizeScale.grid()
# Colour box
colourLabel = Label(root, text="Color(HEX or name):")
colourLabel.grid()
colourFrame = Frame(root)
colourFrame.grid()
colourBox = Entry(colourFrame, bd=1)
colourBox.pack(side=LEFT)
colourSubmit = Button(colourFrame, text="OK", command=changeColour)
colourSubmit.pack(side=RIGHT)
# Fill
fillLabel = Label(root, text='Fill')
fillLabel.grid()
fillFrame = Frame(root)
fillFrame.grid()
beginFill = Button(fillFrame, text='Begin Fill', command=terrapin.begin_fill)
endFill = Button(fillFrame, text='End Fill', command=terrapin.end_fill)
beginFill.pack(side=LEFT)
endFill.pack(side=RIGHT)
# More shapes
Label(root, text='Shapes').grid()
# Circle form
Label(root, text='Circle', font=FONT).grid()
circleSize = Frame(root)
circleSize.grid()
circleSizeBox = Entry(circleSize, bd=1)
circleSizeBox.insert(0, 'Radius')
circleSizeBox.pack(side=LEFT)
fillCheck = Checkbutton(circleSize, text='Fill', variable=checkFillIsTrue).pack(side=LEFT)
circleSizeSubmit = Button(circleSize, text='Draw', command=doCircle).pack(side=RIGHT)
# Text form
Label(root, text='Text', font=FONT).grid()
textFrame = Frame(root)
textFrame.grid()
window.mainloop()

How to display text using Tkinter.Text at a random place on screen

Using python Tkinter, I want to place text at random locations upon the clicking of a key on mouse. Here is my code:
from Tkinter import *
import sys
from random import randint
root = Tk()
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
x = 10
y = 10
def key(event):
if event.char == 'c':
xloc = randint(0, 100)
yloc = randint(0, 100)
T.mark_set("insert", "%d.%d" % (0, 0))
T.insert('%d.%d' % (xloc, yloc), 'something')
if event.char == 'q':
sys.exit()
frame = Frame(root, width=screen_width, height=screen_height)
frame.bind("<Key>", key)
frame.focus_set()
frame.pack()
T = Text(frame, height=screen_height, width=screen_width, bg="white", fg="blue")
T.mark_set("insert", "%d.%d" % (0, 0))
T.pack()
root.mainloop()
After the window pops up, pressing 'c' causes the text to be inserted after the previous text, and not at a random location as expected.
Try using a canvas. This is very similar to your current implementation.
from Tkinter import *
from random import randint
root = Tk()
def key(event):
print "pressed", repr(event.char)
if event.char == 'c':
xloc = randint(0, 100)
yloc = randint(0, 100)
canvas.create_text(xloc, yloc, text='something')
canvas = Canvas(width=100, height=100, bg='white')
canvas.focus_set()
canvas.bind("<Key>", key)
canvas.pack(expand=YES, fill=BOTH)
root.mainloop()
I'd recommend something like this
from tkinter import *
import random
Text(text="put your text here".grid(row=random.randrange(0,9),column=random.randrange(0,9),columnspan=2)
This code simply places the text widget randomly in an 10*10 window. It should be easy enough to bind to a mouse click.

How do you create a grid of buttons in a frame using .pack() or .grid()?

from tkinter import *
from random import *
class Game:
def __init__(self):
self.root = Tk()
self.frame1 = Frame(self.root, width = 1055, height = 30)
self.frame1.pack()
self.frame_lvl = Frame(self.root, width = 1055, height = 1055)
self.frame_lvl.pack()
for frame_lvl in range(0,31):
self.frame_lvl = Frame(self.root)
self.frame_lvl.pack(side = BOTTOM)
for i in range(0,31):
for j in range(0,31):
button = Button(self.i, width = 30, height = 30, padx = 2, pady = 2)
button.pack(side = LEFT)
self.root.mainloop()
app = Game()
So I try to create a new frame level so the buttons won't keep printing on the same line but I'm not sure if the frame level will be saved as self.0, self.1, self.2, etc...
When I tried making the frame a grid and adjusting the width, height, rowspan, and column span, I got the error ("cannot use geometry manager grid inside . which already has slaves managed by pack") The error comes from these lines:
self.frame2 = Frame(width = 1055, height = 1055)
self.frame2.grid(columnspan = 30, rowspan = 30)
Any suggestions.
Note that there is only one self.frame_lvl so each time you assign it a new value you lose the existing value. In the end it will only contain the last value assigned to it. Also
for i in range(31):
for j in range(31):
creates 31*31 buttons(which is over 900 buttons). While you are learning, stick with all grid() or all pack() in a program until you learn how to mix the two. To create 3 rows of 5 buttons using grid()
from tkinter import *
from functools import partial
class Game:
def __init__(self):
self.root = Tk()
self.frame1 = Frame(self.root, width = 900, height = 30)
self.frame1.grid()
self.label=Label(self.frame1, text="")
self.label.grid(row=0, column=0, columnspan=5)
## save each button id in a list
self.list_of_button_ids=[]
for ctr in range(15):
## use partial to pass the button number to the
## function each time the button is pressed
button = Button(self.frame1, text=str(ctr), width = 20,
height = 20, padx = 2, pady = 2,
command=partial(self.button_callback, ctr))
this_row, this_col=divmod(ctr, 5)
## the label is in the top row so add one to each row
button.grid(row=this_row+1, column=this_col)
## add button's id to list
self.list_of_button_ids.append(button)
self.root.mainloop()
def button_callback(self, button_num):
""" display button number pressed in the label
"""
self.label.config(text="Button number %s and it's id=%s"
% (button_num, self.list_of_button_ids[button_num]))
app = Game()

Categories