You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kivy无边框窗口拖拽异常求助:释放鼠标后窗口仍跟随光标

问题描述

我想给无边框Kivy应用实现窗口拖拽功能,网上找的方案都不管用,自己写的代码能实现拖拽,但释放鼠标后窗口还跟着光标动,最后程序直接无响应被终止。补充:win32api.GetKeyState(0x01)在循环启动后没法更新状态,赋值给变量也没用。

我的代码如下:

from kivy.app import App
from win32api import GetSystemMetrics
from kivy.lang.builder import Builder
from kivy.core.window import Window
from kivy.uix.widget import Widget
import pyautogui
import win32api
import re


Window.size=(600,300)
Window.borderless=True
#The following causes the window to open in the middle of the screen:
Window.top=((GetSystemMetrics(1)/2)-150)
Window.left=((GetSystemMetrics(0)/2)-300) 
#####################################################################

Builder.load_string("""
<Grab>
    GridLayout:
        size:root.width,root.height
        cols:2
        Label:
            id:label
            text:'A label'
        Button:
            id:button
            text:'The button that changes the window position'
            on_press: root.grab_window()
""")

class Grab(Widget):
    def grab_window(self):
        #The following saves the mouse position relative to the window:
        Static_Mouse_pos=re.findall('\\d+',str(pyautogui.position()))
        Mouse_y=int(Static_Mouse_pos[1])-Window.top
        Mouse_x=int(Static_Mouse_pos[0])-Window.left
        ###############################################################
        #The following is what causes the window to follow the mouse position:
        while win32api.GetKeyState(0x01)<0: #In theory this should cause the loop to start as soon as it is clicked, I ruled out that it would start and end when the button was pressed and stopped being pressed because as soon as the screen starts to move, it stops being pressed.
            Relative_Mouse_pos=re.findall('\\d+',str(pyautogui.position()))
            Window.left=(int(Relative_Mouse_pos[0])-Mouse_x)
            Window.top=(int(Relative_Mouse_pos[1])-Mouse_y)
            print(f'Mouse position: ({Mouse_x},{Mouse_y})') #To let you know the mouse position (Not necessary)
            print(f'Window position: ({Window.top},{Window.left})') #To let you know the position of the window (Not necessary)
            if win32api.GetKeyState(0x01)==0: #This is supposed to stop everything (Detects when you stop holding the click)
                break
        ######################################################################
class app(App):
    def build(self):
        return Grab()
if __name__=='__main__':
    app().run()

解决方案

修复现有代码的问题

你的核心问题是阻塞了Kivy的主事件循环——Kivy是单线程框架,你写的while循环会一直霸占主线程,导致Kivy没法处理鼠标释放事件、更新UI,最终程序无响应。另外win32api.GetKeyState的判断逻辑也不对,得用位运算来检测按键是否按住。

修改后的代码:

from kivy.app import App
from win32api import GetSystemMetrics
from kivy.lang.builder import Builder
from kivy.core.window import Window
from kivy.uix.widget import Widget
import pyautogui
import win32api
from kivy.clock import Clock

Window.size=(600,300)
Window.borderless=True
Window.top=((GetSystemMetrics(1)/2)-150)
Window.left=((GetSystemMetrics(0)/2)-300)

Builder.load_string("""
<Grab>
    GridLayout:
        size:root.width,root.height
        cols:2
        Label:
            id:label
            text:'A label'
        Button:
            id:button
            text:'The button that changes the window position'
            on_press: root.start_drag()
            on_release: root.stop_drag()
""")

class Grab(Widget):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.drag_schedule = None
        self.offset_x = 0
        self.offset_y = 0

    def start_drag(self):
        # 保存鼠标相对于窗口的偏移量
        mouse_x, mouse_y = pyautogui.position()
        self.offset_x = mouse_x - Window.left
        self.offset_y = mouse_y - Window.top
        # 用Clock调度每帧更新,不阻塞主循环
        self.drag_schedule = Clock.schedule_interval(self.update_window_pos, 1/60)

    def update_window_pos(self, dt):
        # 正确判断鼠标左键是否按住:位运算检测最高位
        if not (win32api.GetKeyState(0x01) & 0x8000):
            self.stop_drag()
            return
        # 更新窗口位置
        mouse_x, mouse_y = pyautogui.position()
        Window.left = mouse_x - self.offset_x
        Window.top = mouse_y - self.offset_y
        print(f'Mouse offset: ({self.offset_x},{self.offset_y})')
        print(f'Window position: ({Window.top},{Window.left})')

    def stop_drag(self):
        if self.drag_schedule:
            self.drag_schedule.cancel()
            self.drag_schedule = None

class app(App):
    def build(self):
        return Grab()
if __name__=='__main__':
    app().run()

更优的Kivy原生实现(跨平台)

没必要依赖Windows专属API,Kivy本身的触摸事件系统就能实现拖拽,而且跨Windows、Mac、Linux都能用,这也是官方推荐的方式。

示例代码:

from kivy.app import App
from win32api import GetSystemMetrics
from kivy.lang.builder import Builder
from kivy.core.window import Window
from kivy.uix.widget import Widget

Window.size=(600,300)
Window.borderless=True
Window.top=((GetSystemMetrics(1)/2)-150)
Window.left=((GetSystemMetrics(0)/2)-300)

Builder.load_string("""
<Grab>
    GridLayout:
        size:root.width,root.height
        cols:2
        Label:
            id:label
            text:'A label'
        Button:
            id:drag_btn
            text:'Drag me to move window'
            on_touch_down: root.on_drag_down(*args)
            on_touch_move: root.on_drag_move(*args)
            on_touch_up: root.on_drag_up(*args)
""")

class Grab(Widget):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.is_dragging = False
        self.offset_x = 0
        self.offset_y = 0

    def on_drag_down(self, widget, touch):
        # 判断触摸是否在按钮范围内,且是左键
        if widget.collide_point(*touch.pos) and touch.button == 'left':
            self.is_dragging = True
            # 计算鼠标相对于窗口的偏移(用触摸事件的原始坐标ox/oy)
            self.offset_x = touch.x + Window.left - touch.ox
            self.offset_y = touch.y + Window.top - touch.oy
            return True  # 标记事件已处理,避免向上传递

    def on_drag_move(self, widget, touch):
        if self.is_dragging and touch.button == 'left':
            # 根据触摸移动量更新窗口位置
            Window.left = touch.ox + self.offset_x - touch.x
            Window.top = touch.oy + self.offset_y - touch.y
            return True

    def on_drag_up(self, widget, touch):
        if self.is_dragging and touch.button == 'left':
            self.is_dragging = False
            return True

class app(App):
    def build(self):
        return Grab()
if __name__=='__main__':
    app().run()

内容的提问来源于stack exchange,提问作者Berdy Alexei Cadaeib Fecei

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 00:45:24