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

时间:2019-05-09 16:48:25

标签: 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()

1 个答案:

答案 0 :(得分:3)

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


重构

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

列表和循环

现在,将变量命名为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使用缩进来确定每行代码所在的块范围。

除了我将其重命名为dnd的{​​{1}}类之外,您的变量名也很清晰,值得称赞!


添加新行为

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

碰撞

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

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()。请查看docs了解更多信息。我使用字典来映射应用程序ID,并将正确的命令传递给subprocess.Popen()

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


代码

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

apps

演示

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

enter image description here