Как мога да задам позицията на мишката в прозорец на 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 (който е вграден, ако използвате Python на Apple; в противен случай трябва да го инсталирате):

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
Работи като чаровен abernart! Има ли начин да направя това без да натискам клавиш? най-близкото, което съм получил, е чрез поставяне на функцията 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('‹Motion›', warp=True, x=newX, y=newY). Очевидно canvas.winfor_pointerxy() е относително към родителя на canvas, а не в рамките на самото платно. - 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