欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

【记录】python3 使用tkinter制作tkinterUI编辑器 《二十》使用canvas重构选中框

程序员文章站 2022-04-02 11:09:28
使用tkinter制作tkinterUI编辑器目录使用tkinter制作tkinterUI编辑器前言一、实现新的选中控件二、选中控件的使用前言这篇记录记录一下选中框的重构,内容改动较大,可能会出现一些问题,所以目前只在1.0分支中提交。完整代码已上传到github,可从第一篇记录下载 提示:1.由于代码改动较大,之前创建的工程都不好使了,需要重新创建。2.为了让所有控件都能拖动,增加了两个属性,pixel_width,pixel_height,修改这两个属性就能改变控件的尺寸,其实就是调...

使用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个方向拖拽修改控件的大小:
【记录】python3 使用tkinter制作tkinterUI编辑器 《二十》使用canvas重构选中框


一、实现新的选中控件

先上代码,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"

说明:

  1. 控件继承Canvas,主要利用了canvas的画矩形功能。
  2. 初始化后绘制9个矩形,1个显示边框,8个用来调整大小,然后给调整大小的矩形绑定事件。
  3. show函数将9个矩形显示出来,使用coords函数重新修改矩形的位置,is_fill参数会填充8个小矩形的颜色,之后做多重选择的时候使用。
  4. hide函数将矩形移动到看不见的位置。
  5. on_mouse_move函数用来修改自己的尺寸,目前只支持anchor=’nw’的情况,移动后需要使用after_idle函数延迟调用show函数,因为修改尺寸后控件还没有更新,调用winfo_width,winfo_height获取不到真正的尺寸。

二、选中控件的使用

  1. 修改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
    
  2. 修改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)
    
  3. 删除componentDragAble中改变尺寸的逻辑。

  4. 其他一些修改就不贴了。


上一篇记录

本文地址:https://blog.csdn.net/m0_51849494/article/details/110880443

相关标签: python tkinter