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
相关产品推荐
相关产品推荐

