Mouse Position Python Tkinter - python

Is there a way to get the position of the mouse and set it as a var?

You could set up a callback to react to <Motion> events:
import Tkinter as tk
root = tk.Tk()
def motion(event):
x, y = event.x, event.y
print('{}, {}'.format(x, y))
root.bind('<Motion>', motion)
root.mainloop()
I'm not sure what kind of variable you want. Above, I set local variables x and y to the mouse coordinates.
If you make motion a class method, then you could set instance attributes self.x and self.y to the mouse coordinates, which could then be accessible from other class methods.

At any point in time you can use the method winfo_pointerx and winfo_pointery to get the x,y coordinates relative to the root window. To convert that to absolute screen coordinates you can get the winfo_pointerx or winfo_pointery, and from that subtract the respective winfo_rootx or winfo_rooty
For example:
root = tk.Tk()
...
x = root.winfo_pointerx()
y = root.winfo_pointery()
abs_coord_x = root.winfo_pointerx() - root.winfo_rootx()
abs_coord_y = root.winfo_pointery() - root.winfo_rooty()

Personally, I prefer to use pyautogui, even in combination with Tkinter. It is not limited to Tkinter app, but works on the whole screen, even on dual screen configuration.
import pyautogui
x, y = pyautogui.position()
In case you want to save various positions, add an on-click event.
I know original question is about Tkinter.

I would like to improve Bryan's answer, as that only works if you have 1 monitor, but if you have multiple monitors, it will always use your coordinates relative to your main monitor. in order to find it relative to both monitors, and get the accurate position, then use vroot, instead of root, like this
root = tk.Tk()
...
x = root.winfo_pointerx()
y = root.winfo_pointery()
abs_coord_x = root.winfo_pointerx() - root.winfo_vrootx()
abs_coord_y = root.winfo_pointery() - root.winfo_vrooty()

Related

Mouse Coordinates in Graph Window

I'm trying to figure out how to find the mouse coordinates when clicking on the graph window a few times.
So far I've tried
mx ,my = win.mouseX(), win.mouseY() and it tells me that the Nonetype is not callable. I've seen other posts involving tkinter, but I am not using that library even though I see that it's easier. Some more example code is as follows:
from graphics import *
win = GraphWin("test", 300, 300)
for i in range(3):
win.getMouse()
mx, my = win.mouseX(), win.mouseY()
print(mx,my)
I want the above code to have the user click on the window and print the regarding mouse coordinates. Eventually I want to store these coordinates, but I think I can figure that out.
win.getMouse() returns a Point which you can get coordinates from like this:
from graphics import *
win = GraphWin("test", 300, 300)
for i in range(3):
point = win.getMouse()
mx, my = point.getX(), point.getY()
print(mx,my)

How to capture image (screenshot) of tkinter window on MacOS

I have created a GUI app in Python tkinter for analyzing data in my laboratory. There are a number of buttons, figures, and canvas widgets. It would be helpful to take a screenshot of the entire window ('root') using just a single button that saves the filename appropriately. Example using Mac's built-in "screenshot" app here.
Related questions here, here, and here, but none worked successfully. The final link was almost successful, however the image that is saved is my computer's desktop background. My computer is a Mac, MacOS Monterey 12.0.1.
'root' is the tkinter window because
root = tk.Tk()
appears at the beginning of the script, analogous to 'window' in the example here. I'm using PIL.ImageGrab in the code sample below.
This is the current code, which takes an unhelpful screenshot of my desktop background,
def screenshot():
# retrieve the time string to use as a filename
file_name = root.time_string[-6:]
full_file_name = file_name + '_summary' + '.png'
x = root.winfo_rootx() + root.winfo_x()
y = root.winfo_rooty() + root.winfo_y()
x1 = x + root.winfo_width()
y1 = y + root.winfo_height()
ImageGrab.grab().crop((x, y, x1, y1)).save(full_file_name)
I create the button like so:
screenshot_btn = tk.Button(root, text='Screenshot', command=lambda: screenshot(), font=('Verdana', 24), state=DISABLED)
And I place the button in 'root' like this:
screenshot_btn.grid(row=11, column=3)
[This is my first post at stackoverflow. I apologize in advance if I did not follow all the guidelines perfectly on the first try. Thanks for your patience.]
First, I didn't have an issue with the grab showing my desktop, but it was showing an improperly cropped image.
I have found a hacky solution. The issues appears to be with the resolution. So the dimensions need some scaling.
What I did was get the output from ImageGrab.grab().save(full_file_name) ( no cropping ) and measure the size of the desired image area in pixels. These dimensions will be called x_pixels and y_pixels.
Then I measured that same area on the actual window in screen units. I did this by bringing up the mac screenshot tool which shows the dimensions of an area. I then call these dimensions x_screen and y_screen. Then I modified your screenshot function as follows.
def screenshot():
# retrieve the time string to use as a filename
file_name = root.time_string[-6:]
full_file_name = file_name + '_summary' + '.png'
x = root.winfo_rootx()
y = root.winfo_rooty()
x1 = x + root.winfo_width()
y1 = y + root.winfo_height()
x_pixels = 337
y_pixels = 79
x_screen = 171
y_screen = 41
x = x*(x_pixels/x_screen)
y = y*(y_pixels/y_screen)
x1 = x1*(x_pixels/x_screen)
y1 = y1*(y_pixels/y_screen)
ImageGrab.grab().crop((x, y, x1, y1)).save(full_file_name)
Notice I also removed +root.winfo_x() and +root.winfo_y()
The result is shown below. It's not perfect, but I believe if I more carefully measured the pixels at the bounds of the screenshot and window the scaling would be improved.
Do you by chance have a "Retina" monitor? ImageGrab fails to take into account the 144 DPI image it collects from the Mac's screencapture command. You can compensate for this by multiplying all the x, y, x1, y1 values by 2 (but you may still miss a bit if you need to account for the Mac window titlebar).
Alternatively, use the pyscreenshot package (or steal its code...). It uses the "-R" option of screencapture to get the proper bounds for the image. Works on extended monitors like mine where the x co-ord is < 0 ('cause it's to the left of the main monitor).

(Instantiating an array of Buttons, but only one works

I'm trying to create a GUI for a virtual board for the game Go. There should be an nxn grid of tiles where a player can place a stone, either black or white. Clicking on a tile will make it change from tan(the default) to black, click again to white, and click a third time to go back to tan. Player one can click once on a spot to place a stone there, and player two can click twice (you need to remove stones later, so three clicks resets it). I created a tile object and then used a nested for loop to instantiate 9 by 9 of them. Unfortunately, running the code only seems to produce 1 functional tile, not 81. This code should work on any python machine (I'm using Python 3.4), so you can try to run it and see for yourself. Can anyone point out the reason the loop is only running once?
from tkinter import *
window = Tk()
n = 9
"""
A tile is a point on a game board where black or white pieces can be placed. If there are no pieces, it remains tan.
The basic feature is the "core" field which is a tkinter button. when the color is changed, the button is configured to represent this.
"""
class tile(object):
core = Button(window, height = 2, width = 3, bg = "#F4C364")
def __init__(self):
pass
"""the cycle function makes the tile object actually change color, going between three options: black, white, or tan."""
def cycle(self):
color = self.core.cget("bg")
if(color == "#F4C364"): #tan, the inital value.
self.core.config(bg = "#111111")#white.
elif (color == "#111111"):
self.core.config(bg = "#DDDDDD")#black.
else:
self.core.config(bg = "#F4C364")#back to tan.
board = [] #create overall array
for x in range(n):
board.append([])#add subarrays inside it
for y in range(n):
board[x].append(tile())#add a tile n times in each of the n subarrays
T = board[x][y] #for clarity, T means tile
T.core.config(command = lambda: T.cycle()) #I do this now because cycle hadn't been defined yet when I created the "core" field
T.core.grid(row = x, column = y) #put them into tkinter.
window.mainloop()
As mhawke points out in his answer you need to make the core an instance variable, so that each Tile gets its own core.
And as I mention in my comment above, you also need to fix the Button's command callback function. The code you use in your question will call the .cycle() method of the current value of T, which happens to be the last tile created. So no matter where you click only the last tile changes color. One way to fix that is to pass the current tile as a default argument of the lambda function when you create it. But because you are using OOP to create your Tile there's a better way, which you can see below.
I've made a few modifications to your code.
Although many Tkinter examples use from tkinter import * it's not a good practice. When you do from some_module import * it brings all of the names from some_module into the current module (your script), which means you could accidentally override those names with your own names. Even worse, if you do import * with multiple modules each new module's names can clash with the previous module's names, and you have no way of knowing that's happened until you start getting mysterious bugs. Using import tkinter as tk means you need to do a little more typing, but it makes the resulting program less bug-prone and easier to read.
I've modified the __init__ method so that it is called with the window and the (x, y) location of the tile (it's customary to use x for the horizontal coordinate and y for the vertical coordinate). Each Tile object now keeps track of its current state, where 0=empty, 1=black, 2=white. This makes it easier to update the colors. And because we've passed in the window and (x, y) we can use that info to add the tile to the grid. The tile also remembers the location (in self.location), which may come in handy.
I've modified the cycle method so that it updates both the background color and the activebackground of the tile. So when the mouse hovers over the tile it changes to a color that's (roughly) halfway between its current color and the color it will turn if you click it. IMO, this is nicer than the tile always turning pale grey when the mouse hovers over it.
I've also optimized the code that creates all the tiles and stores them in the board list of lists.
import tkinter as tk
colors = (
#background, #activebackground
("#F4C364", "#826232"), #tan
("#111111", "#777777"), #black
("#DDDDDD", "#E8C8A8"), #white
)
class Tile(object):
""" A tile is a point on a game board where black or white pieces can be placed.
If there are no pieces, it remains tan.
The basic feature is the "core" field which is a tkinter button.
when the color is changed, the button is configured to represent this.
"""
def __init__(self, win, x, y):
#States: 0=empty, 1=black, 2=white
self.state = 0
bg, abg = colors[self.state]
self.core = tk.Button(win, height=2, width=3,
bg=bg, activebackground=abg,
command=self.cycle)
self.core.grid(row=y, column=x)
#self.location = x, y
def cycle(self):
""" the cycle function makes the tile object actually change color,
going between three options: black, white, or tan.
"""
#cycle to the next state. 0 -> 1 -> 2 -> 0
self.state = (self.state + 1) % 3
bg, abg = colors[self.state]
self.core.config(bg=bg, activebackground=abg)
#print(self.location)
window = tk.Tk()
n = 9
board = []
for y in range(n):
row = [Tile(window, x, y) for x in range(n)]
board.append(row)
window.mainloop()
The problem is that core is a class variable which is created once and shared by all instances of class tile. It should be an instance variable for each tile instance.
Move core = Button(window, height = 2, width = 3, bg = "#F4C364") into tile.__init__() like this:
class Tile(object):
def __init__(self):
self.core = Button(window, height = 2, width = 3, bg = "#F4C364")
The root of the problem is that core is shared by all instances of the class by virtue of how you've defined it. You need to move creation of the button into the initializer.
I also suggest moving the configuration of the command into the button itself. The caller shouldn't need (nor care) how the button works internally. Personally I'd have the tile inherit from Button, but if you favor composition over inheritance I'll stick with that.
Example:
class tile(object):
def __init__(self):
self.core = Button(window, height = 2, width = 3, bg = "#F4C364"
command=self.cycle)

Python get mouse x, y position on click

Coming from IDL, I find it quite hard in python to get the x-y position of the mouse on a single left click using a method that is not an overkill as in tkinter. Does anyone know about a python package that contains a method simply returning x-y when the mouse is clicked (similar to the cursor method in IDL)?
There are a number of libraries you could use. Here are two third party ones:
Using PyAutoGui
A powerful GUI automation library allows you to get screen size, control the mouse, keyboard and more.
To get the position you just need to use the position() function. Here is an example:
>>>import pyautogui
>>>pyautogui.position()
(1358, 146)
>>>
Where 1358 is the X position and 146 is the Y position.
Relavent link to the documentation
Using Pynput
Another (more minimalistic) library is Pynput:
>>> from pynput.mouse import Controller
>>> mouse = Controller()
>>> mouse.position
(1182, 153)
>>>
Where 1182 is the X position and 153 is the second.
Documentation
This library is quite easy to learn, does not require dependencies, making this library ideal for small tasks like this (where PyAutoGui would be an overkill). Once again though, it does not provide so many features though.
Windows Specific:
For platform dependant, but default library options (though you may still consider them overkills) can be found here: Getting cursor position in Python.
Using PyMouse:
>>> import pymouse
>>> mouse = pymouse.PyMouse()
>>> mouse.position()
(231L, 479L)
As an example, for plot or images, it is possible to use the matplotlib tool called ginput.
At every click of the mouse the [x,y] coordinates of the selected point are stored in a variable.
# show image
fig, ax=plt.subplots()
ax.imshow(img)
# select point
yroi = plt.ginput(0,0)
using ginput(0,0) you can select any points on the plot or image.
here the link for the ginput documentation
https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.pyplot.ginput.html
I made this the other day.
It a function to get color or pos on right click / left click:
#Add Any helpfull stuff in functions here for later use
def GetMouseInfos(WhatToGet="leaving emety will get you x and y", GetXOnly=False, GetYOnly=False, GetColor=False, Key='Right', OverrideKey=False):#gets color of whats under Key cursor on right click
try:
import win32api
except ModuleNotFoundError:
print("win32api not found, to install do pip install pywin32")
try:
import time
except ModuleNotFoundError:
print("time not found, to install do pip install time?")
try:
import pyautogui
except ModuleNotFoundError:
print("py auto gui not found, to install do pip install pyautogui")
#--------------------------------------------------------------
#above checks if needed modules are installed if not tells user
#code below is to get all varibles needed
#---------------------------------------------------------------
print(WhatToGet)
if OverrideKey:
Key_To_click = Key
if Key == 'Left':
Key_To_click = 0x01
if Key == 'Right':
Key_To_click = 0x02
if Key == 'Wheel':
Key_To_click = 0x04
state_left = win32api.GetKeyState(Key_To_click) # Left button up = 0 or 1. Button down = -127 or -128
IsTrue = True
while IsTrue:
a = win32api.GetKeyState(Key_To_click)
if a != state_left: # Button state changed
state_left = a
if a < 0:
global Xpos, Ypos
Xpos, Ypos = win32api.GetCursorPos()
x, y = pyautogui.position()
pixelColor = pyautogui.screenshot().getpixel((x, y))
else:
posnowX, posnowY = win32api.GetCursorPos()
win32api.SetCursorPos((posnowX, posnowY))
IsTrue = False#remove this for it to keep giving coords on click without it just quitting after 1 click
time.sleep(0.001)
#--------------------------------------------------------------------
#The Code above is the code to get all varibles and code below is for the user to get what he wants
#--------------------------------------------------------------------
if GetXOnly: #Checks if we should get Only X (def options) the command to do this would be GetKeyInfos("Click To get X ONLY", True)
if GetYOnly:
return(Xpos , Ypos)
if GetColor:
return(Xpos, pixelColor)
return(Xpos)
if GetYOnly: #Checks if we should get Only Y (def options) the command to do this would be GetKeyInfos("Click To get X ONLY",False, True)
if GetXOnly:
return(Xpos , Ypos)
if GetColor:
return(Ypos, pixelColor)
return(Ypos)
if GetColor:
return(pixelColor) #Checks
return(Xpos, Ypos)
# getKeyinfos("Anything here without any other guidelines will give u x and y only on right click")
Use pygame
import pygame
mouse_pos = pygame.mouse.get_pos()
This returns the x and y position of the mouse.
See this website: https://www.pygame.org/docs/ref/mouse.html#pygame.mouse.set_pos
Here is an example for canvas with tkinter:
def callback(event):
print("clicked at: ", event.x, event.y)
canvas.bind("<Button-1>", callback)
For turtle :
def get_mouse_click_coor(x, y):
print(x, y)
turtle.onscreenclick(get_mouse_click_coor)
Capture the coordinates (x,y) of the mouse, when clicking with the left button, without using Tkinter?
It's simple:
Install pynput (use pip install pynput (without the 'i').
Copy and paste this code into your editor:
from pynput.mouse import Listener
def on_click(x, y, button, pressed):
x = x
y = y
print('X =', x, '\nY =', y)
with Listener(on_click=on_click) as listener:
listener.join()
I hope this help you =D
You all are making it too hard, its just as easy as:
import pyautogui as pg
pos = pg.position()
# for x pos
print(pos[0])
# for y pos
print(pos[1])

Get window position and size in python with Xlib

I need to find window position and size, but I cannot figure out how. For example if I try:
id.get_geometry() # "id" is Xlib.display.Window
I get something like this:
data = {'height': 2540,
'width': 1440,
'depth': 24,
'y': 0, 'x': 0,
'border_width': 0
'root': <Xlib.display.Window 0x0000026a>
'sequence_number': 63}
I need to find window position and size, so my problem is: "y", "x" and "border_width" are always 0; even worse, "height" and "width" are returned without window frame.
In this case on my X screen (its dimensions are 4400x2560) I expected x=1280, y=0, width=1440, height=2560.
In other words I'm looking for python equivalent for:
#!/bin/bash
id=$1
wmiface framePosition $id
wmiface frameSize $id
If you think Xlib is not what I want, feel free to offer non-Xlib solution in python if it can take window id as argument (like the bash script above). Obvious workaround to use output of the bash script in python code does not feel right.
You are probably using reparenting window manager, and because of this id window has zero x and y. Check coordinates of parent window (which is window manager frame)
Liss posted the following solution as a comment:
from ewmh import EWMH
ewmh = EWMH()
def frame(client):
frame = client
while frame.query_tree().parent != ewmh.root:
frame = frame.query_tree().parent
return frame
for client in ewmh.getClientList():
print frame(client).get_geometry()
I'm copying it here because answers should contain the actual answer, and to prevent link rot.
Here's what I came up with that seems to work well:
from collections import namedtuple
import Xlib.display
disp = Xlib.display.Display()
root = disp.screen().root
MyGeom = namedtuple('MyGeom', 'x y height width')
def get_absolute_geometry(win):
"""
Returns the (x, y, height, width) of a window relative to the top-left
of the screen.
"""
geom = win.get_geometry()
(x, y) = (geom.x, geom.y)
while True:
parent = win.query_tree().parent
pgeom = parent.get_geometry()
x += pgeom.x
y += pgeom.y
if parent.id == root.id:
break
win = parent
return MyGeom(x, y, geom.height, geom.width)
Full example here.
In the same idea as #mgalgs, but more direct, I ask the root window to translate the (0,0) coordinate of the target window :
# assuming targetWindow is the window you want to know the position of
geometry = targetWindow.get_geometry()
position = geometry.root.translate_coords(targetWindow.id, 0, 0)
# coordinates are in position.x and position.y
# if you are not interested in the geometry, you can do directly
import Xlib.display
position = Xlib.display.Display().screen().root.translate_coords(targetWindow.id, 0, 0)
This gives the position of the client region of the targeted window (ie. without borders, title bar and shadow decoration created by the window manage). If you want to include them, replace targetWindow with targetWindow.query_tree().parent (or second parent).
Tested with KUbuntu 20.04 (ie KDE, Plasma and KWin decoration).

Categories

Resources