Как я могу установить положение мыши в окне tkinter

У меня есть программа 3D-рендеринга, которая вращает мир вокруг наблюдателя в зависимости от положения мыши на экране. Количество в радианах, на которое вращается мир, определяется этой строкой

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

Где xy[0] — координата x центра экрана.

Это означает, что угол поворота поля зрения наблюдателя ограничен расстоянием, которое может пройти мышь. Если бы я мог заставить мышь вернуться в центр экрана, я мог бы решить эту проблему. Любые идеи?


person Display name    schedule 23.05.2013    source источник
comment
Вместо перемещения указателя, возможно, сделайте так, чтобы положение указателя контролировало скорость, с которой вращается мир — чем дальше от центра, тем быстрее вращение.   -  person unutbu    schedule 23.05.2013
comment
Это отличное предложение unetbu, и я использовал эту схему управления, но она не так удобна для пользователя, как мне хотелось бы, поэтому я хочу что-то похожее на то, как управляется шутер от первого лица.   -  person Display name    schedule 23.05.2013


Ответы (2)


Хорошая новость заключается в том, что есть способ сделать это.

Промежуточная новость заключается в том, что это плохо задокументировано.

Плохая новость заключается в том, что он работает только на некоторых платформах.

Другая промежуточная новость заключается в том, что вы можете выйти за пределы Tk по крайней мере на некоторых платформах.


В Tcl/Tk это можно сделать, создав событие <Motion> с помощью -warp 1. Документация по этому вопросу немногочисленна и разбросана по нескольким разным страницам (начните с bind), но подробности описаны здесь. В основном, это просто:

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: Does not work.
  • Windows: Usually works.
    • You must have Tk 8.4.something or later. I couldn't find the bug for this, but you can count on 8.4 with any official Windows binary install of Python 2.7 or 3.x+.
    • Вы также не должны запускать полноэкранное приложение (чего обычно не бывает с Tk).
    • В Vista и более поздних версиях в некоторых случаях это не сработает. Это может иметь какое-то отношение к тому, что вы не владеете сеансом рабочего стола или не являетесь сеансом локальной консоли, или это может быть связано с необходимостью прав администратора или других привилегий.
    • Если не работает, то легко сразу перейти к Win32 API.
  • X11 (most linux, *BSD, etc.): Usually
    • Your window manager must not have disabled other clients from warping the pointer. Fortunately, that doesn't seem to be a common thing to do.
    • Если у вас есть эта проблема, нет никакого способа обойти это.
  • Другие платформы (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))
person abarnert    schedule 23.05.2013
comment
@Тим: Отлично! Я сделал немного больше исследований; позвольте мне обновить вещи. - person abarnert; 23.05.2013
comment
Работает как шарм абернарт! Есть ли способ сделать это, не нажимая клавишу? самое близкое, что я получил, это поместить функцию root.event_generate в событие движения мыши, но это обновляется довольно медленно - person Display name; 23.05.2013
comment
Неважно, я просто добавил event_generate в цикл while, и это сработало, спасибо за все! - person Display name; 23.05.2013
comment
@sam: вы можете вызывать функцию event_generate из любого места; Я просто привязал его к <Key>, чтобы упростить тестирование. Рад, что это работает для вас. - person abarnert; 23.05.2013
comment
Хорошо, я узнал (большую часть) деталей для Windows и Mac и обновил ответ. - person abarnert; 23.05.2013
comment
В моем Linux-боксе (RHEL 6.7 и Gnome) для размещения указателя мыши внутри виджета холста необходимо сделать еще один шаг — сместить canvas.winfo_pointerxy() с помощью canvas.winfo_rootx() и winfo_rooty() перед передачей нужных координат на холст. envent_generate('‹Движение›', warp=True, x=newX, y=newY). По-видимому, canvas.winfor_pointerxy() относится к родителю холста, а не к самому холсту. - person Hackless; 27.04.2021
comment
ответ TheLizzard отлично работает в Linux (ubuntu). - person WinEunuuchs2Unix; 26.07.2021

Для всех, кто заинтересован в перемещении курсора в абсолютное положение на экране (используя метод tkinter @abarnert):

# 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. Обратите внимание, что эта функция создает новое окно, которое обновляется дважды, поэтому оно может работать медленно, и пользователь может заметить созданное окно.

person TheLizzard    schedule 25.03.2021
comment
Кажется, работает отлично. Рад быть первым плюсом :) - person WinEunuuchs2Unix; 26.07.2021