如何在tkinter窗口中设置鼠标位置

16

我有一个3D渲染程序,它根据鼠标在屏幕上的位置使世界围绕观察者旋转。世界旋转的弧度量由以下代码定义:

    glob.worldx=-(w.winfo_pointerxy()[0]-xy[0])/250

其中 xy[0] 是屏幕中心的 x 坐标

这意味着观察者可以旋转视野的量受到鼠标移动距离的限制。如果我能让鼠标回到屏幕中心,就可以解决这个问题了。有什么想法吗?


不必移动指针,也许可以通过指针的位置来控制世界旋转的速度——离中心越远,旋转速度越快。 - unutbu
这是一个很好的建议,unetbu。我曾经使用过那种控制方案,但它并不像我想要的那样用户友好,所以我想要类似于第一人称射击游戏的控制方式。 - Display name
2个回答

17
好消息是有一种方法可以做到这一点。
中间消息是它没有很好的文档记录。
坏消息是它只适用于某些平台。
另一个中间消息是你可以在至少一些平台上走出Tk。
在Tcl/Tk中实现这一点的方法是使用<Motion>事件,并使用-warp 1参数。关于此的文档较少,分散在几个不同的页面上(从bind开始),但详细信息在here中描述。基本上就是这样:
event generate . <Motion> -warp 1 -x 50 -y 50

那么,如何从Tkinter中实现呢?
嗯,event_generate没有任何文档说明,<Motion>事件和warp参数也是如此...但是如果您知道Tk映射到Tkinter的方式,那么很容易弄清楚:
window.event_generate('<Motion>', warp=True, x=50, y=50)

这确实会生成一个事件,你可以通过绑定<Motion>来观察到。下面是一个简单的测试程序:

from tkinter import *

root = Tk()

def key(event):
    root.event_generate('<Motion>', warp=True, x=50, y=50)

def motion(event):
    print('motion {}, {}'.format(event.x, event.y))

root.bind('<Key>', key)
root.bind('<Motion>', motion)
root.mainloop()

运行它,点击窗口以确保它有焦点,移动光标,你会看到它打印出类似这样的东西:

motion 65, 69
motion 65, 70
motion 65, 71

然后按下一个键,它将打印出这个:
motion 50, 50

这很好…但是它可能实际上不能移动您的光标,如果是这样,所有这些只是欺骗Tk认为光标已经移动了。


从浏览各种论坛来看,似乎是:
  • Mac: 无法使用。
  • Windows: 通常可以使用。
    • 您必须拥有Tk 8.4或更高版本。我找不到此错误的原因,但您可以在任何官方Windows二进制Python 2.7或3.x+安装中使用8.4。
    • 您还不能运行全屏应用程序(通常情况下,您不会使用Tk运行全屏应用程序)。
    • 在Vista及更高版本中,在某些情况下可能不起作用。这可能与未拥有桌面会话或未成为本地控制台会话有关,或者可能需要管理员或其他特权。
    • 如果不起作用,直接使用Win32 API很容易。
  • X11(大多数Linux、*BSD等):通常
    • 您的窗口管理器不能禁用其他客户端来控制指针。幸运的是,这似乎不是一件常见的事情。
    • 如果出现此问题,则没有解决方法。
  • 其他平台(iOS、Android等):不知道。

对于Mac,您想生成并发送一个NSMouseMoved事件。简单的方法是使用pyobjc(如果您使用的是Apple的Python,则内置;否则您必须安装它):

app = Foundation.NSApplication.sharedApplication()
event = Foundation.NSEvent.mouseEventWithType_location_modifierFlags_timestamp_windowNumber_context_eventNumber_clickCount_pressure_(
    Foundation.NSMouseMoved, (50, 50), 0, 0,
    app.mainWindow().windowNumber(), None, 0, 0, 0.0)
app.sendEvent_(event)

对于Windows系统,你需要调用SetCursorPos API或生成并发送MOUSEEVENT。前者可能无法在DirectX游戏中使用;后者可能无法在远程桌面上使用。针对这种情况,你可能想要使用前者。无论哪种方式,最简单的方法是安装pywin32,然后只需执行以下操作:

win32api.SetCursorPos((50, 50))

非常好用,abernart!有没有办法可以不按键完成这个操作?我已经尝试把root.event_generate函数放到鼠标运动事件中,但更新速度比较慢。 - Display name
没关系,我只是把 event_generate 放在一个 while 循环中,它就起作用了,谢谢你的帮助! - Display name
@sam:你可以在任何地方调用event_generate函数;我只是将其绑定到<Key>以便轻松测试。很高兴它对你起作用了。 - abarnert
好的,我找到了Windows和Mac的(大部分)细节并更新了答案。 - abarnert
在我的Linux系统(RHEL 6.7和Gnome)中,将鼠标指针放置在画布小部件内,需要额外的步骤是,在将所需坐标传递给canvas.envent_generate('<Motion>', warp=True, x=newX, y=newY)之前,使用canvas.winfo_rootx()和winfo_rooty()偏移canvas.winfo_pointerxy()。显然,canvas.winfor_pointerxy()相对于canvas的父级而言,并不在canvas本身内部。 - Hackless
显示剩余2条评论

3

对于任何想要将光标移动到屏幕上绝对位置的人(使用@abarnert的tkinter方法):

# Moves the mouse to an absolute location on the screen
def move_mouse_to(x, y):
    # Create a new temporary root
    temp_root = tk.Tk()
    # Move it to +0+0 and remove the title bar
    temp_root.overrideredirect(True)
    # Make sure the window appears on the screen and handles the `overrideredirect`
    temp_root.update()
    # Generate the event as @abarnert did
    temp_root.event_generate("<Motion>", warp=True, x=x, y=y)
    # Make sure that tcl handles the event
    temp_root.update()
    # Destroy the root
    temp_root.destroy()

这个函数不应该干扰其他的tkinter窗口。请注意,这个函数会创建一个新的窗口并进行两次刷新,因此可能会比较慢,用户可能会注意到新创建的窗口。


1
看起来完美无缺。很高兴成为第一个点赞的人 :) - WinEunuuchs2Unix

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接