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.
Related
This is my first experience with tkinter package. I'm trying to show a large image (5000, 5000) on a small window using x and y scrolls to explore the whole image. By following this answer, I have implemented a ScrollabarImage:
import tkinter
from PIL import Image, ImageTk
class ScrollableImage(tkinter.Canvas):
def __init__(self, master=None, **kw):
self.image = kw.pop('image', None)
super(ScrollableImage, self).__init__(master=master, **kw)
self['highlightthickness'] = 0
self.propagate(0) # wont let the scrollbars rule the size of Canvas
self.create_image(0,0, anchor='nw', image=self.image)
# Vertical and Horizontal scrollbars
self.v_scroll = tkinter.Scrollbar(self, orient='vertical', width=6)
self.h_scroll = tkinter.Scrollbar(self, orient='horizontal', width=6)
self.v_scroll.pack(side='right', fill='y')
self.h_scroll.pack(side='bottom', fill='x')
# Set the scrollbars to the canvas
self.config(xscrollcommand=self.h_scroll.set,
yscrollcommand=self.v_scroll.set)
# Set canvas view to the scrollbars
self.v_scroll.config(command=self.yview)
self.h_scroll.config(command=self.xview)
# Assign the region to be scrolled
self.config(scrollregion=self.bbox('all'))
self.focus_set()
self.bind_class(self, "<MouseWheel>", self.mouse_scroll)
def mouse_scroll(self, evt):
if evt.state == 0 :
# self.yview_scroll(-1*(evt.delta), 'units') # For MacOS
self.yview_scroll( int(-1*(evt.delta/120)) , 'units') # For windows
if evt.state == 1:
# self.xview_scroll(-1*(evt.delta), 'units') # For MacOS
self.xview_scroll( int(-1*(evt.delta/120)) , 'units') # For windows
And in the main:
if __name__ == "__main__":
hdul = fits.open('gputest.fits')
data = hdul[1].data
header = hdul[1].header
img = Image.fromarray(data, mode='F')
root = tkinter.Tk()
root.geometry("1000x1000")
root.resizable(0, 0) #Don't allow resizing in the x or y direction
show_image = ScrollableImage(root, image=img)
show_image.pack()
root.mainloop()
But the result is completely wrong:
The problem is that the image is not complete, it had been cut. Even if the scrolls works, I can not move on the whole image, only a cutout has been displayed. Why this happens?
I would like to: expand the image to the whole window dimension, keeping the scrolls since the image is larger than the window size. I tried to fined an explanation and correct the code, but I was not able to find a solution, maybe because I'm quite new to tkinter.
I've been working on adding label frames to my window but for some reason whenever I use .place it never places the frame. Grid and pack work though. I'm trying to get the label frame right in the middle of the screen through coordinates. Heres my code (the error is somewhere in the createstock functiom):
import tkinter as tk
import yfinance
class StockWindow:
def __init__(self,master,number):
self.master = master
self.frame=tk.Frame(self.master)
self.frame.pack()
w, h = master.winfo_screenwidth(), master.winfo_screenheight()
self.master.overrideredirect(1)
self.master.geometry("%dx%d+0+0" % (w, h))
self.master.focus_set() # <-- move focus to this widget
self.master.bind("<Escape>", lambda e: e.widget.quit())
#################################################
## Labels
self.amountChanged = tk.Label(self.master,text = "$1000")
self.amountChanged.place(x=w/2,y=h/2)
self.highestChangedStock = tk.Label(self.master,text = "Amzn")
self.highestChangedStock.place(x=w/2+10,y=h/2+40)
self.lowestChangedStock = tk.Label(self.master,text = "this one")
self.stockTips = tk.Label(self.master,text = "Buy some")
self.stockTips.place(x=2,y=777)
self.marketChange = tk.Label(self.master,text = "Alot!")
self.marketChange.place(x=23,y=66)
self.stockNews = tk.Label(self.master,text = "News Here!")
self.stockNews.place(x=23,y=234)
self.stockNewds = tk.Label(self.master,text = "News Hewewere!")
self.stockNewds.place(x=300,y=300)
## Buttons
self.seeAllStocks = tk.Button(self.master,text ="do you wanna see more stocks?")
self.seeAllStocks.place(x=0,y=0)
self.goBack =tk.Button(self.master,text = "Go back",command=self.close_windows)
self.goBack.place(x=100,y=100)
self.createStock("afdhsfdhsfhsfghsgfhsdg",3,30)
#########
def createStock(self,name,pricechange,placement):
stockframe = tk.LabelFrame(self.frame,text='')
#stockframe.size(200)
stockframe.place(x=400,y=400,height=10,width=10)
#stockframe.pack(expand='yes',fill='both')
#stockframe.grid(column=5,row=5)
tempLabel = tk.Label(stockframe,text=name)
tempLabel.pack()
def close_windows(self):
self.master.destroy()
The problem is that self.frame is used as the parent of the label frame, but it has a height of 1x1 since there's nothing in it. Thus, any widget placed inside it will also be invisible.
If you want to use place to center a widget, the simplest solution is to use relative coordinates with a relative x and y coordinate of .5 (ie: 50% of the width and height of the window).
For example, you don't need to place the labelframe inside a label inside the root window. Just remove self.frame and use relative coordinates for the label frame:
stockframe = tk.LabelFrame(self.master,text='')
stockframe.place(relx=.5, rely=.5)
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]
I'm trying to make it possible for my program's user to drag an image from a widget and drop it in another without removing the image from its first position.
I thought on using a TopLevel for this. Upon a click+drag event, a TopLevel, containing the same image, would pop up right above the clicked image on the event.x and event.y position. Then it would change its position along with the mouse and only upon ButtonRelease-1 the TopLevel would be destroyed.
If the Button Release event was triggered on the master x and y coordinates corresponding to where is my other widget (in the case, a canvas), then it would trigger a canvas.create_image() using the event.x and event.y.
The problems I'm facing are:
Only 1 TopLevel should appear at once, but I had no success limiting it. Several windows overlap as I click and drag the mouse.
I can't make Toplevel.geometry use the bound event's x and y positions.
How to make the Toplevel display the same image as the user clicked, without magic numbers and variables?
Here's my code so far:
class animalImgList():
def __init__(self, a):
#Constructor
initX = 75
initY = 40
animalList = ['pig2.jpg', 'pig3.jpg', 'pig4.jpg']
for a in animalList:
vars(self)[a+'img'] = PIL.Image.open(a)
vars(self)[a+'tkimg'] = PIL.ImageTk.PhotoImage(vars(self)[a+'img'])
vars(self)[a+'labelName'] = Label(anmlCanvas, image=vars(self)[a+'tkimg'])
vars(self)[a+'canvasImg'] = anmlCanvas.create_image(initX, initY,image=(vars(self)[a+'tkimg']))
initY = initY + 70
anmlImgList = []
anmlImgList.append(vars(self)[a+'canvasImg'])
imgTags = anmlCanvas.addtag_all("img")
anmlCanvas.tag_bind("img", "<Button-1>", self.createImg)
def createImg(self, event):
newImg = Toplevel(root)
newImg.geometry("50x40"+"+"+ x+"+"+y)
newImgMsg = Message(newImg, text="This is supposed to be an image")
newImgMsg.pack()
newImg.update_idletasks()
newImg.overrideredirect(True)
createImgOpen = True
if createImgOpen == True:
pass
To drag an image between two canvases without removing the image from the first canvas.
The idea is the following:
When the user clicks on the canvas can1 (click1 function):
get the item the user clicked on with can1.find_closest
get its image with can1.itemcget
create a toplevel containing the image
bind mouse motion to drag along the toplevel: for that you need to use the event.x_root and event.y_root to change the toplevel's geometry.
When the user releases the left mouse button (release function):
unbind mouse motion
if the toplevel is inside the canvas can2, create the image in can2 at the mouse position
destroy the toplevel
This way, there can be only one toplevel because each time the button is released, the toplevel is destroyed.
Here is the code:
import tkinter as tk
class DragToplevel(tk.Toplevel):
def __init__(self, master, image, x, y):
tk.Toplevel.__init__(self, master)
self.overrideredirect(True)
self.geometry('+%i+%i' % (x, y))
self.image = image
self.label = tk.Label(self, image=image, bg='red')
self.label.pack()
def move(self, x, y):
self.geometry('+%i+%i' % (x, y))
root = tk.Tk()
can1 = tk.Canvas(root, width=300, height=300, bg='white')
can2 = tk.Canvas(root, width=300, height=300, bg='white')
can1.pack(side='left')
can2.pack(side='right')
root.geometry('800x800')
im = tk.PhotoImage('tux', master=root, file='/home/juliette/Images/tux_mini.png')
drag_id = ''
dragged = None
can1.create_image(100, 200, image=im)
def click1(event):
global drag_id, dragged
items = can1.find_closest(event.x, event.y)
if items:
image = can1.itemcget(items[0], 'image')
dragged = DragToplevel(root, image, event.x_root, event.y_root)
drag_id = root.bind('<Motion>', lambda e: dragged.move(e.x_root, e.y_root))
def release(event):
global drag_id, dragged
root.unbind('<Motion>', drag_id)
drag_id = ""
xr, yr = event.x_root, event.y_root
x2, y2 = can2.winfo_rootx(), can2.winfo_rooty()
w2, h2 = can2.winfo_width(), can2.winfo_height()
if dragged and xr >= x2 and xr < x2 + w2 and yr >= y2 and yr < y2 + h2:
can2.create_image(xr - x2, yr - y2, image=dragged.image, anchor='nw')
if dragged:
dragged.destroy()
dragged = None
can1.bind('<ButtonPress-1>', click1)
root.bind('<ButtonRelease-1>', release)
root.mainloop()
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()