Python tkinter Canvas Dynamic Create.Line - python

What I want to do is have the user click on somewhere on the Canvas, then click elsewhere and have a straight line drawn between the two points. I'm new to TKinter and after some googling and searching on here I'm having trouble finding a solid answer for this.
The way that I have been thinking about it, there should be an onclick event which passes the mouse coordinates on the canvas and then an onrelease event which passes those coordinates on the canvas, thus creating a line between them. This line would have to be an object that I could then remove at some point via another button but that's a separate issue.
Any help would be greatly appreciated, or even any links to articles/tutorials that may help also

The only thing you have to do is bind "<Button-1>" and "<ButtonRelease-1>" to the canvas:
from Tkinter import Tk, Canvas
start = None
def onclick_handler(event):
global start
start = (event.x, event.y)
def onrelease_handler(event):
global start
if start is not None:
x = start[0]
y = start[1]
event.widget.create_line(x, y, event.x, event.y)
start = None
master = Tk()
canvas = Canvas(master, width=200, height=200)
canvas.bind("<Button-1>", onclick_handler)
canvas.bind("<ButtonRelease-1>", onrelease_handler)
canvas.pack()
master.mainloop()
I don't like at all using global variables, it is much cleaner to wrap all the widgets and related functions in a class. However, as an example I think it is clear enough.

Looks something pretty starightforward for me.
Just check the documentation on Canvas here:
http://effbot.org/tkinterbook/canvas.htm
And on events here:
http://effbot.org/tkinterbook/tkinter-events-and-bindings.htm
And them, just type in some code like this -
the class is even more complicated than a sinple hello World -
two global variables would have done for simpler code:
from Tkinter import Canvas, Tk, mainloop
w = Tk()
c = Canvas(w)
c.pack()
class DrawLines(object):
def __init__(self, canvas):
self.canvas = canvas
self.start_coords = None
self.end_coords = None
def __call__(self, event):
coords = event.x, event.y
if not self.start_coords:
self.start_coords = coords
return
self.end_coords = coords
self.canvas.create_line(self.start_coords[0],
self.start_coords[1],
self.end_coords[0],
self.end_coords[1])
self.start_coords = self.end_coords
c.bind("<Button-1>", DrawLines(c))
mainloop()

Related

tkinter: Returning the (x,y) results of a mouse button click event to be used as a variable

I am eventually trying to run a code that's a bit more sophisticated than this, but this is the hump I'm trying to get over. I just need to be able to run a code that takes the x,y coordinates of the pixel clicked and records them as either a variable or appends them to a list. For right now I have them set as L_Click_x and L_Click_y, I would eventually like to be able to add these coordinates to a list or be able to call upon them in the function. Nothing I have tried seems to work, I'm pretty new to this so there's probably something fundamental that I'm just not getting. (Also the red lines that appear are just for visual confirmation of the click)
from tkinter import *
def motion(event):
x, y = event.x, event.y
print("Current Position = ",(x,y))
def LeftClick(event):
L_Click_x, L_Click_y = event.x, event.y
redline = canvas.create_line(0,L_Click_y,400,L_Click_y,fill = "red")
redline = canvas.create_line(L_Click_x,0,L_Click_x,300,fill = "red")
print("Left Click = ",(L_Click_x,L_Click_y))
root = Tk()
root.bind('<Motion>',motion)
root.bind('<Button-1>',LeftClick)
canvas = Canvas(root, width = "400",height = "300")
canvas.pack()
root.mainloop()
You will need to keep track of the first clicked point, until the second is known, and then draw a line segment between the two.
This simple example uses a global variable, and the points are discarded when drawn; you will probably want to use a container to keep track of the line segments, and a program construction that allows you to access it without making it global.
Please note that I bound the events to the canvas instead of root; I changed the imports to avoid cluttering the namespace (as your app grows, you'll be glad you did), and changed the camelcase names to a more pythonic convention.
import tkinter as tk
def motion(event):
x, y = event.x, event.y
print("Current Position = ", (x, y))
def left_click(event):
global line_segment
line_segment.append(event.x)
line_segment.append(event.y)
canvas.create_oval(event.x+1, event.y+1, event.x-1, event.y-1)
if len(line_segment) == 4:
canvas.create_line(*line_segment, fill="red")
line_segment = []
if __name__ == '__main__':
line_segment = []
root = tk.Tk()
canvas = tk.Canvas(root, width="400", height="300")
canvas.pack()
canvas.bind('<Motion>', motion)
canvas.bind('<Button-1>', left_click)
root.mainloop()

using python drag & drop selected image segment

My requirement is i need to drag an image to desired location.
Based on the link board-drawing code to move an oval
the following is the code snapshot i tried. I am not getting any errors its blank. Please let me know the way to take it forward.
Sample image segment attached
import Tkinter as tk
from Tkinter import *
from PIL import ImageTk, Image
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
# create a canvas
self.canvas = tk.Canvas(width=400, height=400)
self.canvas.pack(fill="both", expand=True)
# this data is used to keep track of an
# item being dragged
self._drag_data1 = {"x": 0, "y": 0, "item1": None}
startframe = tk.Frame(root)
canvas = tk.Canvas(startframe,width=1280,height=720)
startframe.pack()
canvas.pack()
one = tk.PhotoImage(file=r'images/test1.gif')
root.one = one # to prevent the image garbage collected.
canvas.create_image((0,0), image=one, anchor='nw',tags="img1")
self.canvas.tag_bind("img1", "<1>", self.on_token_press1)
self.canvas.tag_bind("img1", "<1>", self.on_token_release1)
self.canvas.tag_bind("token", "<B1-Motion>", self.on_token_motion1)
def on_token_press1(self, event):
print("sss")
# record the item and its location
self._drag_data1["item1"] = self.canvas.find_closest(event.x, event.y)[0]
self._drag_data1["x"] = event.x
self._drag_data1["y"] = event.y
def on_token_release1(self, event):
# reset the drag information
self._drag_data1["item1"] = None
self._drag_data1["x"] = 0
self._drag_data1["y"] = 0
def on_token_motion1(self, event):
'''Handle dragging of an object'''
# compute how much the mouse has moved
delta_x = event.x - self._drag_data1["x"]
delta_y = event.y - self._drag_data1["y"]
# move the object the appropriate amount
self.canvas.move(self._drag_data1["item1"], delta_x, delta_y)
# record the new position
self._drag_data1["x"] = event.x
self._drag_data1["y"] = event.y
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()
As said in the comments:
Indentation is important!
Within the Example-class its initiation method this entire code-block is misaligned:
startframe = tk.Frame(root)
canvas = tk.Canvas(startframe,width=1280,height=720)
startframe.pack()
canvas.pack()
one = tk.PhotoImage(file=r'images/test1.gif')
root.one = one # to prevent the image garbage collected.
canvas.create_image((0,0), image=one, anchor='nw',tags="img1")
self.canvas.tag_bind("img1", "<1>", self.on_token_press1)
self.canvas.tag_bind("img1", "<1>", self.on_token_release1)
self.canvas.tag_bind("token", "<B1-Motion>", self.on_token_motion1)
If I simply add a Tab to the indentation of the entire block I was able to run the OP's code without any problem.
Since the script requires a GIF file to be placed in images/test1.gif I downloaded and used this GIF:
tkinter doesn't seem to actually play the gif (which isn't asked by the OP), but it does indeed show it.

tkinter - How to drag and drop widgets?

I am trying to make a Python program in which you can move around widgets.
This is my code:
import tkinter as tk
main = tk.Tk()
notesFrame = tk.Frame(main, bd = 4, bg = "a6a6a6")
notesFrame.place(x=10,y=10)
notes = tk.Text(notesFrame)
notes.pack()
notesFrame.bind("<B1-Motion>", lambda event: notesFrame.place(x = event.x, y = event.y)
But, this gets super glitchy and the widget jumps back and forth.
The behavior you're observing is caused by the fact that the event's coordinates are relative to the dragged widget. Updating the widget's position (in absolute coordinates) with relative coordinates obviously results in chaos.
To fix this, I've used the .winfo_x() and .winfo_y() functions (which allow to turn the relative coordinates into absolute ones), and the Button-1 event to determine the cursor's location on the widget when the drag starts.
Here's a function that makes a widget draggable:
def make_draggable(widget):
widget.bind("<Button-1>", on_drag_start)
widget.bind("<B1-Motion>", on_drag_motion)
def on_drag_start(event):
widget = event.widget
widget._drag_start_x = event.x
widget._drag_start_y = event.y
def on_drag_motion(event):
widget = event.widget
x = widget.winfo_x() - widget._drag_start_x + event.x
y = widget.winfo_y() - widget._drag_start_y + event.y
widget.place(x=x, y=y)
It can be used like so:
main = tk.Tk()
frame = tk.Frame(main, bd=4, bg="grey")
frame.place(x=10, y=10)
make_draggable(frame)
notes = tk.Text(frame)
notes.pack()
If you want to take a more object-oriented approach, you can write a mixin that makes all instances of a class draggable:
class DragDropMixin:
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
make_draggable(self)
Usage:
# As always when it comes to mixins, make sure to
# inherit from DragDropMixin FIRST!
class DnDFrame(DragDropMixin, tk.Frame):
pass
# This wouldn't work:
# class DnDFrame(tk.Frame, DragDropMixin):
# pass
main = tk.Tk()
frame = DnDFrame(main, bd=4, bg="grey")
frame.place(x=10, y=10)
notes = tk.Text(frame)
notes.pack()
I came up with a different approach and it is useful when you want ALL of your widgets drag-able in the SAME window. I do also like the math used in this approach more than in the accepted answer.
The code is explained line by line as commented lines below:
import tkinter as tk
def drag_widget(event):
if (w:=root.dragged_widget): #walrus assignment
cx,cy = w.winfo_x(), w.winfo_y() #current x and y
#deltaX and deltaY to mouse position stored
dx = root.marked_pointx - root.winfo_pointerx()
dy = root.marked_pointy - root.winfo_pointery()
#adjust widget by deltaX and deltaY
w.place(x=cx-dx, y=cy-dy)
#update the marked for next iteration
root.marked_pointx = root.winfo_pointerx()
root.marked_pointy = root.winfo_pointery()
def drag_init(event):
if event.widget is not root:
#store the widget that is clicked
root.dragged_widget = event.widget
#ensure dragged widget is ontop
event.widget.lift()
#store the currently mouse position
root.marked_pointx = root.winfo_pointerx()
root.marked_pointy = root.winfo_pointery()
def finalize_dragging(event):
#default setup
root.dragged_widget = None
root = tk.Tk()
#name and register some events to some sequences
root.event_add('<<Drag>>', '<B1-Motion>')
root.event_add('<<DragInit>>', '<ButtonPress-1>')
root.event_add('<<DragFinal>>', '<ButtonRelease-1>')
#bind named events to the functions that shall be executed
root.bind('<<DragInit>>', drag_init)
root.bind('<<Drag>>', drag_widget)
root.bind('<<DragFinal>>', finalize_dragging)
#fire the finalizer of dragging for setup
root.event_generate('<<DragFinal>>')
#populate the window
for color in ['yellow','red','green','orange']:
tk.Label(root, text="test",bg=color).pack()
root.mainloop()
Tkinter has a module for this, documented in the module docstring. It was expected that it would be replaced by a tk dnd module, but this has not happened. I have never tried it. Searching SO for [tkinter] dnd returns this page. Below is the beginning of the docstring.
>>> from tkinter import dnd
>>> help(dnd)
Help on module tkinter.dnd in tkinter:
NAME
tkinter.dnd - Drag-and-drop support for Tkinter.
DESCRIPTION
This is very preliminary. I currently only support dnd *within* one
application, between different windows (or within the same window).
[snip]

Changing text color when hovering over text with Tkinter?

So I have a bunch of text on a canvas in Tkinter and I want to make it so the text color changes when the mouse is hovering over the text. For the life of me I can't figure out how to do it, and there doesn't seem to be a lot of information about Tkinter anywhere.
for city in Cities:
CityText = Cities[i]
board.create_text(CityLocs[CityText][0], CityLocs[CityText][1], text=CityText, fill="white")
CityText = Cities[i]
i = i + 1
That's just my code to place the text on the canvas, although I'm not sure what else to post to get my point across. Is there no 'hover' function or something like that built into Tkinter?
You can bind arbitrary events (mouse, keyboard, window manager and possibly others) to any widget in Tkinter.
A nice documentation for that is at http://effbot.org/tkinterbook/tkinter-events-and-bindings.htm -
For example, to bind color changes to widgets when mouse hover over them:
import Tkinter
from functools import partial
def color_config(widget, color, event):
widget.configure(foreground=color)
parent = Tkinter.Tk()
text = Tkinter.Label(parent, text="Hello Text")
text.bind("<Enter>", partial(color_config, text, "red"))
text.bind("<Leave>", partial(color_config, text, "blue"))
text.pack()
Tkinter.mainloop()
The use of functools.partial here allows you to re-use a variable for your text (Label) widget, since you are appending them to a list. If one would settle to simply using lambda you would have a disgusting surprise, as the variable referring to the widget in the body of the lambda function would always point to the last value it had inside the for loop. functools.partial "freeze" the variable content at the time it is called, and yields a new function.
However, since you are placing the items in a Canas, you can either set the "fill" and "fillactive" attributes for each item, as in #mgilson's answer, or you can create a more generic class to handle not only hovering, but other events you choose to implement later.
If your class has a __call__ method, you can pass an instance of it to the bind method of the canvas, so that the resulting object is called for each event on the canvas. In this case, mouse-motion event suffices:
from Tkinter import *
class Follower(object):
def __init__(self,on_color="#fff", off_color="#000"):
self.on_color = on_color
self.off_color = off_color
self.previous_item = None
def hover(self, canvas, item, x, y):
x1, y1, x2, y2 = canvas.bbox(item)
if x1 <= x <= x2 and y1 <= y <= y2:
return True
return False
def __call__(self, event):
canvas = event.widget
item = canvas.find_closest(event.x, event.y)
hovering = self.hover(canvas, item, event.x, event.y)
if (not hovering or item != self.previous_item) and self.previous_item is not None:
canvas.itemconfig(self.previous_item, fill=self.off_color)
if hovering:
canvas.itemconfig(item, fill=self.on_color)
self.previous_item = item
master=Tk()
canvas=Canvas(master)
canvas.pack()
canvas.create_text((40,20),text="Hello World!",fill="black")
canvas.create_text((60,80),text="FooBar",fill="black")
canvas.bind("<Motion>", Follower())
master.mainloop()
(ps. canvas and text placement example borrowed from #mgilson's answer)
Here's a (admittedly) pretty lame example that works on OS-X...
from Tkinter import *
master=Tk()
canvas=Canvas(master)
canvas.pack()
canvas.create_text((20,20),activefill="red",text="Hello World!",fill="black")
master.mainloop()
reference: http://effbot.org/tkinterbook/canvas.htm
This is already built into Tkinter. Use the 'activefill' option when you create your text to specify which color you want it to be.
See the following link for more information. Effbot.org/tkinterbook is my go to tkinter.
http://effbot.org/tkinterbook/canvas.htm#Tkinter.Canvas.create_text-method

Getting Tkinter pack_forget to work only on unique objects

Ubuntu Linux 11.04 / Python 2.7 / Tkinter
I'm a novice at Python GUIs and I'm having trouble making single objects disappear and reappear. pack_forget kind of works for me, but the way I'm doing it makes all the objects blink in and out, when I only want one at a time. In the boiled-down example below, I'm trying to only make object "a" appear and then disappear and then do the same for object "b".
I would be very grateful for any help, plus some better docs on this would be cool too. The stuff I've been able to find has been light on examples.
from Tkinter import *
import time
class Box:
def __init__(self, canvas):
self.canvas = canvas
def type(self, type):
if type == "1":
self.if = self.canvas.create_rectangle(0, 0, 50, 50, fill="red")
elif type == "2":
self.if = self.canvas.create_rectangle(50, 50, 100, 100, fill="blue")
def hide(self):
self.canvas.pack_forget()
self.canvas.update_idletasks()
root.update()
def unhide(self):
self.canvas.pack()
self.canvas.update_idletasks()
root.update()
root = Tk()
frame = Frame(root)
frame.pack()
canvas = Canvas(frame, width=500, height=200)
canvas.pack()
root.update()
a = Box(canvas)
a.type("1")
b = Box(canvas)
b.type("2")
a.unhide()
time.sleep(.5)
a.hide()
time.sleep(1)
b.unhide()
time.sleep(.5)
b.hide()
time.sleep(1)
There are many things wrong or curious about your code. For one, you should never call sleep in a program that has an event loop. When you call that, your whole program will freeze. If you want something to happen after a fixed period of time, use the after method to schedule a function to run in the future.
Second, there is no point in calling root.update after calling self.canvas.update_idletasks. For one, assume it is a rule written in stone that you should never call update. It's OK to call update if you know the ramifications of that, but since you are just learning and don't know, assume it's unsafe to call it. Plus, update does the same work as update_idletasks and more, so if you do choose to call update, calling update_idletasks is unnecessary.
As to your real problem. You seem to be wanting to create two distinct "Box" objects, and want to hide and show them independently (?). However, both boxes are rectangles that are drawn on the same canvas. When you call pack_forget, that affects the whole canvas so both of these objects will disappear and then reappear.
It isn't clear what your intention is. If each instance of "Box" is just a rectangle on the canvas, you don't want to use pack_forget because that works on widgets, not canvas objects.
If you just want the rectangles to appear and disappear you have several choices. You can destroy and recreate them each time, or you can use the canvas move or coords method to move the item to a non-visible portion of the canvas. You could also manipulate the stacking order to raise or lower an object above or below any or all other objects.
Here's a quick example that uses the trick of moving the items outside the visible area of the canvas.
from Tkinter import *
import time
class Box:
def __init__(self, canvas, type):
self.canvas = canvas
if type == "1":
self.rect = canvas.create_rectangle(0, 0, 50, 50, fill="red")
elif type == "2":
self.rect = canvas.create_rectangle(50, 50, 100, 100, fill="blue")
self.coords = canvas.coords(canvas, self.rect)
def hide(self):
# remember where this object was
self.coords = canvas.coords(self.rect)
# move it to an invisible part of the canvas
self.canvas.move(self.rect, -1000, -1000)
def unhide(self):
# restore it to where it was
self.canvas.coords(self.rect, *self.coords)
root = Tk()
frame = Frame(root)
frame.pack()
canvas = Canvas(frame, width=500, height=200)
canvas.pack()
a = Box(canvas, type="1")
b = Box(canvas, type="2")
root.after(1000, a.hide)
root.after(2000, a.unhide)
root.after(3000, b.hide)
root.after(4000, b.unhide)
root.mainloop()
Finally, if what you really want is for an object to blink continuously, you can have the hide method automatically call the unhide method and visa versa:
def hide(self):
# remember where this object was
self.coords = canvas.coords(self.rect)
# move it to an invisible part of the canvas
self.canvas.move(self.rect, -1000, -1000)
# unhide after a second
self.canvas.after(1000, self.unhide)
def unhide(self):
# restore it to where it was
self.canvas.coords(self.rect, *self.coords)
# re-hide after a second
self.canvas.after(1000, self.hide)

Categories