Tkinter:鼠标拖动一个没有边框的窗口,例如。覆盖直接(1)

Posted

技术标签:

【中文标题】Tkinter:鼠标拖动一个没有边框的窗口,例如。覆盖直接(1)【英文标题】:Tkinter: Mouse drag a window without borders, eg. overridedirect(1) 【发布时间】:2011-05-02 14:24:30 【问题描述】:

关于如何创建允许用户通过鼠标拖动无边框窗口的事件绑定的任何建议,例如。用overridedirect(1)创建的窗口?

用例:我们想创建一个浮动工具栏/调色板窗口(无边框),我们的用户可以在他们的桌面上拖动。

这是我的想法(伪代码):

    window.bind( '<Button-1>', onMouseDown ) 捕捉鼠标的初始位置。

    window.bind( '<Motion-1>', onMouseMove ) 跟踪鼠标开始移动后的位置。

    计算鼠标移动了多少并计算newXnewY的位置。

    使用window.geometry( '+%d+%d' % ( newX, newY ) )移动窗口。

Tkinter 是否公开了足够的功能以允许我实现手头的任务?或者有没有更简单/更高层次的方法来实现我想做的事情?

【问题讨论】:

这听起来与您在上一个问题中所问的完全相反,即创建一个无法移动或调整大小的窗口。如果您只想要一个无法调整大小的窗口,请修复窗口最小和最大大小,并让用户通过标题栏移动窗口。还是我不明白你想要完成什么? 上一个问题的用例是创建一个停靠窗口效果,例如。窗口停靠在用户桌面上的某个位置。 Tkinter 公开了 .minsize() 和 .maxsize() 窗口方法,但没有用于控制桌面上窗口 x、y 位置的等效方法。你是对的 - 上面的问题几乎与我之前的问题相反。不同之处在于用例,例如。我的新问题是基于用户在桌面上拖动小型浮动工具栏(“调色板”)窗口的需要...... ... 续上:这些浮动工具栏不需要位置限制。出于美学原因,我们希望这些浮动工具栏窗口/调色板是没有边框的窗口(我们将自己添加边框外观)。感谢您的帮助! 【参考方案1】:

是的,Tkinter 公开了足够的功能来做到这一点,不,没有更简单/更高级别的方法来实现你想做的事情。您的想法几乎是正确的。

这是一个例子,虽然它不是唯一的方法:

import tkinter as tk

class App(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        self.floater = FloatingWindow(self)

class FloatingWindow(tk.Toplevel):
    def __init__(self, *args, **kwargs):
        tk.Toplevel.__init__(self, *args, **kwargs)
        self.overrideredirect(True)

        self.label = tk.Label(self, text="Click on the grip to move")
        self.grip = tk.Label(self, bitmap="gray25")
        self.grip.pack(side="left", fill="y")
        self.label.pack(side="right", fill="both", expand=True)

        self.grip.bind("<ButtonPress-1>", self.start_move)
        self.grip.bind("<ButtonRelease-1>", self.stop_move)
        self.grip.bind("<B1-Motion>", self.do_move)

    def start_move(self, event):
        self.x = event.x
        self.y = event.y

    def stop_move(self, event):
        self.x = None
        self.y = None

    def do_move(self, event):
        deltax = event.x - self.x
        deltay = event.y - self.y
        x = self.winfo_x() + deltax
        y = self.winfo_y() + deltay
        self.geometry(f"+x+y")

app=App()
app.mainloop()

【讨论】:

布莱恩!非常感谢您的帮助。多么酷的功能——我将有很多有趣的构建工具来利用这个功能。我也很喜欢你创造握把的技巧——非常聪明。对于关注此线程的读者,我在“self.overrideredirect(True)”行之后添加了“self.attributes('-topmost', 1)”行,以使 Bryan 的浮动窗口显示在所有窗口的顶部。我认为这展示了人们如何使用 Bryan 的解决方案来创建各种桌面实用程序的潜力。 Bryan,您不介意进行编辑说明如何将鼠标光标重新定位到用户单击的特定坐标上吗?正如您当前的代码一样,在用户完成通过标签拖动窗口并释放鼠标左键后,鼠标光标会重新定位在窗口的左上角。 @the_prole:我的代码不应该将鼠标移动到任何地方。它所做的只是响应鼠标移动。您是说这个确切的代码正在为您移动鼠标吗?在什么平台上? @BryanOakley 你是对的......我把这个帖子***.com/questions/7455573/… 和你的混淆了。您推荐哪种方法?我尝试使用您的方法,因为它显然有效,但我没有看到标签。除非我将self.overrideredirect(True) 插入def __init__(self):,否则窗框也不会消失。如果有什么不同,我使用的是 Python 3.4。 我想隐藏在我的情况下不包含任何内容的主窗口。我在 tk.Tk.__init__(self) 之后添加了下一行: tk.Tk.withdraw(self) 并且主窗口消失了,但仍然显示浮动窗口。【参考方案2】:

这是我的解决方案:

from tkinter import *
from webbrowser import *


lastClickX = 0
lastClickY = 0


def SaveLastClickPos(event):
    global lastClickX, lastClickY
    lastClickX = event.x
    lastClickY = event.y


def Dragging(event):
    x, y = event.x - lastClickX + window.winfo_x(), event.y - lastClickY + window.winfo_y()
    window.geometry("+%s+%s" % (x , y))


window = Tk()
window.overrideredirect(True)
window.attributes('-topmost', True)
window.geometry("400x400+500+300")
window.bind('<Button-1>', SaveLastClickPos)
window.bind('<B1-Motion>', Dragging)
window.mainloop()

【讨论】:

【参考方案3】:

试试这个,它确实有效;

    创建一个事件函数来移动窗口:

    def 移动窗口(事件): root.geometry('+0+1'.format(event.x_root, event.y_root))

    绑定窗口:

    root.bind('', movewindow)

现在您可以触摸窗口并拖动

【讨论】:

在 python 3 中我收到一个错误文件“C:\python\python3.5\lib\tkinter_init_.py”,第 1686 行,在 wm_geometry return self .tk.call('wm', 'geometry', self._w, newGeometry) _tkinter.TclError: bad geometry specifier "+562 + 580"【参考方案4】:

Loïc Faure-Lacroix的思路很有用,下面是我自己在Python3.7.3上的简单代码sn-ps,希望对你有帮助:

from tkinter import *


def move_window(event):
    root.geometry(f'+event.x_root+event.y_root')


root = Tk()
root.bind("<B1-Motion>", move_window)
root.mainloop()

但是鼠标的位置总是在窗口的左上角。我怎样才能保持不变?期待更好的答案!


感谢 Bryan Oakley,因为一开始我无法在我的电脑上运行你的代码,所以我没有注意它。刚刚修改后,运行起来很好,不会出现上述情况(鼠标一直在左上角),最近更新代码如下:

def widget_drag_free_bind(widget):
    """Bind any widget or Tk master object with free drag"""
    if isinstance(widget, Tk):
        master = widget  # root window
    else:
        master = widget.master

    x, y = 0, 0
    def mouse_motion(event):
        global x, y
        # Positive offset represent the mouse is moving to the lower right corner, negative moving to the upper left corner
        offset_x, offset_y = event.x - x, event.y - y  
        new_x = master.winfo_x() + offset_x
        new_y = master.winfo_y() + offset_y
        new_geometry = f"+new_x+new_y"
        master.geometry(new_geometry)

    def mouse_press(event):
        global x, y
        count = time.time()
        x, y = event.x, event.y

    widget.bind("<B1-Motion>", mouse_motion)  # Hold the left mouse button and drag events
    widget.bind("<Button-1>", mouse_press)  # The left mouse button press event, long calculate by only once

【讨论】:

【参考方案5】:

此代码与 Bryan 的解决方案相同,但不使用 overridedirect。

它经过测试:python 3.7、Debian GNU/Linux 10 (buster)、Gnome 3.30

import tkinter as tk


class App(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        self.floater = FloatingWindow(self)


class FloatingWindow(tk.Toplevel):
    def __init__(self, *args, **kwargs):
        tk.Toplevel.__init__(self, *args, **kwargs)

        #self.overrideredirect(True)
        self.resizable(0, 0)  # Window not resizable
        self.wm_attributes('-type', 'splash')  # Hide title bar (Linux)

        self.label = tk.Label(self, text="Click on the grip to move")
        self.grip = tk.Label(self, bitmap="gray25")
        self.grip.pack(side="left", fill="y")
        self.label.pack(side="right", fill="both", expand=True)

        self.grip.bind("<ButtonPress-1>", self.StartMove)
        self.grip.bind("<ButtonRelease-1>", self.StopMove)
        self.grip.bind("<B1-Motion>", self.OnMotion)

    def StartMove(self, event):
        self.x = event.x
        self.y = event.y

    def StopMove(self, event):
        self.x = None
        self.y = None

    def OnMotion(self, event):
        deltax = event.x - self.x
        deltay = event.y - self.y
        x = self.winfo_x() + deltax
        y = self.winfo_y() + deltay
        self.geometry("+%s+%s" % (x, y))


app = App()
app.mainloop()

【讨论】:

以上是关于Tkinter:鼠标拖动一个没有边框的窗口,例如。覆盖直接(1)的主要内容,如果未能解决你的问题,请参考以下文章

VC中CDialogBox,停靠时,如何用鼠标拖动边框来改变大小

对tkinter简单封装了窗口拖动和点击获取鼠标位置的方法

猎豹MFC--拖动无边框窗体

C# winform鼠标移动到窗口给窗口加边框并获得句柄

Qt自定义界面边框后,移动鼠标拖动界面,界面会拖到任务栏以下。。。如何解决?

VC:如何实现窗口和窗口内容在鼠标拖动下改变大小