【记录】python3 使用tkinter制作tkinterUI编辑器 《十三》

使用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

完整代码已上传到github,可从第一篇记录下载


一、可编辑的控件类实现

代码如下,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)

说明:

  1. editor是编辑器的实例,component是真正编辑的控件,component_info是真正编辑的控件属性字典,component_master是真正编辑的控件master,parent是editComponent实例的parent,children存储editComponent实例的child

  2. find_edit_component根据真正编辑的控件找到editComponent实例

  3. add_child中的is_quick_create在通过快捷功能创建时调用有特殊的逻辑,之后创建控件的时候进行处理

  4. 修改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})

说明:

  1. 创建ui基本上就是调用之前写好的接口创建一个xml文件,然后直接走打开ui的逻辑
  2. 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

说明:

  1. init_file_tab_window函数中注册打开ui的回调
  2. on_gui_open函数中创建一个editComponent存储这个ui的信息,然后调用load_from_xml读取ui并进行创建
  3. 重写了componentMgr的create_component_by_info函数,主要是为了选中控件时的效果,以后我要是觉得修改属性麻烦时可能会去掉
  4. on_component_create是控件创建成功的回调,在这里创建editComponent实例并加到父editComponent中

上一篇记录 下一篇记录

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

archmage199

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值