How draw a dot on canvas on click event Tkinter Python - python-3.x

I have the following piece of code that takes an image within a canvas and then whenever I click the paint function draws a dot over it.
Everything is working fine except that the paint function is not working as expected.
Desirable output
Click event draws a dot. No need to drag the on click event
Actual output
I have to drag the on mouse click event to see a drawing on the canvas.
I guess there might be a slight problem with the paint function. However, I haven't been able to know what it is exactly.
from tkinter import *
from PIL import Image, ImageTk
class Main(object):
def __init__(self):
self.canvas = None
def main(self):
master = Tk()
# Right side of the screen / image holder
right_frame = Frame(master, width=500, height=500, cursor="dot")
right_frame.pack(side=LEFT)
# Retrieve image
image = Image.open("./image/demo.JPG")
image = image.resize((800, 700), Image.ANTIALIAS)
photo = ImageTk.PhotoImage(image)
# Create canvas
self.canvas = Canvas(right_frame, width=800, height=700)
self.canvas.create_image(0, 0, image=photo, anchor="nw")
self.canvas.pack()
self.canvas.bind("<B1-Motion>", self.paint)
mainloop()
def paint(self, event):
python_green = "#476042"
x1, y1 = (event.x - 1), (event.y - 1)
x2, y2 = (event.x + 1), (event.y + 1)
self.canvas.create_oval(x1, y1, x2, y2, fill=python_green, outline=python_green, width=10)
if __name__ == "__main__":
Main().main()
Fix:
Added these two methods:
def on_button_press(self, event):
self.x = event.x
self.y = event.y
def on_button_release(self, event):
python_green = "#476042"
x0,y0 = (self.x, self.y)
x1,y1 = (event.x, event.y)
changed canvas to this:
self.canvas.bind("<ButtonPress-1>", self.on_button_press)
self.canvas.bind("<ButtonRelease-1>", self.on_button_release)

When you only click and don't move the mouse, B1-Motion isn't triggering.
To bind to mouse press (as well as mouse moving), you can add self.canvas.bind("<ButtonPress-1>", self.paint) before the mainloop.

Related

Customtkinter/Tkinter canvas objects jumping around

I am fairly new to tkinter and I've been working on this feature of my project that allows a user to drag and drop canvas objects around, however when I move around the canvas and try to move the canvas object again, it behaves weirdly. It's somewhat hard to explain so I left a video below for context and the code as well. Any kind of help is appreciated :)
import customtkinter
from tkinter import Canvas
from PIL import Image, ImageTk
def move(event):
my_canvas.moveto(my_image,event.x-50,event.y-50)
def scan(event):
my_canvas.scan_mark(event.x, event.y)
def drag(event):
my_canvas.scan_dragto(event.x, event.y, gain=2)
def display_coords(event):
my_label.configure(text=f"X: {event.x} Y:{event.y}")
app = customtkinter.CTk()
frame1 = customtkinter.CTkFrame(master=app)
frame1.pack(padx=10,pady=10, expand=True, fill="both")
my_canvas = Canvas(master=frame1, height=100, width=100, bg="black")
my_canvas.pack(expand=True, fill="both")
#Resize image(originally 512 x 512)
img = Image.open("assets/computadora.png")
resized_image = img.resize((100,100))
image = ImageTk.PhotoImage(resized_image)
frame1.image = image
my_image = my_canvas.create_image(0, 0, image=image, anchor="nw")
my_canvas.tag_bind(my_image,"<Button1-Motion>", move, add="+")
my_canvas.bind("<Button-3>", scan)
my_canvas.bind("<Button3-Motion>", drag)
#Provides X-Y coordinates of mouse cursor when canvas object is selected
my_label = customtkinter.CTkLabel(master=my_canvas, text="X: None Y: None")
my_label.pack(padx="10px", pady="10px", anchor="se")
my_canvas.tag_bind(my_image, "<Button1-Motion>", display_coords, add="+")
my_canvas.configure(scrollregion=my_canvas.bbox(my_image))
app.mainloop()
here

Cropping multiple parts of the image and placing on the canvas in Tkinter

I am new to the Tkinter platform so please help me where I'm going wrong.
I have a floorplan image in which I want to cut the objects in it and place it on a canvas screen so that individual objects can be dragged if I want.
I'm able to cut and paste the objects on the screen except the first object. It is not placed on the screen. Can anyone help me?
I am using a Matlab code to identify the objects in the floorplan image. I am attaching the Matlab file.
Is it possible to add the wall to the screen? I have no idea at all. Can anyone suggest how to add the wall?
Here is my code
import tkinter as tk
from tkinter import *
from PIL import Image,ImageTk
from scipy.io import loadmat
root = tk.Tk()
canvas = tk.Canvas(width=800, height=800)
canvas.grid(row=4,column=0,sticky=(N,W,E,S))
#canvas.config(width=100,height=100)
root.grid_columnconfigure(0, weight=1)
root.grid_rowconfigure(4, weight=1)
mfile=loadmat('C:\\Users\\User\\Desktop\\tkinter_codes\\obj identification\\ans.mat')
#print(mfile.values())
#print(len(mfile['ans'][0]))
print(mfile.values())
class Example(tk.Frame):
def __init__(self, parent):
self.parent =parent
tk.Frame.__init__(self, parent)
self.canvas = tk.Canvas(width=800, height=800)
self.canvas.grid(row=0,column=0,sticky=(N,W,E,S))
#canvas.pack (expand =1, fill =tk.BOTH)
self.canvas.tag_bind("DnD","<Button-1>")
self._drag_data = {"x": 0, "y": 0, "item": None}
self.canvas.tag_bind("token", "<ButtonPress-1>", self.drag_start)
self.canvas.tag_bind("token", "<ButtonRelease-1>", self.drag_stop)
self.canvas.tag_bind("token", "<B1-Motion>", self.drag)
self.canvas.bind("<ButtonPress-1>", self.on_button_1)
self.iimg=Image.open("C:\\Users\\User\\Desktop\\tkinter_codes\\floorplans\\ROBIN\\Dataset_3rooms\\Cat1_1.jpg")
#iimg=iimg.resize((1000, 800), Image.ANTIALIAS)
self.canvas.img=ImageTk.PhotoImage(self.iimg)
#canvas.img = canvas.img.resize((250, 250), Image.ANTIALIAS)
self.canvas_img=canvas.create_image(0,0,image=self.canvas.img,anchor="nw")
self.mylist=[]
for x in range(len(mfile['ans'][0])):
#canvas.create_rectangle((mfile['ans'][0][x][0][0],mfile['ans'][0][x][0][1],mfile['ans'][0][x][0][0]+mfile['ans'][0][x][0][2],mfile['ans'][0][x][0][1]+mfile['ans'][0][x][0][3]),outline='red',tags=("token","DnD"))
self.im_crop = self.iimg.crop((mfile['ans'][0][x][0][0],mfile['ans'][0][x][0][1],mfile['ans'][0][x][0][0]+mfile['ans'][0][x][0][2],mfile['ans'][0][x][0][1]+mfile['ans'][0][x][0][3]))
self.canvas.im_crop2=ImageTk.PhotoImage(self.im_crop)
self.canvas.create_image(mfile['ans'][0][x][0][0],mfile['ans'][0][x][0][1], image=self.canvas.im_crop2)
#canvas.create_image(1000,1000,image=im_crop2)
#if(x>=0):
self.mylist.append(self.canvas.im_crop2)
#im_crop.show()
#canvas.iiiimg=ImageTk.PhotoImage(im_crop)
#canvas.create_image(150,150,image=canvas.im_crop2)
self.popup = tk.Menu(root, tearoff=0)
#self.popup.add_command(label="delete",command=lambda: self.dele(id))
self.popup.add_command(label="delete",
command=lambda: self.dele(id))
self.popup.add_command(label="add",command= lambda: root.bind("<Button-1>",self.addn))
root.bind("<Button-3>", self.do_popup)
self.canvas.delete(self.canvas_img)
def do_popup(self,event):
# display the popup menu
try:
self.popup.tk_popup(event.x_root, event.y_root, 0)
finally:
# make sure to release the grab (Tk 8.0a1 only)
self.popup.grab_release()
def on_button_1(self, event):
iid = self.canvas.find_enclosed(event.x-150, event.y-150, event.x + 150, event.y + 100)
#iid=canvas.find_closest(event.x, event.y)[0]
self.canvas.itemconfigure("DEL")
self.canvas.dtag("all","DEL")
self.canvas.itemconfigure(iid, tags=("DEL","DnD","token","drag"))
#canvas.unbind("<Button-1>")
def create_token(self, x, y, color):
"""Create a token at the given coordinate in the given color"""
self.canvas.create_rectangle(
x ,
y ,
x + 50,
y + 50,
outline=color,
fill=color,
tags=("token","DnD"),
)
def create_token1(self,x,y,color):
self.canvas.create_rectangle(
x ,
y ,
x + 25,
y + 25,
outline=color,
fill=color,
tags=("token","DnD"),
)
def drag_start(self, event):
"""Begining drag of an object"""
# record the item and its location
self._drag_data["item"] = self.canvas.find_closest(event.x, event.y)[0]
rect=self.canvas.bbox(self._drag_data["item"])
self.canvas.addtag_enclosed("drag",*rect)
print(rect)
self._drag_data["x"] = event.x
self._drag_data["y"] = event.y
def drag_stop(self, event):
"""End drag of an object"""
# reset the drag information
self._drag_data["item"] = None
self._drag_data["x"] = 0
self._drag_data["y"] = 0
self.canvas.dtag("drag","drag")
def drag(self, event):
"""Handle dragging of an object"""
# compute how much the mouse has moved
self.delta_x = event.x - self._drag_data["x"]
self.delta_y = event.y - self._drag_data["y"]
# move the object the appropriate amount
self.canvas.move("drag", self.delta_x, self.delta_y)
# record the new position
self._drag_data["x"] = event.x
self._drag_data["y"] = event.y
def dele(self,id):
#canvas.tag_bind(id,"<Button-1>")
self.canvas.delete("DEL")
def addn(self,event):
canvas.create_rectangle(event.x,event.y,event.x+25,event.y+25,fill='red',tags=("DnD","token"))
root.unbind("<Button-1>")
Example(root) #pack(fill="both", expand=True)
root.mainloop()
This is the Matlab code I am using for identifying objects

Resizing a Gtk.DrawingArea in PyGtk3 [duplicate]

This question already has an answer here:
Cannot reduce size of gtk window programatically
(1 answer)
Closed 3 years ago.
Please run the code below to get something like this:
Basically, I've set up a Gtk.DrawingArea, which is inside a Gtk.Viewport, which is also in a Gtk.Scrolledwindow. In said DrawingArea I draw an image, and with the 2 buttons you can scale said image.
# -*- encoding: utf-8 -*-
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from gi.repository import Gdk
from gi.repository import GdkPixbuf
class MyWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="DrawingTool")
self.set_default_size(800, 600)
# The ratio
self.ratio = 1.
# Image
filename = "image.jpg"
self.original_image = GdkPixbuf.Pixbuf.new_from_file(filename)
self.displayed_image = GdkPixbuf.Pixbuf.new_from_file(filename)
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6)
# Zoom buttons
self.button_zoom_in = Gtk.Button(label="Zoom-In")
self.button_zoom_out = Gtk.Button(label="Zoom-Out")
# |ScrolledWindow
# |-> Viewport
# |--> DrawingArea
scrolledwindow = Gtk.ScrolledWindow()
viewport = Gtk.Viewport()
self.drawing_area = Gtk.DrawingArea()
self.drawing_area.set_size_request(
self.displayed_image.get_width(), self.displayed_image.get_height())
self.drawing_area.set_events(Gdk.EventMask.ALL_EVENTS_MASK)
# Pack
viewport.add(self.drawing_area)
scrolledwindow.add(viewport)
box.pack_start(self.button_zoom_in, False, True, 0)
box.pack_start(self.button_zoom_out, False, True, 0)
box.pack_start(scrolledwindow, True, True, 0)
self.add(box)
# Connect
self.connect("destroy", Gtk.main_quit)
self.button_zoom_in.connect("clicked", self.on_button_zoom_in_clicked)
self.button_zoom_out.connect("clicked", self.on_button_zoom_out_clicked)
self.drawing_area.connect("enter-notify-event", self.on_drawing_area_mouse_enter)
self.drawing_area.connect("leave-notify-event", self.on_drawing_area_mouse_leave)
self.drawing_area.connect("motion-notify-event", self.on_drawing_area_mouse_motion)
self.drawing_area.connect("draw", self.on_drawing_area_draw)
self.show_all()
def on_button_zoom_in_clicked(self, widget):
self.ratio += 0.1
self.scale_image()
self.drawing_area.queue_draw()
def on_button_zoom_out_clicked(self, widget):
self.ratio -= 0.1
self.scale_image()
self.drawing_area.queue_draw()
def scale_image(self):
self.displayed_image = self.original_image.scale_simple(self.original_image.get_width() * self.ratio,
self.original_image.get_height() * self.ratio, 2)
def on_drawing_area_draw(self, drawable, cairo_context):
pixbuf = self.displayed_image
self.drawing_area.set_size_request(pixbuf.get_width(), pixbuf.get_height())
Gdk.cairo_set_source_pixbuf(cairo_context, pixbuf, 0, 0)
cairo_context.paint()
def on_drawing_area_mouse_enter(self, widget, event):
print("In - DrawingArea")
def on_drawing_area_mouse_leave(self, widget, event):
print("Out - DrawingArea")
def on_drawing_area_mouse_motion(self, widget, event):
(x, y) = int(event.x), int(event.y)
offset = ( (y*self.displayed_image.get_rowstride()) +
(x*self.displayed_image.get_n_channels()) )
pixel_intensity = self.displayed_image.get_pixels()[offset]
print("(" + str(x) + ", " + str(y) + ") = " + str(pixel_intensity))
MyWindow()
Gtk.main()
Run the application. I've also set up some callbacks so when you hover the mouse pointer over the image, you get to know:
i) When you enter the DrawingArea
ii) When you leave the DrawingArea
iii) The (x, y) at the DrawingArea and image pixel intensity
The problem I'm facing is, when you Zoom-out enough times, the image will look like this:
However, the 'mouse enter' and 'mouse leave' signals, aswell the 'mouse motion' one are sent like the DrawingArea still has the same size it was created with. Please hover the mouse over the image and outside the image but inside the DrawingArea to see the output in your terminal.
I would like the signals not to send themselves when hovering outside the image. This is, adapt the DrawingArea size to the displayed image size, if possible.
I've used gtk_window_resize() as it is mentioned here: Cannot reduce size of gtk window programatically
def on_drawing_area_draw(self, drawable, cairo_context):
pixbuf = self.displayed_image
# New line. Get DrawingArea's window and resize it.
drawable.get_window().resize(pixbuf.get_width(), pixbuf.get_height())
drawable.set_size_request(pixbuf.get_width(), pixbuf.get_height())
Gdk.cairo_set_source_pixbuf(cairo_context, pixbuf, 0, 0)
cairo_context.paint()

How to use matplotlib blitting to add matplot.patches to an matplotlib plot in wxPython?

I am making a plot using the matplotlib library and showing it in my wxPython GUI. I am plotting a massive amount of data points from a LIDAR instrument. The thing is, I would like to draw rectangles in this plot to indicate interesting areas. But when I draw a rectangle on the same axes as the plot, the whole plot gets replotted which takes lots of time. This is because of the self.canvas.draw(), a function which replots everything.
The code gets displayed as follows in the GUI:
Printscreen of GUI
Here is a minimal working example of the problem. U can draw rectangles by holding the right mouse button. Once you plot the NetCDF data using the button on the left, the drawing of rectangles gets really slow. I tried some things with blitting using the examples provided by ImportanceOfBeingErnest but after a lot of tries, I still have not managed to get it to work.
To make the minimal working example work, you will have to specify the path to the NetCDF file under the plot_Data() function. I provided the NetCDF file which to download here:
Download NetCDF file
How can I blit the self.square to the self.canvas in the onselect function?
import netCDF4 as nc
import matplotlib
matplotlib.use('WXAgg')
from matplotlib.figure import Figure
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
import matplotlib.pyplot as plt
import matplotlib.colors as colors
import matplotlib.widgets
import time
import wx
class rightPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent, style=wx.SUNKEN_BORDER)
self.initiate_Matplotlib_Plot_Canvas()
self.add_Matplotlib_Widgets()
def initiate_Matplotlib_Plot_Canvas(self):
self.figure = Figure()
self.axes = self.figure.add_subplot(111)
self.colorbar = None
self.canvas = FigureCanvas(self, -1, self.figure)
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.sizer.Add(self.canvas, proportion=1, flag=wx.ALL | wx.GROW)
self.SetSizer(self.sizer)
self.Fit()
self.canvas.draw()
def add_Matplotlib_Widgets(self):
self.rectangleSelector = matplotlib.widgets.RectangleSelector(self.axes, self.onselect,
drawtype="box", useblit=True,
button=[3], interactive=False
)
def onselect(self, eclick, erelease):
tstart = time.time()
x1, y1 = eclick.xdata, eclick.ydata
x2, y2 = erelease.xdata, erelease.ydata
height = y2-y1
width = x2-x1
self.square = matplotlib.patches.Rectangle((x1,y1), width,
height, angle=0.0, edgecolor='red',
fill=False
#blit=True gives Unknown property blit
)
self.axes.add_patch(self.square)
self.canvas.draw()
# =============================================================================
# self.background = self.canvas.copy_from_bbox(self.axes.bbox)
#
#
# self.canvas.restore_region(self.background)
#
# self.axes.draw_artist(self.square)
#
# self.canvas.blit(self.axes.bbox)
# =============================================================================
tend = time.time()
print("Took " + str(tend-tstart) + " sec")
def plot_Data(self):
"""This function gets called by the leftPanel onUpdatePlot. This updates
the plot to the set variables from the widgets"""
path = "C:\\Users\\TEST_DATA\\cesar_uvlidar_backscatter_la1_t30s_v1.0_20100501.nc"
nc_data = self.NetCDF_READ(path)
print("plotting......")
vmin_value = 10**2
vmax_value = 10**-5
combo_value = nc_data['perp_beta']
self.axes.clear()
plot_object = self.axes.pcolormesh(combo_value.T, cmap='rainbow',
norm=colors.LogNorm(vmin=vmin_value, vmax=vmax_value))
self.axes.set_title("Insert title here")
if self.colorbar is None:
self.colorbar = self.figure.colorbar(plot_object)
else:
self.colorbar.update_normal(plot_object)
self.colorbar.update_normal(plot_object)
print('canvas draw..............')
self.canvas.draw()
print("plotting succesfull")
###############################################################################
###############################################################################
"""BELOW HERE IS JUST DATA MANAGEMENT AND FRAME/PANEL INIT"""
###############################################################################
###############################################################################
def NetCDF_READ(self, path):
in_nc = nc.Dataset(path)
list_of_keys = in_nc.variables.keys()
nc_data = {} #Create an empty dictionary to store NetCDF variables
for item in list_of_keys:
variable_shape = in_nc.variables[item].shape
variable_dimensions = len(variable_shape)
if variable_dimensions > 1:
nc_data[item] = in_nc.variables[item][...] #Adding netCDF variables to dictonary
return nc_data
class leftPanel(wx.Panel):
def __init__(self, parent, mainPanel):
wx.Panel.__init__(self, parent)
button = wx.Button(self, -1, label="PRESS TO PLOT")
button.Bind(wx.EVT_BUTTON, self.onButton)
self.mainPanel = mainPanel
def onButton(self, event):
self.mainPanel.rightPanel.plot_Data()
class MainPanel(wx.Panel):
def __init__(self, parent):
"""Initializing the mainPanel. This class is called by the frame."""
wx.Panel.__init__(self, parent)
self.SetBackgroundColour('red')
"""Acquire the width and height of the monitor"""
width, height = wx.GetDisplaySize()
"""Split mainpanel into two sections"""
self.vSplitter = wx.SplitterWindow(self, size=(width,(height-100)))
self.leftPanel = leftPanel(self.vSplitter, self)
self.rightPanel = rightPanel(self.vSplitter)
self.vSplitter.SplitVertically(self.leftPanel, self.rightPanel,102)
class UV_Lidar(wx.Frame):
"""Uppermost class. This class contains everything and calls everything.
It is the container around the mainClass, which on its turn is the container around
the leftPanel class and the rightPanel class. This class generates the menubar, menu items,
toolbar and toolbar items"""
def __init__(self, parent, id):
print("UV-lidar> Initializing GUI...")
wx.Frame.__init__(self, parent, id, 'UV-lidar application')
self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)
self.mainPanel = MainPanel(self)
def OnCloseWindow(self, event):
self.Destroy()
if __name__ == '__main__':
app = wx.App()
frame = UV_Lidar(parent=None, id=-1)
frame.Show()
print("UV-lidar> ")
print("UV-lidar> Initializing GUI OK")
app.MainLoop()
I have found the solution myself:
In order to blit a matplotlib patch, you will have to first add the patch to the axes. Then draw the patch on the axes and then you can blit the patch to the canvas.
square = matplotlib.patches.Rectangle((x1,y1), width,
height, angle=0.0, edgecolor='red',
fill=False)
self.axes.add_patch(square)
self.axes.draw_artist(square)
self.canvas.blit(self.axes.bbox)
If you do not want to use self.canvas.draw but still use matplotlib widgets which have useblit=True, you can save the plot as a background image: self.background = self.canvas.copy_from_bbox(self.axes.bbox) and restore it later by using: self.canvas.restore_region(self.background). This is a lot faster than drawing everything over!
When using the matplotlib's RectangleSelector widget with useblit=True, it will create another background instance variable, which interferes with your own background instance variable. To fix this problem, you will have to set the background instance variable of the RectangleSelector widget to be equal to your own background instance variable. However, this should only be done after the RectangleSelector widget is no longer active. Otherwise it will save some of the drawing animation to the background. So once the RectangleSelector has become inactive, you can update its background using: self.rectangleSelector.background = self.background
The code that had to be edited is given below. wx.CallLater(0, lambda: self.tbd(square)) is used so that the background instance variable of the RectangleSelector widget is updated only when it has become inactive.
def add_Matplotlib_Widgets(self):
"""Calling these instances creates another self.background in memory. Because the widget classes
restores their self-made background after the widget closes it interferes with the restoring of
our leftPanel self.background. In order to compesate for this problem, all background instances
should be equal to eachother. They are made equal in the update_All_Background_Instances(self)
function"""
"""Creating a widget that serves as the selector to draw a square on the plot"""
self.rectangleSelector = matplotlib.widgets.RectangleSelector(self.axes, self.onselect,
drawtype="box", useblit=True,
button=[3], interactive=False
)
def onselect(self, eclick, erelease):
self.tstart = time.time()
x1, y1 = eclick.xdata, eclick.ydata
x2, y2 = erelease.xdata, erelease.ydata
height = y2-y1
width = x2-x1
square = matplotlib.patches.Rectangle((x1,y1), width,
height, angle=0.0, edgecolor='red',
fill=False
#blit=True gives Unknown property blit
)
"""In order to keep the right background and not save any rectangle drawing animations
on the background, the RectangleSelector widget has to be closed first before saving
or restoring the background"""
wx.CallLater(0, lambda: self.tbd(square))
def tbd(self, square):
"""leftPanel background is restored"""
self.canvas.restore_region(self.background)
self.axes.add_patch(square)
self.axes.draw_artist(square)
self.canvas.blit(self.axes.bbox)
"""leftPanel background is updated"""
self.background = self.canvas.copy_from_bbox(self.axes.bbox)
"""Setting all backgrounds equal to the leftPanel self.background"""
self.update_All_Background_Instances()
print('Took '+ str(time.time()-self.tstart) + ' s')
def update_All_Background_Instances(self):
"""This function sets all of the background instance variables equal
to the lefPanel self.background instance variable"""
self.rectangleSelector.background = self.background

Python Tkinter fixing text location on scrolling screen

Trying to scroll a graphic on the screen while keeping the text in the same place. The text shows where the mouse is located. I've thought about the idea of scrolling the text in the opposite direction of the screen scroll but I'm not sure if there is an easier way of doing it and if I have to scroll the text the opposite way I'm not sure how to set the initial text pointer so I can come back and recall/reset it. I'm only wanting it to show the position on not as will be doing other things in the near future.
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
self.keys = dict.fromkeys(('Left', 'Right', 'Up', 'Down'))
self.canvas = tk.Canvas(self, background="bisque", width=700, height=700)
self.canvas.pack(fill="both", expand=True)
self.canvas.configure(scrollregion=(-1000, -1000, 1000, 1000))
self.looper() # start the looping
def keypress(self,event):
if event.keysym in self.keys:
# event type 2 is key down, type 3 is key up
self.keys[event.keysym] = event.type == '2'
def looper(self):
if self.keys['Up']:
self.canvas.yview_scroll(-2,'units')
if self.keys['Down']:
self.canvas.yview_scroll(2,'units')
if self.keys['Left']:
self.canvas.xview_scroll(-2,'units')
if self.keys['Right']:
self.canvas.xview_scroll(2,'units')
self.after(5, self.looper) # set the refresh rate here ... ie 20 milliseconds. Smaller number means faster scrolling
def on_press(self, event):
self.last_x = event.x
self.last_y = event.y
self.startx, self.starty = self.canvas.canvasx(event.x),self.canvas.canvasy(event.y)
def on_motion(self, event):
self.canvas.delete("sx")
self.startx, self.starty = self.canvas.canvasx(event.x),self.canvas.canvasy(event.y)
px = round(-(((1000-self.startx) * .00015) + 69.3),5)
py = round((45.05-((1000+self.starty) * .00015)),5)
self.canvas.create_text(400,-400, text = str(px), fill = "black", tags = "sx")
self.canvas.create_text(475,-400, text = str(py), fill = "black", tags = "sx")
def button_motion(self,event):
delta_x = event.x - self.last_x
delta_y = event.y - self.last_y
self.last_x = event.x
self.last_y = event.y
self.canvas.xview_scroll(-delta_x, "units")
self.canvas.yview_scroll(-delta_y, "units")
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()
The simplest solution is to use a widget that is not embedded in the canvas for the text so that it won't scroll when the canvas scrolls. Create a Label with it's parent being the canvas, and then use place to superimpose the label on top of the canvas.

Resources