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

使用tkinter制作tkinterUI编辑器


前言

这篇文章记录一下属性的编辑逻辑,先把EditorPropertyList中的key_click函数注释调,目前不用,更新了一下componentProperty.py,增加了一个更新一条属性的函数。

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


一、修改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)

说明:

  1. on_component_create函数中添加了绑定移动鼠标与释放鼠标左键的事件,这两个事件处理拖拽控件的逻辑
  2. 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

说明:

  1. 鼠标左键点击控件并移动鼠标会修改控件的坐标
  2. 鼠标左键点击控件右下角然后移动鼠标可以改变控件的尺寸
  3. 凡是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, })

说明:

  1. update_property函数在属性编辑框编辑属性后或者使用鼠标移动控件后调用,用来更新控件或者编辑框的属性
  2. 重写了componentDragAble的get_real_component,change_width,change_height,change_pos_x,change_pos_y,因为我希望ComponentDragAble以后在别的地方也可以使用,在编辑器移动控件时需要移动控件的master

上一篇记录 下一篇记录

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

archmage199

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

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

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

打赏作者

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

抵扣说明:

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

余额充值