使用tkinter制作tkinterUI编辑器
前言
这篇文章记录新建ui以及打开ui的功能。
先导入一下需要用到的库,tkinterEditor.py:
import uuid
from functools import partial
from tkinter.filedialog import askopenfilename, asksaveasfilename, askdirectory
from componentEdited import editComponent
from componentProperty import get_default_component_info
from components import create_component_from_dict, create_default_component
一、可编辑的控件类实现
代码如下,componentEdited.py
#!/usr/bin/python
# -*- coding: utf-8 -*-
from tkinter import END
class editComponent:
def __init__(self, editor, component, component_info, component_master, parent):
self.editor = editor
self.component = component
self.component_info = component_info
self.component_master = component_master
self.parent = parent
self.children = []
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)
说明:
-
editor是编辑器的实例,component是真正编辑的控件,component_info是真正编辑的控件属性字典,component_master是真正编辑的控件master,parent是editComponent实例的parent,children存储editComponent实例的child
-
find_edit_component根据真正编辑的控件找到editComponent实例
-
add_child中的is_quick_create在通过快捷功能创建时调用有特殊的逻辑,之后创建控件的时候进行处理
-
修改tkinterEditor.py初始化函数,添加管理所有editComponent的字典,如下:
class tkinterEditor(componentMgr): def __init__(self, master, gui_path): componentMgr.__init__(self, master) self.config_parser = ToolConfigParser() self.config_parser.read("default.ini", encoding="utf-8-sig") self.load_from_xml(master, gui_path, True) self.theme = EDITOR_THEME_DEFAULT # 主题 self.right_edit_menu = None # 鼠标右键edit菜单 self.edit_components = {} # 存储可编辑的控件 self.init_frame()
二、创建UI
修改tkinterEditor.py,代码如下:
class tkinterEditor(componentMgr):
def new_gui(self, file_path=None, prop=None, component_name=None):
"""
新建ui
:param file_path: 文件路径
:param prop: 额外属性
:param component_name: 控件名字
:return: None
"""
if file_path is None:
file_path = asksaveasfilename(title=u"选择文件", filetypes=[("xml files", "xml"),])
if not file_path:
return
if not file_path.endswith(".xml"):
file_path += ".xml"
if component_name is None:
component_name = self.create_random_name("frame")
bg = "grey" if self.get_theme() == EDITOR_THEME_WHITE else "white"
prop_update = {
"gui_type": "Frame",
"is_main": 1,
"component_name": component_name,
"title": "demo",
"background": bg,
}
component_info = get_default_component_info("Frame", prop_update)
if prop is not None:
component_info.update(prop)
self.xml_parser.write_dict_to_xml(file_path, {component_name : component_info,}, "root", ("self_component", "parent_component",))
self._open_gui(file_path)
def create_random_name(self, gui_name):
"""
随机生成一个名字
:param gui_name: 控件名字
:return: string
"""
return gui_name.lower() + "_" + str(uuid.uuid1().hex)
def _open_gui(self, file_path):
"""
读取ui公共操作
:param file_path: 文件路径
:return: None
"""
file_name = os.path.basename(file_path)
bg = EDITOR_THEME[self.theme]
self.file_tab_window.add_tab({"label_text": file_name}, {"background": bg}, {"path": file_path})
说明:
- 创建ui基本上就是调用之前写好的接口创建一个xml文件,然后直接走打开ui的逻辑
- create_random_name生成随机的名字,使用uuid.uuid1().hex创建一个随机的id
三、读取UI
修改tkinterEditor.py,代码如下:
class tkinterEditor(componentMgr):
def open_gui(self):
"""
读取ui文件
:return: None
"""
file_path = askopenfilename(title=u"选择文件", filetypes=[("xml files", "xml"), ("all files", "*")])
if not file_path:
return
if file_path in self.edit_components:
print("该文件已经打开")
return
self._open_gui(file_path)
def init_frame(self):
"""
初始化ui
:return: None
"""
self.init_menu()
self.init_theme()
self.init_file_tab_window()
################################################ tab control ################################################
def init_file_tab_window(self):
"""
初始化file_tab_window
:return:
"""
self.file_tab_window.set_on_add_tab(self.on_gui_open)
def on_gui_open(self, tab_num):
"""
当文件读取成功时调用
:param tab_num: tab编号
:return: None
"""
tab_frame = self.file_tab_window.get_tab_frame(tab_num)
data = self.file_tab_window.get_data(tab_num)
edit_component = editComponent(self, tab_frame.get_child_master(), {"component_name": "None"}, None, None)
self.edit_components[data["path"]] = edit_component
components = self.load_from_xml(tab_frame.get_child_master(), data["path"], False, partial(self.on_component_create, tab_num, False))
edit_component.set_component_info(components)
tab_frame.update_scroll()
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, "Frame", "None", frame_prop, False)
# 创建控件
component = create_component_from_dict(frame, component_info)
frame.configure(width=component.winfo_reqwidth() + 4, height=component.winfo_reqheight() + 4)
# 以下控件需要重新修改宽和高
if gui_type in ("Progressbar", "Scrollbar"):
frame.configure(width=int(component_info["width"]) + 4, height=int(component_info["height"]) + 4)
frame.place_configure(x=component_info["x"], y=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)
def find_edit_component_by_component(self, component, path):
"""
根据控件获取编辑的控件
:param component: 控件
:return: EditComponent
"""
if path not in self.edit_components:
return None
return self.edit_components[path].find_edit_component(component)
def default_get_child_master(self, component):
"""
此函数是为了支持我自己写的控件
:param component: 控件
:return: 控件
"""
return component
说明:
- init_file_tab_window函数中注册打开ui的回调
- on_gui_open函数中创建一个editComponent存储这个ui的信息,然后调用load_from_xml读取ui并进行创建
- 重写了componentMgr的create_component_by_info函数,主要是为了选中控件时的效果,以后我要是觉得修改属性麻烦时可能会去掉
- on_component_create是控件创建成功的回调,在这里创建editComponent实例并加到父editComponent中