【记录】python3 使用tkinter制作tkinterUI编辑器 《二十》使用canvas重构选中框
使用tkinter制作tkinterUI编辑器
前言
这篇记录记录一下选中框的重构,内容改动较大,可能会出现一些问题,所以目前只在1.0分支中提交。
完整代码已上传到github,可从第一篇记录下载
提示:
1.由于代码改动较大,之前创建的工程都不好使了,需要重新创建。
2.为了让所有控件都能拖动,增加了两个属性,pixel_width,pixel_height,修改这两个属性就能改变控件的尺寸,其实就是调用了place_configure(width=xx, height=xx)实现的,之后在编辑器就不能编辑width和height属性了。
3.在代码里直接修改width和height属性可能会没有效果,需要使用place_configure(width=xx, height=xx)进行修改。
4.我在使用滑动条的时候发现canvas在调用create_window((0,0), window=xxx)后就不能再用place_configure修改xxx的place属性了,需要特殊处理。
5.有时候还是会想要按照字符数修改Button,Entry等控件的大小,这些地方也需要特殊处理,之后有时间加个函数统一进行处理。
6.新的拖拽逻辑需要根据控件的anchor属性做不同的处理,目前我还来不及处理,只能处理anchor是’nw‘的情况,所以anchor属性目前也不能在编辑器编辑了,以后有时间再处理。
新的选中框效果如下,可以从8个方向拖拽修改控件的大小:
一、实现新的选中控件
先上代码,selectedCanvas.py
#!/usr/bin/python
# -*- coding: utf-8 -*-
from tkinter import Canvas
from functools import partial
class SelectedCanvas(Canvas):
def __init__(self, master=None, cnf={}, **kw):
Canvas.__init__(self, master, cnf, **kw)
self.is_sizing = False
self.old_width = 0
self.old_height = 0
self.old_pos_x = 0
self.old_pos_y = 0
self.start_x = 0
self.start_y = 0
self.start_root_x = 0
self.start_root_y = 0
self.on_resize_complete = None
def set_on_resize_complete(self, on_resize_complete):
self.on_resize_complete = on_resize_complete
def on_update(self):
"""
初始化后会被调用,在这里绘制矩形
:return: None
"""
self.create_rectangle(-1, -1, -2, -2, tag='side', dash=3, outline='red')
for name in ('nw', 'w', 'sw', 'n', 's', 'ne', 'e', 'se'):
self.create_rectangle(-1, -1, -2, -2, tag=name, outline='red')
self.tag_bind(name, "<Enter>", partial(self.on_mouse_enter, name))
self.tag_bind(name, "<Leave>", partial(self.on_mouse_leave, name))
self.tag_bind(name, "<Button-1>", partial(self.on_mouse_click, name))
self.tag_bind(name, "<B1-Motion>", partial(self.on_mouse_move, name))
self.tag_bind(name, "<ButtonRelease-1>", partial(self.on_mouse_release, name))
def show(self, is_fill=False):
"""
显示
:param is_fill: 是否填充
:return: None
"""
width = self.winfo_width()
height = self.winfo_height()
self.coords('side', 6, 6, width - 6, height - 6)
self.coords('nw', 0, 0, 7, 7)
self.coords('sw', 0, height - 8, 7, height - 1)
self.coords('w', 0, (height - 7) / 2, 7, (height - 7) / 2 + 7)
self.coords('n', (width - 7) / 2, 0, (width - 7) / 2 + 7, 7)
self.coords('s', (width - 7) / 2, height - 8, (width - 7) / 2 + 7, height - 1)
self.coords('ne', width - 8, 0, width - 1, 7)
self.coords('se', width - 8, height - 8, width - 1, height - 1)
self.coords('e', width - 8, (height - 7) / 2, width - 1, (height - 7) / 2 + 7)
if is_fill:
for name in ('nw', 'w', 'sw', 'n', 's', 'ne', 'e', 'se'):
self.itemconfig(name, fill='red')
def hide(self):
"""
隐藏
:return: None
"""
self.coords('side', -1, -1, -2, -2,)
for name in ('nw', 'w', 'sw', 'n', 's', 'ne', 'e', 'se'):
self.coords(name, -1, -1, -2, -2)
def on_mouse_enter(self, tag_name, event):
"""
鼠标进入事件
:param tag_name: 标签名字
:param event: event
:return: None
"""
if tag_name in ("nw", "sw", "ne", "se"):
self["cursor"] = "sizing"
elif tag_name in ("w", "e"):
self["cursor"] = "sb_h_double_arrow"
else:
self["cursor"] = "sb_v_double_arrow"
def on_mouse_leave(self, tag_name, event):
"""
鼠标离开事件
:param tag_name: 标签名字
:param event: event
:return: None
"""
if self.is_sizing:
return
self["cursor"] = "arrow"
def on_mouse_click(self, tag_name, event):
"""
鼠标点击事件
:param tag_name: 标签名字
:param event: event
:return: None
"""
self.is_sizing = True
self.start_x = event.x
self.start_y = event.y
self.start_root_x = event.x_root
self.start_root_y = event.y_root
self.old_width = self.winfo_width()
self.old_height = self.winfo_height()
self.old_pos_x = int(self.place_info()['x'])
self.old_pos_y = int(self.place_info()['y'])
def on_mouse_move(self, tag_name, event):
"""
鼠标移动事件
:param tag_name: 标签名字
:param event: event
:return: None
"""
if not self.is_sizing:
return
if 'e' in tag_name:
width = max(0, self.old_width + (event.x - self.start_x))
self.place_configure(width=width)
if 'w' in tag_name:
width = max(0, self.old_width + (self.start_root_x - event.x_root))
to_x = event.x - self.start_x + int(self.place_info()['x'])
self.place_configure(width=width, x=to_x)
if 's' in tag_name:
height = max(0, self.old_height + (event.y - self.start_y))
self.place_configure(height=height)
if 'n' in tag_name:
height = max(0, self.old_height + (self.start_root_y - event.y_root))
to_y = event.y - self.start_y + int(self.place_info()['y'])
self.place_configure(height=height, y=to_y)
self.after_idle(self.show)
def on_mouse_release(self, tag_name, event):
"""
鼠标松开事件
:param tag_name: 标签名字
:param event: event
:return: None
"""
self.is_sizing = False
if self.on_resize_complete is not None:
self.on_resize_complete()
self["cursor"] = "arrow"
说明:
- 控件继承Canvas,主要利用了canvas的画矩形功能。
- 初始化后绘制9个矩形,1个显示边框,8个用来调整大小,然后给调整大小的矩形绑定事件。
- show函数将9个矩形显示出来,使用coords函数重新修改矩形的位置,is_fill参数会填充8个小矩形的颜色,之后做多重选择的时候使用。
- hide函数将矩形移动到看不见的位置。
- on_mouse_move函数用来修改自己的尺寸,目前只支持anchor=’nw’的情况,移动后需要使用after_idle函数延迟调用show函数,因为修改尺寸后控件还没有更新,调用winfo_width,winfo_height获取不到真正的尺寸。
二、选中控件的使用
-
修改tkinterEditor.py,创建控件时先创建一个SelectedCanvas,然后将控件创建到SelectedCanvas中,控件创建后新增了绑定控件尺寸变化以及SelectedCanvas尺寸变化的函数
def create_component_by_info(self, master, component_info, is_init_main, on_create_success=None): """ 暂时重写componentMgr的创建控件函数,主要是为了选中控件时的效果,以后可能会去掉 :param master: 父控件 :param component_info: 控件信息 :param is_init_main: 是否是初始化主界面时调用的 :param on_create_success: 创建成功回调 :return: 控件 """ # 初始化调用的话直接调父类的就可以 if is_init_main: return componentMgr.create_component_by_info(self, master, component_info, is_init_main, on_create_success) gui_type = component_info.get("gui_type", "None") if gui_type == "None": return None # toplevel直接调父类的 if gui_type == "Toplevel": return componentMgr.create_component_by_info(self, master, component_info, is_init_main, on_create_success) # 创建一个frame套在真正的控件外面 frame_prop = { "background": master["background"], } frame, info = create_default_component(master, "SelectedCanvas", "None", frame_prop, False) # 创建控件 component = create_component_from_dict(frame, component_info) frame.place_configure(x=int(component_info["x"]), y=int(component_info["y"]), anchor=component_info["anchor"]) component.place_configure(x=0, y=0, anchor="nw") if on_create_success: on_create_success(component, component_info, master) # 创建children for child in component_info.get("children", ()): if component == None: print("create_component error component=" + child["component_name"]) continue master2 = component.get_child_master() self.create_component_by_info(master2, child, is_init_main, on_create_success) return component def on_component_create(self, tab_num, is_quick_create, component, component_info, master): """ 控件创建成功回调 :param tab_num: 标签编号 :param is_quick_create: 是否是从功能快捷键创建的 :param component: 控件 :param component_info: 控件信息 :param master: 父控件 :return: None """ data = self.file_tab_window.get_data(tab_num) master_edit_component = self.find_edit_component_by_component(master, data["path"]) if master_edit_component is None: print("create component error", component_info) return if not hasattr(component, "get_child_master"): component.get_child_master = partial(self.default_get_child_master, component) edit_component = editComponent(self, component, component_info, master, master_edit_component) master_edit_component.add_child(edit_component, is_quick_create) child_master = component.get_child_master() child_master.bind("<Button-1>", partial(self.on_edit_component_selected, edit_component, False)) child_master.bind("<Button-3>", partial(self.on_edit_component_button_3_click, edit_component)) child_master.bind("<B1-Motion>", partial(self.on_edit_component_motion, edit_component)) child_master.bind("<ButtonRelease-1>", partial(self.on_edit_component_btn_release, edit_component)) child_master.bind("<Configure>", partial(self.on_edit_component_configure, edit_component)) component.master.set_on_resize_complete(partial(self.on_edit_component_master_resize_complete, edit_component)) def on_edit_component_configure(self, edit_component, event): """ 当控件尺寸变化时 :param edit_component: 编辑控件 :param event: event :return: None """ edit_component.on_edit_component_configure(edit_component is self.selected_component) def on_edit_component_master_resize_complete(self, edit_component): """ 编辑控件master尺寸变化后调用 :param edit_component: 编辑控件 :return: None """ edit_component.on_edit_component_master_resize_complete() def create_control(self, quick_name, gui_name, property_dict=None): """ 创建控件 :param quick_name: 快捷按钮名字 :param gui_name: 控件名字 :return: None """ if self.get_selected_component() is None: return child_master = self.get_selected_component().component.get_child_master() if time.time() - self.created_time > 2: self.created_pos_x = 0 self.created_pos_y = 0 control_name = self.create_random_name(gui_name) prop = { "background": "white", "x": self.created_pos_x, "y": self.created_pos_y, "component_name": control_name, "gui_type": gui_name, } if child_master["background"]== "white": prop["background"] = "SystemScrollbar" # 创建一个frame套在真正的控件外面 frame_prop = { "background": child_master["background"], } frame, info = create_default_component(child_master, "SelectedCanvas", "None", frame_prop, False) # 创建控件 if property_dict is not None: property_dict["component_name"] = control_name property_dict["is_main"] = 0 property_dict["x"] = prop["x"] property_dict["y"] = prop["y"] component = create_component_from_dict(frame, property_dict) else: component, property_dict = create_default_component(frame, gui_name, control_name, prop) frame.place_configure(x=int(property_dict["x"]), y=int(property_dict["y"]), anchor=property_dict["anchor"]) component.place_configure(x=0, y=0, anchor="nw") self.on_component_create(self.file_tab_window.get_cur_tab(), True, component, property_dict, child_master) self.created_time = time.time() self.created_pos_x += 10 self.created_pos_y += 10
-
修改componentEdited.py,删除初始化的can_not_sizing,选中控件时显示选中框,并且降低选中框的层级,这样选中框就不会覆盖在其他控件上,取消选中时隐藏选中框,编辑控件尺寸变化的时候修改选中框的尺寸,选中框尺寸变化的时候修改编辑控件的尺寸。
class editComponent(ComponentDragAble): def __init__(self, editor, component, component_info, component_master, parent): ComponentDragAble.__init__(self) self.editor = editor self.component = component self.component_info = component_info self.component_master = component_master self.parent = parent self.children = [] self.can_not_move = ("Toplevel",) def on_edit_component_select(self, is_tree_select): """ 被选中时 :return: None """ self.editor.property_list.add_show_rows(self) if not is_tree_select: self.editor.treeview.tree.selection_set(self.name) self.component.master.place( x=int(self.component_info["x"]) - 8, y=int(self.component_info["y"]) - 8, width=int(self.component_info["pixel_width"]) + 16, height=int(self.component_info["pixel_height"]) + 16 ) self.component.place(x=8, y=8) self.component.master.tk.call('lower', self.component.master._w) self.component.master.show() def on_edit_component_cancel_select(self): """ 取消选中时 :return: None """ if not self.component or not self.component.master: return self.component.master.place( x=self.component_info["x"], y=self.component_info["y"], width=self.component_info["pixel_width"], height=self.component_info["pixel_height"] ) self.component.place(x=0, y=0) self.component.master.hide() def on_edit_component_configure(self, is_selected): """ 当编辑控件尺寸变化时 :return: None """ width = self.component.winfo_width() height = self.component.winfo_height() if is_selected: width += 16 height += 16 update_single_prop(self.component.master, "pixel_width", width, "SelectedCanvas") update_single_prop(self.component.master, "pixel_height", height, "SelectedCanvas") if is_selected: self.component.master.after_idle(self.component.master.show) def on_edit_component_master_resize_complete(self): """ 编辑控件master尺寸变化后调用 :return: None """ width = self.component.master.winfo_width() - 16 height = self.component.master.winfo_height() - 16 self.update_property({"pixel_width": width, "pixel_height": height}) def update_property(self, prop_dict, not_update=None): """ 更新属性 :param prop_dict: key:属性名,value:属性值 :param not_update: 不更新的 :return: None """ try: # 更新属性信息 self.component_info.update(prop_dict) # 更新属性列表 if not_update != "prop_list": self.editor.property_list.update_property(self.component_info, prop_dict.keys()) # 更新控件属性 if not_update != "component": for prop_name, prop_value in prop_dict.items(): # 更改坐标的话修改控件的parent if prop_name in ("x", "y"): update_single_prop(self.component.master, prop_name, int(prop_value) - 8, "SelectedCanvas") continue update_single_prop(self.component, prop_name, prop_value, self.gui_type) # 更改图片的话更新一下尺寸 if prop_name == "image" and prop_value not in ("", "None"): self.component.after_idle(self.update_property, { "pixel_width": self.component.winfo_reqwidth(), "pixel_height": self.component.winfo_reqheight(), }, "component" ) # 更改背景的话更新一下child的背景 if prop_name == "background": for child in self.children: update_single_prop(child.component.master, "background", prop_value, "SelectedCanvas") pass # 更新树 if "component_name" in prop_dict: self.editor.refresh_tree() self.editor.treeview.tree.selection_set(prop_dict["component_name"]) except Exception as e: print(e)
-
删除componentDragAble中改变尺寸的逻辑。
-
其他一些修改就不贴了。
本文地址:https://blog.csdn.net/m0_51849494/article/details/110880443