python - 如何通过tk/tcl初始化小部件

标签 python user-interface tkinter

我正在尝试制作GUI。我正在使用图像作为标签。
矩形表示操作区域,我可以在其中拖动标签来启动小部件或应用程序。怎么做?

我用矩形制作了一个画布,并且实现了拖放功能。

我已经使用pack()用矩形实现了画布,并且使用了拖放功能

import subprocess
from tkinter import *

class DragAndDrop:
    def __init__(self, boxes, apps, width=1920, height=1080, bg="white"):
    self.photos = []
    self.__apps = {}
    self.__boxes = set()
    self.root = Tk()
    self.canvas = Canvas(self.root, width=width, height=height, bg=bg)
    self.canvas.pack()

    for box in boxes:
        self.__boxes.add(
            self.canvas.create_rectangle(
                box["x1"], box["y1"], box["x2"], box["y2"],
                width=box["width"], fill=box["fill"]
            )
        )

    for app in apps:
        self.photos.append(PhotoImage(file=app["img"]))
        self.__apps[(
            self.canvas.create_image(app["x"], app["y"], image=self.photos[-1])
        )] = app["cmd"]

    self.__move = False
    self.canvas.bind("<Button-1>", self.start_movement)
    self.canvas.bind("<ButtonRelease-1>", self.stop_movement)
    self.canvas.bind("<Motion>", self.movement)

def run(self):
    self.root.mainloop()

def start_movement(self, event):
    self.initi_x = self.canvas.canvasx(event.x)
    self.initi_y = self.canvas.canvasy(event.y)
    self.movingimage = self.canvas.find_closest(
        self.initi_x, self.initi_y, halo=5
    )

    if self.movingimage[0] in self.__apps:
        self.__move = True

def stop_movement(self, event):
    self.__move = False
    overlaps = self.canvas.find_overlapping(*self.canvas.bbox(self.movingimage))

    if len(overlaps) > 1 and not self.movingimage[0] in self.__boxes and \
       any(x in self.__boxes for x in overlaps):
        subprocess.Popen(self.__apps[self.movingimage[0]])

def movement(self, event):
    if self.__move:
        end_x = self.canvas.canvasx(event.x)
        end_y = self.canvas.canvasy(event.y)
        deltax = end_x - self.initi_x
        deltay = end_y - self.initi_y
        self.initi_x = end_x
        self.initi_y = end_y
        self.canvas.move(self.movingimage, deltax, deltay)


if __name__ == "__main__":
boxes = (
    {"x1": 618, "y1": 100, "x2": 693, "y2": 175, "width": 5, "fill": "white"},
    {"x1": 693, "y1": 100, "x2": 768, "y2": 175, "width": 5, "fill": "white"},
    {"x1": 618, "y1": 175, "x2": 693, "y2": 250, "width": 5, "fill": "green"},
    {"x1": 693, "y1": 175, "x2": 768, "y2": 250, "width": 5, "fill": "green"},
    {"x1": 618, "y1": 250, "x2": 693, "y2": 325, "width": 5, "fill": "blue"},
    {"x1": 693, "y1": 250, "x2": 768, "y2": 325, "width": 5, "fill": "blue"},
    {"x1": 618, "y1": 325, "x2": 693, "y2": 400, "width": 5, "fill": "yellow"},
    {"x1": 693, "y1": 325, "x2": 768, "y2": 400, "width": 5, "fill": "yellow"},
    {"x1": 543, "y1": 175, "x2": 618, "y2": 250, "width": 5, "fill": "dark orange"},
    {"x1": 468, "y1": 175, "x2": 543, "y2": 250, "width": 5, "fill": "dark orange"},
    {"x1": 768, "y1": 175, "x2": 843, "y2": 250, "width": 5, "fill": "red"},
    {"x1": 843, "y1": 175, "x2": 918, "y2": 250, "width": 5, "fill": "red"},
)

apps = (
    {"x": 125, "y": 125, "img": "chrome.png", "cmd": r"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe"},
    {"x": 125, "y": 225, "img": "firefox.png", "cmd": r"C:\Program Files\Mozilla Firefox\firefox.exe"},
    {"x": 125, "y": 325, "img": "np++.png", "cmd": r"C:\Program Files\Notepad++\notepad++.exe"},
    {"x": 125, "y": 425, "img": "word.png", "cmd": r"C:\Program Files\Microsoft Office\root\Office16\WINWORD.exe"},
    {"x": 200, "y": 125, "img": "excel.png", "cmd": r"C:\Program Files\Microsoft Office\root\Office16\EXCEL.EXE"},
    {"x": 200, "y": 225, "img": "ppt.png", "cmd": r"C:\Program Files\Microsoft Office\root\Office16\POWERPNT.EXE"},
    {"x": 200, "y": 325, "img": "outlook.png", "cmd": r"C:\Program Files\Microsoft Office\root\Office16\OUTLOOK.EXE"},
    {"x": 200, "y": 425, "img": "access.png", "cmd": r"C:\Program Files\Microsoft Office\root\Office16\MSACCESS.EXE"},
    {"x": 50, "y": 125, "img": "onenote.png", "cmd": r"C:\Program Files\Microsoft Office\root\Office16\ONENOTE.EXE"},
    {"x": 50, "y": 225, "img": "pub.png", "cmd": r"C:\Program Files\Microsoft Office\root\Office16\MSPUB.EXE"},
    {"x": 50, "y": 325, "img": "vlc.png", "cmd": r"C:\Program Files\VideoLAN\VLC\vlc.exe"},
    {"x": 50, "y": 425, "img": "ccl.png", "cmd": r"C:\Program Files\CCleaner\CCleaner64.exe"},

)

dnd = DragAndDrop(boxes, apps)
dnd.run()

最佳答案

首先,这是一个非常酷的项目,欢迎来到SO!



重构

在继续添加新逻辑之前,值得花时间清理当前代码。

列表和循环

现在,将变量命名为image0image1 ... image12的方法非常严格且不可扩展。如果您需要添加另一个框或应用程序图标,则基本上要重写所有代码以适应更改。至于可伸缩性,如果要50、100或1000个应用程序怎么办?这将是很多打字!

这就是为什么发明了lists和类似的阵列状结构的原因。这个想法是将类似物品放入其中的单个容器。您可以在列表上loop并对列表中的每个项目执行操作。我不会去学习有关列表和循环的完整教程,但是它们是完成任何编程任务的必备工具,因此必须学习如何使用它们,以使自己成为编码人员。

作为您即时代码中的一个具体示例,请使用一个变量images = []代替image1 ... image12。在花括号内,添加图像数据,然后使用images[n]访问其中一个,其中n是要处理的图像的索引。您可以使用以下结构遍历它们:

for image in images:
    # do something with this image


您也可以将元组用作无法更改的列表(我在整个应用程序中都使用元组-与列表类似,但它们看起来像apps = ())。

辞典

列表是水平的,并且像项目一样存储在集合中,而dictionaries是垂直的,或与组相关但不同的属性一起组合到单个实体中。在您的代码中,“ app”实体由一些字符串和数字描述,如下所示:

{
    "x": 125, 
    "y": 125, 
    "img": "np++.png", 
    "cmd": r"C:\Program Files (x86)\Notepad++\notepad++.exe"
}


我列出了这些词典,以存储有关我希望框和应用程序显示在何处以及与每个命令和图像关联的命令和图像(或颜色/宽度)的数据。

套装

Sets对于检查成员资格很有用。在此应用程序中,我们需要确定哪些画布实体是应用程序图标,哪些是投递箱。我使用集合来执行此标记逻辑,其中两个不相交的集合分别包含应用程序和框的ID。

封装形式

当前,您的代码在许多不同的地方都有相关的逻辑。该类访问全局状态中包含的许多数据。这是不安全的:如果您更改了有关全局状态的某些信息,则很可能导致错误或破坏类。尝试编写具有强encapsulation的函数和类,并尽可能减少组件之间的依赖关系。在此应用程序中,可以将所有内容整齐地打包到DragAndDrop类中,并简单地传入参数以告知其操作方法。这样,调用者只能使用该类的可用公共功能,并且故障很容易被隔离和可预测。

样式清理

通过Python convention,将snake_case用作变量和函数名称,将UpperCamelCase用作类。发布代码时,请确保缩进正确,因为Python使用缩进来确定每行代码所在的块范围。

除了我重命名为dndDragAndDrop类之外,您的变量名也很清晰,值得称赞!



添加新行为

重构并设置数据结构后,我们可以开始自由添加新功能。

碰撞

虽然您的拖放功能很漂亮,但是尚无代码来确定何时将应用程序图标拖放到框上。这有点棘手:我们可以使用canvas.find_overlapping()来检查是否重叠,但是我们需要确保将图标放到一个盒子上而不是另一个图标上。移动停止后,我们可以调用此函数来执行此操作:

def stop_movement(self, event):
    self.__move = False
    overlaps = self.canvas.find_overlapping(*self.canvas.bbox(self.movingimage))

    if len(overlaps) > 1 and not self.movingimage[0] in self.__boxes and \
       any(x in self.__boxes for x in overlaps):
        subprocess.call(self.__apps[self.movingimage[0]])


该函数使用了我之前提到的一些数据结构来建立不同实体之间的关系。

创建和终止进程

使用subprocess.call()进行系统调用以打开一个新进程并阻塞直到其关闭。如果要打开多个应用程序而不会阻塞等待它们完成,则可以使用subprocess.Popen()。请查看docs以获取更多信息。我使用字典来映射带有正确命令的应用程序ID,以传递到subprocess.Popen()

根据您的其他请求,在创建的子进程上调用kill()将其杀死。我将所有这些信息保存在self.__app词典中,但是它可能使用对类的重构来进行适当的封装,因为应用程序正在累积自己的属性和行为逻辑。





注意,这只是添加了新行为的初始重构。总会有改进的空间,我在组织数据方面所做的一些选择可能并不符合您的喜好,因此我建议您进一步探索并调整口味。我也只想添加两个文本编辑器应用程序,但是您可以根据需要将尽可能多的应用程序添加到apps元组中,以进行进一步测试。

import subprocess
from tkinter import *

class DragAndDrop:
    def __init__(self, boxes, apps, width=1920, height=1080, bg="white"):
        self.photos = []
        self.__apps = {}
        self.__boxes = set()
        self.root = Tk()
        self.canvas = Canvas(self.root, width=width, height=height, bg=bg)
        self.canvas.pack()

        for box in boxes:
            self.__boxes.add(
                self.canvas.create_rectangle(
                    box["x1"], box["y1"], box["x2"], box["y2"], 
                    width=box["width"], fill=box["fill"]
                )
            )

        for app in apps:
            self.photos.append(PhotoImage(file=app["img"]))
            self.__apps[(
                self.canvas.create_image(app["x"], app["y"], image=self.photos[-1])
            )] = {"cmd": app["cmd"], "running": False, "proc": None}

        self.__move = False
        self.canvas.bind("<Button-1>", self.start_movement)
        self.canvas.bind("<ButtonRelease-1>", self.stop_movement)
        self.canvas.bind("<Motion>", self.movement)

    def run(self):    
        self.root.mainloop()

    def start_movement(self, event):
        self.initi_x = self.canvas.canvasx(event.x)
        self.initi_y = self.canvas.canvasy(event.y)
        self.movingimage = self.canvas.find_closest(
            self.initi_x, self.initi_y, halo=5
        )

        if self.movingimage[0] in self.__apps:
            self.__move = True

    def stop_movement(self, event):
        self.__move = False
        overlaps = self.canvas.find_overlapping(*self.canvas.bbox(self.movingimage))
        app = self.movingimage[0]

        if len(overlaps) > 1 and app not in self.__boxes and not self.__apps[app]["running"] \
           and any(x in self.__boxes for x in overlaps):
            self.__apps[app]["proc"] = subprocess.Popen(self.__apps[app]["cmd"])
            self.__apps[app]["running"] = True
        elif app not in self.__boxes and self.__apps[app]["running"] \
           and not any(x in self.__boxes for x in overlaps):
            self.__apps[app]["proc"].kill()
            self.__apps[app]["running"] = False

    def movement(self, event):
        if self.__move:
            end_x = self.canvas.canvasx(event.x)
            end_y = self.canvas.canvasy(event.y)
            deltax = end_x - self.initi_x
            deltay = end_y - self.initi_y
            self.initi_x = end_x
            self.initi_y = end_y
            self.canvas.move(self.movingimage, deltax, deltay)


if __name__ == "__main__":
    boxes = (
        {"x1": 618, "y1": 100, "x2": 693, "y2": 175, "width": 5, "fill": "white"},
        {"x1": 693, "y1": 100, "x2": 768, "y2": 175, "width": 5, "fill": "white"},
        {"x1": 618, "y1": 175, "x2": 693, "y2": 250, "width": 5, "fill": "green"},
        {"x1": 693, "y1": 175, "x2": 768, "y2": 250, "width": 5, "fill": "green"},
        {"x1": 618, "y1": 250, "x2": 693, "y2": 325, "width": 5, "fill": "blue"},
        {"x1": 693, "y1": 250, "x2": 768, "y2": 325, "width": 5, "fill": "blue"},
        {"x1": 618, "y1": 325, "x2": 693, "y2": 400, "width": 5, "fill": "yellow"},
        {"x1": 693, "y1": 325, "x2": 768, "y2": 400, "width": 5, "fill": "yellow"},
        {"x1": 543, "y1": 175, "x2": 618, "y2": 250, "width": 5, "fill": "dark orange"},
        {"x1": 468, "y1": 175, "x2": 543, "y2": 250, "width": 5, "fill": "dark orange"},
        {"x1": 768, "y1": 175, "x2": 843, "y2": 250, "width": 5, "fill": "red"},
        {"x1": 843, "y1": 175, "x2": 918, "y2": 250, "width": 5, "fill": "red"},
    )

    apps = (
        {"x": 125, "y": 125, "img": "np++.png", "cmd": r"C:\Program Files (x86)\Notepad++\notepad++.exe"},
        {"x": 125, "y": 225, "img": "vim.png", "cmd": r"C:\Program Files (x86)\Vim\vim74\vim.exe"},
    )

    dnd = DragAndDrop(boxes, apps)
    dnd.run()




演示版

这是Windows上该程序的快速运行。我打开几个文本编辑器并检查碰撞。

enter image description here

关于python - 如何通过tk/tcl初始化小部件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56064099/

相关文章:

python - python的sphinx中var、cvar、ivar有什么区别?

python - 使用 django 分页器显示项目编号。

css - 编写 MVC 3 应用程序时的表单设计方法

java - 以图形方式显示图中两个顶点之间的最短路径

python - 如何为 tkinter Scale 小部件滚动条使用非整数、浮点值?

Python Tkinter canvas.tag_bind()

python - 从 SciPy 稀疏 Coo 矩阵填充 Pandas SparseDataFrame

Python tkinter 使用按钮进行串行工作

java - 在 Swing 中创建符号调色板

python - Tkinter Notebook 中的可滚动页面