使用tkinter制作tkinterUI编辑器
前言
这篇文章记录一下属性的编辑逻辑,先把EditorPropertyList中的key_click函数注释调,目前不用,更新了一下componentProperty.py,增加了一个更新一条属性的函数。
一、修改tkinterEditor.py
先上代码:
class tkinterEditor(componentMgr):
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))
def on_edit_component_selected(self, edit_component, is_tree_select, event):
"""
当控件被选中
:param edit_component: 编辑控件
:param event: event
:return: None
"""
edit_component.handle_mouse_click(event, edit_component.component)
if self.get_selected_component() is edit_component:
return
self.cancel_select_component()
self.set_selected_component(edit_component)
edit_component.on_edit_component_select(is_tree_select)
return "break"
def on_edit_component_motion(self, edit_component, event):
"""
当控件移动时
:param edit_component: 编辑控件
:param event: event
:return: None
"""
edit_component.handle_mouse_motion(event, edit_component.component)
def on_edit_component_btn_release(self, edit_component, event):
"""
当控件移动停止时
:param edit_component: 编辑控件
:param event: event
:return: None
"""
edit_component.handle_mouse_release(event, edit_component.component)
说明:
- on_component_create函数中添加了绑定移动鼠标与释放鼠标左键的事件,这两个事件处理拖拽控件的逻辑
- on_edit_component_selected函数添加了edit_component.handle_mouse_click,是为了处理拖拽控件的逻辑
二、添加拖拽模块
先上代码,componentDragAble.py
#!/usr/bin/python
# -*- coding: utf-8 -*-
import re
geometry_pattern = re.compile("(\d+)x(\d+)\+(-?\d+)\+(-?\d+)")
# 可拖拽模块
class ComponentDragAble:
def __init__(self):
# 拖拽起始坐标x
self.start_x = 0
# 拖拽起始坐标y
self.start_y = 0
# 是否允许横向拖拽
self.is_able_x = True
# 是否允许纵向拖拽
self.is_able_y = True
# 是否进行横向边缘检测
self.is_check_side_x = False
# 是否进行纵向边缘检测
self.is_check_side_y = False
# 是否正在拖拽
self.is_dragging = False
# 旧光标图标
self.old_cursor = None
# 是否在改变大小
self.is_sizing = False
# 旧宽度
self.old_width = 0
# 旧高度
self.old_height = 0
# 禁止设置大小
self.can_not_sizing = ()
# 禁止移动
self.can_not_move = ()
def set_is_able_x(self, is_able_x):
if self.is_able_x == is_able_x:
return
self.is_able_x = is_able_x
def set_is_able_y(self, is_able_y):
if self.is_able_y == is_able_y:
return
self.is_able_y = is_able_y
def set_is_check_side_x(self, is_check_side_x):
if self.is_check_side_x == is_check_side_x:
return
self.is_check_side_x = is_check_side_x
def set_is_check_side_y(self, is_check_side_y):
if self.is_check_side_y == is_check_side_y:
return
self.is_check_side_y = is_check_side_y
def get_parent_geometry(self, component):
geometry = component.master.master.winfo_geometry()
matched = geometry_pattern.match(geometry)
return matched.groups()
def cursor_in_right_bottom(self, component):
"""
检查光标是否在界面右下角
:param component: 控件
:return: bool
"""
if self.start_x > component.winfo_reqwidth() - 10 and self.start_y > component.winfo_reqheight() - 10:
return True
return False
def on_begin_drag(self):
"""
开始拖拽时触发
:return:None
"""
self.is_dragging = True
def on_end_drag(self, component):
"""
结束拖拽时触发
:param component: 控件
:return:None
"""
self.is_dragging = False
if hasattr(self.get_real_component(component).master, "master") and \
self.get_real_component(component).master.master is not None and \
hasattr(self.get_real_component(component).master.master, "on_end_drag_master"):
component.master.master.master.on_end_drag_master()
def on_dragging(self):
"""
拖拽中触发
:return:
"""
pass
def handle_mouse_click(self, event, component):
"""
点击鼠标左键触发
:param event: event
:param component: 控件
:return: None
"""
if event is None:
return
if component.__class__.__name__ in self.can_not_move:
return
if self.is_able_x:
self.start_x = event.x
if self.is_able_y:
self.start_y = event.y
if self.is_able_x or self.is_able_y:
self.on_begin_drag()
self.old_cursor = component["cursor"]
if self.cursor_in_right_bottom(component) and component.__class__.__name__ not in self.can_not_sizing:
component.configure(cursor="sizing")
self.is_sizing = True
self.old_width = component.winfo_reqwidth()
self.old_height = component.winfo_reqheight()
def change_width(self, component, width):
"""
修改宽度
:param component: 控件
:param width: 宽度
:return: None
"""
component.configure(width=width)
def change_height(self, component, height):
"""
修改高度
:param component: 控件
:param height: 高度
:return: None
"""
component.configure(height=height)
def change_pos_x(self, component, pos_x):
"""
修改x
:param component: 控件
:param pos_x: x
:return: None
"""
component.place_configure(x=pos_x)
def change_pos_y(self, component, pos_y):
"""
修改y
:param component: 控件
:param pos_y: y
:return: None
"""
component.place_configure(y=pos_y)
def get_real_component(self, component):
return component
def handle_mouse_motion(self, event, component):
"""
鼠标左键点击并移动
:param event:事件
:param component: 控件
:return:None
"""
if event is None:
return
if component.__class__.__name__ in self.can_not_move:
return
if not self.is_dragging:
return
# 获取parent宽高
groups = self.get_parent_geometry(component)
if self.is_able_x:
# 计算要设置的位置
to_x = event.x - self.start_x + int(self.get_real_component(component).place_info()["x"])
# 对位置进行边缘检测
if self.is_check_side_x:
if to_x < 0:
to_x = 0
elif to_x + component.winfo_reqwidth() > int(groups[0]):
to_x = int(groups[0]) - component.winfo_reqwidth()
# 光标在右下角,设置大小
if self.is_sizing:
width = max(0, self.old_width + (event.x - self.start_x))
self.change_width(component, width)
else:
# 设置坐标
if component.__class__.__name__ not in self.can_not_move:
self.change_pos_x(component, to_x)
if self.is_able_y:
# 计算要设置的位置
to_y = event.y-self.start_y+int(self.get_real_component(component).place_info()["y"])
# 对位置进行边缘检测
if self.is_check_side_y:
if to_y < 0:
to_y = 0
elif to_y + component.winfo_reqheight() > int(groups[1]):
to_y = int(groups[1]) - component.winfo_reqheight()
# 光标在右下角,设置大小
if self.is_sizing:
height = max(0, self.old_height + (event.y - self.start_y))
self.change_height(component, height)
else:
# 设置坐标
if component.__class__.__name__ not in self.can_not_move:
self.change_pos_y(component, to_y)
if self.is_able_x or self.is_able_y:
self.on_dragging()
def handle_mouse_release(self, event, component):
"""
鼠标左键释放触发
:param event:事件
:param component: 控件
:return:None
"""
if event is None:
return
if component.__class__.__name__ in self.can_not_move:
return
if self.is_able_x:
self.start_x = 0
if self.is_able_y:
self.start_y = 0
if self.is_able_x or self.is_able_y:
self.on_end_drag(component)
component.configure(cursor=self.old_cursor)
self.is_sizing = False
说明:
- 鼠标左键点击控件并移动鼠标会修改控件的坐标
- 鼠标左键点击控件右下角然后移动鼠标可以改变控件的尺寸
- 凡是text类型的控件都不支持改变尺寸,这种类型的尺寸不好计算,以后有时间了再看看能不能解决
三、修改componentEdited.py,继承ComponentDragAble
代码如下:
#!/usr/bin/python
# -*- coding: utf-8 -*-
from tkinter import END
from componentProperty import update_single_prop
from componentDragAble import ComponentDragAble
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")
self.can_not_sizing = (
"Label", "Button", "Checkbutton", "Entry", "Text", "Listbox", "Scrollbar", "Toplevel",
"Treeview", "Message", "Radiobutton", "Scale", "Spinbox", "Separator", "Progressbar"
)
def set_component_info(self, component_info):
if self.component_info is component_info:
return
self.component_info = component_info
def get_component_info(self):
return self.component_info
@property
def name(self):
return self.component_info["component_name"]
@property
def gui_type(self):
return self.component_info["gui_type"]
def find_edit_component(self, component):
"""
查找
:param component: 控件
:return: editComponent
"""
if component is self.component:
return self
for child in self.children:
m = child.find_edit_component(component)
if m is None:
continue
return m
return None
def add_child(self, edit_component, is_quick_create):
"""
增加child
:param edit_component: editComponent
:param is_quick_create: 是否是从功能快捷键创建的
:return: None
"""
self.children.append(edit_component)
def select_first_child(self):
"""
选中第一个child
:return: None
"""
if len(self.children) <= 0:
return
child = self.children[0]
self.editor.on_edit_component_selected(child, False, None)
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.configure(highlightthickness=2)
def on_edit_component_cancel_select(self):
"""
取消选中时
:return: None
"""
if not self.component or not self.component.master:
return
self.component.master.configure(highlightthickness=0)
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", "anchor"):
update_single_prop(self.component.master, prop_name, prop_value, "Frame")
continue
update_single_prop(self.component, prop_name, prop_value, self.gui_type)
# 更改图片的话更新一下尺寸
if prop_name == "image" and prop_value not in ("", "None"):
self.update_property({"width":0, "height":0,}, "component")
# 更改尺寸相关的话修改控件的parent
if prop_name in ("image", "width", "height", "borderwidth", "highlightthickness", "padx", "pady", "relief"):
width = self.component.winfo_reqwidth() + 4
height = self.component.winfo_reqheight() + 4
if self.gui_type in ("Progressbar", "Scrollbar"):
width = int(self.component_info["width"]) + 4
height = int(self.component_info["height"]) + 4
update_single_prop(self.component.master, "width", width, "Frame")
update_single_prop(self.component.master, "height", height, "Frame")
# 更改背景的话更新一下child的背景
if prop_name == "background":
for child in self.children:
update_single_prop(child.component.master, "background", prop_value, "Frame")
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)
def get_real_component(self, component):
return component.master
def change_width(self, component, width):
"""
修改宽度
:param component: 控件
:param width: 宽度
:return: None
"""
self.update_property({"width": width,})
def change_height(self, component, height):
"""
修改高度
:param component: 控件
:param height: 高度
:return: None
"""
self.update_property({"height": height,})
def change_pos_x(self, component, pos_x):
"""
修改x
:param component: 控件
:param pos_x: x
:return: None
"""
self.update_property({"x": pos_x, })
def change_pos_y(self, component, pos_y):
"""
修改y
:param component: 控件
:param pos_y: y
:return: None
"""
self.update_property({"y": pos_y, })
说明:
- update_property函数在属性编辑框编辑属性后或者使用鼠标移动控件后调用,用来更新控件或者编辑框的属性
- 重写了componentDragAble的get_real_component,change_width,change_height,change_pos_x,change_pos_y,因为我希望ComponentDragAble以后在别的地方也可以使用,在编辑器移动控件时需要移动控件的master