Python, Tkinter: How to get coordinates on scrollable canvas - python

I have a Tkinter canvas with a scrollbar, and some items that when I click them, it's supposed to return the coordinates. (Using Python.)
This works fine with the objects that's initially visible in the window. When I scroll down, however, and the items further down on the canvas come into view, I don't get their canvas coordinates when clicking, but the window coordinates.
I can't find info on how to get the absolute coordinates, so I'm wondering if anyone here knows how to do it?
Thanks.

Check out the documentation for the canvas widget here.
To convert from window coordinates to canvas coordinates, use the canvasx and canvasy methods.
Here is an example callback function which converts the window's x and y coordinates and prints the item closest to that position via the find_closest() method.
def callback(event):
canvas = event.widget
x = canvas.canvasx(event.x)
y = canvas.canvasy(event.y)
print canvas.find_closest(x, y)

Related

Is There a Way to Always have a Place Widget Relative to Another Widget(Such as Pack)

So, I'm using the place method to have a widget overlap other widgets, but its position is relative (with winfo) to a widget that uses pack. When the parent frame is resized, the pack position will change, but the place position will not.
This is my code:
from tkinter import *
root = Tk()
root.geometry("200x300")
search = Entry(root)
search.pack()
search.update()
x = search.winfo_x()
y = search.winfo_y()
width = search.winfo_width()
height = search.winfo_height()
frame = LabelFrame(root, width=width, height=200)
frame.place(x=x, y=y+height)
root.mainloop()
The LabelFrame stays in its x and y position when the window is resized. The Entry widget will be used as a search bar and I want autocompletion under it. There will be widgets under the entry widget and the autocompletion will only appear when you are typing (That's not what I'm looking for though. Its just more exposition if you need it). So, is there a way to have the place widget always be relative to the pack widget. If you have any answers, thank you:)
If your goal is to put one widget relative to another, place lets you do that. It's great for things like tooltips or other transient widgets that don't otherwise fit into a normal layout.
The easiest way to do that is to make the widget a child of the controlling widget. For example, for your frame to be placed relative to the search box you can make it a child of the search box. If it's inconvenient to do that, you can use the in_ parameter to tell place which widget is the other widget.
For example, to place your labelframe immediately below the search box and with the same width as the search box you might do it something like this:
frame.place(
in_=search,
bordermode="outside",
anchor="nw",
relx=0,
rely=1.0,
y=5,
relwidth=1.0
)
This is what the options mean:
in_=search: place the frame relative to the search box
bordermode="outside": relative measurements are from the outside of the border (default is "inside")
anchor="nw": place the widget so that the northwest corner of the frame is at the computed coordinate
relx=0: place the anchor point 0% from the left edge of the search box
rely=1.0: place the frame at 100% of the height of the search box
y=5: add 5 pixels to the computed position so it floats just a little below the window
relwidth=1.0: make the width of the frame 100% the width of the search box.
Obviously you don't have to use y=5, I just added it to illustrate the additive behavior of using rely and y.

Tkinter - Getting x,y coordinates of Label object

I have defined a Label object as:
panel = Label(image_frame, image=self.img, cursor="cross")
Now, I'd like to do draw polygon on top of this, and I have created a function called draw() that binds to a canvas and will allow me to draw a polygon on top of it. So, I know my draw() command works.
However, I need to do this on top of the panel that I've defined as a Label.
The biggest problem I'm having is this line in my draw() command
if event.widget.canvasx(event.x)-2 < orig_x < event.widget.canvasx(event.x)+2 and event.widget.canvasy(event.y)-2 < orig_y < event.widget.canvasy(event.y)+2 :
I'm producing the following error:
AttributeError: 'Label' object has no attribute 'canvasx'
Is there an analog for canvasx for Label object? How can I bypass this without changing Label? Or is changing Label to canvas my only option?
The only other thing I can think of is to have a transparent canvas behind the Label, but then on-resize, things get messy.
Is there an analog for canvasx for Label object? How can I bypass this without changing Label? Or is changing Label to canvas my only option?
No, because there is no need. canvasx exists because the canvas can be scrolled in any direction, and you need to be able to convert from widget coordinates to the inner canvas coordinates.
In the case of a label, if you click at the 0,0 coordinate of a label, that will always be the upper left corner of the label, because the interior of the label cannot be scrolled.
Note: it is not possible to embed a label widget (or any other widget) on a canvas, and then draw on top of the label. The official canvas documentation says this about that:
Note: due to restrictions in the ways that windows are managed, it is not possible to draw other graphical items (such as lines and images) on top of window items. A window item always obscures any graphics that overlap it, regardless of their order in the display list.

Tkinter Canvas invalidation issue (object clipped while moved)

I'm pretty new to Python and I'm currently playing a bit with Tkinter which looks amazingly simple. I've tried to implement a simple drag and drop effect using the following code (right mouse button creates a circle, left mouse button allows dragging) :
from tkinter import *
class Point:
def __init__(self, ref, x, y):
self.ref = ref
self.x = x
self.y = y
points = []
selected = None
def OnSelect(event):
global selected
for p in points:
if event.x>=(p.x-10) and event.y>=(p.y-10) and event.x<(p.x+10) and event.y<(p.y+10):
selected = p
break
def OnMMove(event):
if selected is not None:
selected.x = event.x
selected.y = event.y
canvas.coords(selected.ref, event.x-10, event.y-10, event.x+10, event.y+10)
def OnStopDrag(event):
global selected
selected = None
def OnCreate(event):
point = canvas.create_oval(event.x-10, event.y-10, event.x+10, event.y+10, fill="black")
points.append(Point(point, event.x, event.y))
window = Tk()
window.wm_title("Python")
canvas = Canvas(window, width=800, height=600, background='white')
canvas.bind("<Button-1>", OnSelect)
canvas.bind("<B1-Motion>", OnMMove)
canvas.bind("<ButtonRelease-1>", OnStopDrag)
canvas.bind("<Button-3>", OnCreate)
canvas.pack(fill=BOTH, expand=YES)
window.mainloop()
As can be seen in this code, I'm using canvas.cords to move the dragged object around. Everything works fine when the mouse cursor is slowly moved while dragging, however when the mouse cursor is moved rapidly, the dragged circle seems to be partialy clipped in a rectangle while moved as seen on this picture (the whole circle is correctly drawn entirely again when dragging stops or slows down) :
I've already encountered a similar issue whend using GDI in a Win32 C application, when calling screen invalidation to repaint the window client area on the sole area covered by the initial position of the circle being currently dragged.
And indeed, when the window created in my example code is placed above a window which is constantly and entirely being redrawn, like a video game window, the cropping effect while dragging elements is not seen and the whole circle is correctly being redrawn as it is dragged.
Is there a way to solve this issue, like a canvas setting making window invalidation being called on a wider or the whole client area ? I would like to stick with Tkinter so I'm not really interested in switching to another GUI API/framework. This code has been tested on Windows 10.
This answer may confuse you (it does me). But a solution is to configure the cursor within OnMMove. Here is the excerpt that worked on Windows for me.
def OnMMove(event):
if selected is not None:
canvas.configure(cursor='arrow')
selected.x = event.x
selected.y = event.y
canvas.coords(selected.ref, event.x-10, event.y-10, event.x+10, event.y+10)

how can I use the Button widget in python to place buttons in specific places on the screen?

thanks for helping...
I would like to place a button using tkinter's Button function.
I am not exactly sure how to place a button though. I've actually put a button on the screen, using the pack() method, but am unsure of how to have more control over were it goes. here is my code
main = Tk()
canvas = Canvas(main, width = 500, height = 500)
canvas.pack()
btn1 = Button (tk, text = "speak your name and click here!", command = moo_man)
btn1.pack()
btn1.place(bordermode = OUTSIDE, width = 270, height = 25)
say for example, I wanted to put the button in the top right corner.
Thanks, all help is appreciated!!
place has options for relative placement. You can combine absolute and relative positioning, so you want the relative X coordinate to be 1.0 (ie: all the way to the right) and the absolute Y coordinate to be zero. Also, you want the coordinate to represent the upper-right corner of the button so that it is in the upper right of the parent window:
bt1.place(relx=1.0, y=0, anchor="ne")
For more information see http://effbot.org/tkinterbook/place.htm
On a side note: you should consider using pack or place in almost all situations. place is useful for some edge cases, but for most situations it's the least useful geometry manager. pack and grid make your UI much easier to create and modify. They are worth taking time to learn.

Get global mouse position and use in python program?

As part of a larger project, I am trying to create a snapshot tool that works similar to the Mac OS X snapshot. It should take a first click, a second click, and return an image of the area created by the square.
I have some python functions that take an first point (x, y) and a second point (x, y) and create a snapshot of the square that those points create on the screenshot. The missing piece is getting the mouse locations of the initial click and second click, then passing that data to the python program to create the snapshot.
In other words, the flow of the program should be:
first click (save x, y)
second click (save x2, y2)
run snapshot.py using the saved clicked data to return the screenshot
I've only found solutions that can return the position of the pointer within a frame. If it helps, I'm using "import gtk" and "from Xlib import display"
edit: I have tried to use Tkinter to make an invisible frame that covers the whole screen. The idea was to use that invisible frame to get the exact coordinates of two mouse clicks, and then the invisible frame would disappear, pass the coordinates on to the screenshot function, and it would be done. However, the code I've been writing doesn't keep the frame transparent.
edit 2: This code can create a window, make it transparent, size it to the screen, then return the mouse coordinates on that window. I can use this to simply return the mouse coordinates on two clicks, then remove the window and send those coordinates to the snapshot code. When I run the below code line-by-line in the python shell, it works perfectly. However, whenever I run the code as a whole, it seems to skip the part where it makes the window transparent. Even if I copy and paste a block of code that includes the 'attributes("-alpha", 0.1)' into the python shell, it ignores that line.
from Tkinter import *
root = Tk()
root.attributes('-alpha', 0.1)
maxW = root.winfo_screenwidth()
maxH = root.winfo_screenheight()
root.geometry("{0}x{1}+0+0".format(maxW, maxH))
def callback(event):
print "clicked at: ", event.x, "and: ", event.y
root.bind("<Button-1>", callback)
def Exit(event):
root.destroy()
root.bind("<Escape>", Exit)
# root.overrideredirect(True)
root.mainloop()
I am open to using any c or c++ code, or any language's code, to return the coordinates of the mouse on a click. This guy wrote some code to actually make the computer click at given points, which may be on the same track as my problem.
It's just a indentation problem - you bound the callback in the callback by mistake - try this instead:
root.geometry("{0}x{1}+0+0".format(maxW, maxH))
def callback(event):
print "clicked at: ", event.x, "and: ", event.y
root.bind("<Button-1>", callback)
EDIT
Ok, here's a theory - maybe when you run it from the command line, it takes longer for the root window to appear, for some reason, so you set the alpha before it exists, and the alpha option gets ignored. Give this a try:
root.wait_visibility(root)
root.attributes('-alpha', 0.1)

Categories