我正在试着做一个图形用户界面。我使用图片作为标签。矩形表示操作区域,在这里我可以通过拖动标签来启动小部件或应用程序。如何做到这一点?
我已经制作了一个带有矩形的画布,并且我已经实现了拖放功能。
我使用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()
发布于 2019-05-10 03:44:22
首先,这是一个非常酷的项目,欢迎来到SO!
重构
在继续添加新逻辑之前,值得花时间清理当前的代码。
列表和循环
目前,将变量命名为image0
、image1
……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对于检查成员身份很有用。在这个应用程序中,我们需要确定哪些画布实体是应用程序图标,哪些是drop boxes。我使用集合来执行此标记逻辑,其中有两个不相交的集合,分别包含应用程序和盒子的ids。
封装
目前,您的代码在许多不同的地方都有相关的逻辑。该类访问全局状态中包含的大量数据。这是不安全的:如果您更改了全局状态的某些内容,则很可能会导致错误或破坏类。尝试编写具有强encapsulation的函数和类,并且组件之间的依赖关系尽可能少。在这个应用程序中,可以将所有内容都整齐地打包到DragAndDrop
类中,并简单地传入参数来告诉它如何操作。这样,调用者只能使用类的可用公共函数,并且故障很容易隔离和预测。
样式清理
对于Python convention,使用snake_case
表示变量和函数名,使用UpperCamelCase
表示类。在发布代码时,请确保缩进是正确的,因为Python使用缩进来确定每行代码所在的块范围。
除了我重命名为DragAndDrop
的dnd
类之外,您的变量名很清楚,这是值得称赞的!
添加新行为
在重构和设置数据结构之后,我们可以开始自由地添加新功能。
碰撞
虽然您的拖放功能很漂亮,但还没有代码来确定何时将应用程序图标拖放到框中。这有点棘手:我们可以使用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。我使用字典来映射应用程序I,并使用正确的命令将其传递到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上的快速运行。我打开几个文本编辑器并检查冲突。
https://stackoverflow.com/questions/56064099
复制相似问题