【记录】python3 使用tkinter制作tkinterUI编辑器 《十九》,将python解释器嵌入到控件中

使用tkinter制作tkinterUI编辑器



前言

最近在思考多重选择怎么做时又研究了一下vs里的c#编辑器,突然想实现一下它的选中效果,如下:
在这里插入图片描述
感觉不太好实现,我希望能在编辑器中调试这个东西,不用每次改个属性就重启编辑器,所以决定将python解释器嵌入到编辑器里,直接在编辑器里调试好我想要的属性然后再修改编辑器的代码,这样能省不少时间。

解释器控件效果图如下,在下面输入python语句,上面输出结果:
在这里插入图片描述

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


一、使用方法

  1. 在debug菜单点击open_debug_window或者alt+p打开解释器
  2. 解释器开着的时候使用esc关闭解释器
  3. 输入python语句,按alt+回车或者alt+s发送命令
  4. alt+上或者alt+下可以在当前行插入历史输入信息
  5. 可以在输入框调用编辑器(tkinterEditor类)的函数或者修改属性,例如:在输入框输入Editor.change_theme()然后发送就可以直接修改主题

二、实现自己的解释器类与解释器控件

先上代码,debugInterpreter.py

#!/usr/bin/python
# -*- coding: utf-8 -*-

import sys

from tkinter import INSERT, END, Toplevel, Text
from code import InteractiveInterpreter
from WidgetRedirector import WidgetRedirector

from componentProperty import update_all_property, get_default_component_info


def create_default_component(master, component_type, component_name, prop=None, use_name=True):
    """
    创建默认控件
    :param master: 父控件
    :param component_type: 控件类型
    :param component_name: 控件名字
    :param prop: 需要更新的属性
    :param use_name: 是否使用控件名字
    :return: 控件
    """
    class_name = getattr(sys.modules[__name__], component_type)
    if use_name:
        component = class_name(master, name=component_name)
    else:
        component = class_name(master)

    component_info = get_default_component_info(component_type, prop)
    update_all_property(component, component_info, component_type)

    return component, component_info


class DebugInterpreter(InteractiveInterpreter):

    def __init__(self, master, locals=sys.modules["__main__"].__dict__):
        InteractiveInterpreter.__init__(self, locals=locals)
        self.master = master
        sys.stdout.write = master.debug_write
        sys.stderr.write = master.debug_error_write

    def send_msg(self, msg):
        """
        发送消息
        :param msg: 消息
        :return: None
        """
        file_name = "DebugInterpreter"
        lines = msg.split("\n")

        symbol = "single"
        if len(lines) > 1:
            symbol = "exec"

        self.runsource(msg, file_name, symbol)


class DebugInterpreterFrame(Toplevel):

    def __init__(self, master=None, cnf={}, **kw):
        Toplevel.__init__(self, master, cnf, **kw)
        self.input_width = 10
        self.input_height = 1
        self.input_pos_y = 1
        self.output_width = 10
        self.output_height = 1
        self.debug_history = []                             # 存储调试历史信息
        self.cur_debug_history_index = 0                    # 当前调试历史信息索引
        self.max_debug_history = 50                         # 最多存储调试历史条数
        self.debug_interpreter = DebugInterpreter(self)

    def set_input_width(self, input_width):
        if self.input_width == input_width:
            return
        self.input_width = input_width
        self.do_layout()

    def get_input_width(self):
        return self.input_width

    def set_input_height(self, input_height):
        if self.input_height == input_height:
            return
        self.input_height = input_height
        self.do_layout()

    def get_input_height(self):
        return self.input_height

    def set_output_width(self, output_width):
        if self.output_width == output_width:
            return
        self.output_width = output_width
        self.do_layout()

    def get_output_width(self):
        return self.output_width

    def set_output_height(self, output_height):
        if self.output_height == output_height:
            return
        self.output_height = output_height
        self.do_layout()

    def get_output_height(self):
        return self.output_height

    def set_input_pos_y(self, input_pos_y):
        if self.input_pos_y == input_pos_y:
            return
        self.input_pos_y = input_pos_y
        self.do_layout()

    def get_input_pos_y(self):
        return self.input_pos_y

    @property
    def debug_input(self):
        return self.children.get("debug_input", None)

    @property
    def debug_output(self):
        return self.children.get("debug_output", None)

    def on_update(self):
        """
        初始化后会被调用,在这里创建输入框和输出框
        :return: None
        """
        input_prop = {
            "background": "white", "foreground": "black",
            "width": self.get_input_width(), "height": self.get_input_height(),
        }
        create_default_component(self, "Text", "debug_input", input_prop)

        output_prop = {
            "background": "black", "foreground": "white",
            "width": self.get_output_width(), "height": self.get_output_height()
        }
        create_default_component(self, "Text", "debug_output", output_prop)
        self.do_layout()

        copy_right = 'Type "help", "copyright", "credits" or "license" for more information.'
        self.debug_interpreter.write("Python %s on %s\n%s\n(%s)\n" % (sys.version, sys.platform, copy_right,self.__class__.__name__))

        redir = WidgetRedirector(self.debug_input)

        def input_insert(*args):
            if args[1] == "\n":
                self.save_debug_history()
            original_insert(*args)

        def input_delete(*args):
            original_delete(*args)

        original_insert = redir.register("insert", input_insert)
        original_delete = redir.register("delete", input_delete)

        def send_msg():
            msg = self.debug_input.get(0.0, "end")
            msg = msg.strip("\n")
            if not msg:
                return
            self.save_debug_history()
            self.debug_interpreter.send_msg(msg)
            self.debug_input.delete(0.0, "end")
            return "break"

        def press_alt_up(event):
            self.insert_debug_history(-1)
            return 'break'

        def press_alt_down(event):
            self.insert_debug_history(1)
            return 'break'

        self.debug_input.bind("<Alt-Key-Return>", lambda event: send_msg())
        self.debug_input.bind("<Alt-Key-s>", lambda event: send_msg())
        self.debug_input.bind("<Alt-Up>", lambda event: press_alt_up(event))
        self.debug_input.bind("<Alt-Down>", lambda event: press_alt_down(event))

    def save_debug_history(self):
        """
        保存调试历史信息
        :return: None
        """
        line_row_no = self.debug_input.index("insert").split('.')[0]
        msg = self.debug_input.get('{}.0'.format(line_row_no), 'insert')
        if not msg:
            return

        self.debug_history.append(msg)
        if len(self.debug_history) >= self.max_debug_history:
            self.debug_history.pop(0)
        self.cur_debug_history_index = len(self.debug_history)

    def insert_debug_history(self, step):
        """
        插入历史信息
        :param step: 步长
        :return: None
        """
        if len(self.debug_history) == 0:
            return

        new_index = self.cur_debug_history_index + step
        line_row_no = self.debug_input.index("insert").split('.')[0]
        self.debug_input.delete('%s.0' % line_row_no, 'insert')

        if step > 0 and new_index >= len(self.debug_history):
            return

        if step < 0 and new_index < 0:
            new_index = len(self.debug_history) - 1

        self.cur_debug_history_index = new_index

        history = self.debug_history[self.cur_debug_history_index]
        self.debug_input.insert('%s.0' % line_row_no, history)

    def do_layout(self):
        """
        布局
        :return: None
        """
        # todo:之后输入与输出框改成动态计算的,目前先不处理

        self.debug_output.configure(width=self.get_output_width(), height=self.get_output_height())
        self.debug_input.configure(width=self.get_input_width(), height=self.get_input_height())
        self.debug_output.place(x=0, y=0, anchor='nw')
        self.debug_input.place(x=0, y=self.get_input_pos_y(), anchor='nw')

    def debug_write(self, msg):
        """
        调试窗口写函数回调
        :param msg: 写的内容
        :return: None
        """
        if msg == "\n":
            return

        str_output = ">>> "
        self.debug_output.insert("end", str_output + msg + "\n")
        self.debug_output.see("end")

    def debug_error_write(self, msg):
        """
        调试窗口error函数回调
        :param msg: error的内容
        :return: None
        """
        str_output = ">>> "
        self.debug_output.insert("end", str_output + msg + "\n")
        self.debug_output.see("end")

  1. 解释器类继承InteractiveInterpreter,初始化时将系统输出与错误的write函数赋值成解释器控件类里相对应的函数,这样就能监听到系统的输出与错误然后显示在想显示的地方,我这里是将信息输出到一个Text控件里
  2. 解释器实现一个发送消息的函数,调用runsource函数处理,symbol是“single”的话只能处理一条语句,例如print(”hello workd“),symbol是“exec”的话可以处理多条语句,例如for i in range(10): print(i)
  3. 解释器控件中的输入框与输出框的尺寸以及输入框的y坐标目前是写到配置文件中的,以后考虑改成按照父控件进行计算的
  4. 解释器控件中的debug_history是用来存储历史输入信息的,输入框每输入一次回车就把这一行记录进去,之后通过上下键进行快速输入
  5. 解释器控件输入框又使用了WidgetRedirector监听insert与delete事件,之前属性列表控件那里也用了,目前这里只处理了保存历史记录,之后我还想加一个简单的代码补全功能
  6. self.debug_input.index(“insert”).split(’.’)[0]这句可以获取输入框当前行的行号

三、将解释器控件加入编辑器

  1. 先将解释器控件加到componentProperty.py与components.py中,代码这里就不贴了

  2. 在配置文件default.ini的menu中添加一个菜单用来打开解释器debug = open_debug_window#alt+p

  3. 将解释器ui加到tkinterEditor.xml中,

    			<debug_window>
    				<anchor>nw</anchor>
    				<alpha>0.8</alpha>
    				<background>black</background>
    				<component_name>debug_window</component_name>
    				<height>990</height>
    				<input_height>5</input_height>
    				<input_width>274</input_width>
    				<input_pos_y>977</input_pos_y>
    				<output_height>72</output_height>
    				<output_width>274</output_width>
    				<x>735</x>
    				<y>450</y>
    				<positionfrom>program</positionfrom>
    				<title>debug</title>
    				<topmost>1</topmost>
    				<toolwindow>1</toolwindow>
    				<takefocus>0</takefocus>
    				<width>1920</width>
    				<gui_type>DebugInterpreterFrame</gui_type>
    			</debug_window>
    
  4. 修改tkinterEditor.py

    Editor = None
    
    
    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.selected_component = None                                          # 当前被选中的控件
            self.created_time = 0                                                   # 创建控件时的时间
            self.created_pos_x = 0                                                  # 创建控件时的坐标x
            self.created_pos_y = 0                                                  # 创建控件时的坐标y
            self.is_new_project_show = True                                         # 创建新project界面是否显示
            self.is_debug_window_show = True                                        # 调试窗口界面是否显示
            self.copied_component = None                                            # 复制的控件信息
            self.init_frame()
    
        @property
        def debug_window(self):
            return self.editor_window.children.get("debug_window", None)
    
        def init_frame(self):
            """
            初始化ui
            :return: None
            """
            self.init_menu()
            self.init_theme()
            self.init_file_tab_window()
            self.init_property_list()
            self.init_treeview()
            self.init_quick_btn()
            self.init_top_level()
            self.init_hot_key()
            self.init_debug_window()
    
        ############################################## debug menu #################################################
    
        def open_debug_window(self):
            """
            打开调试窗口
            :return: None
            """
            self.change_debug_window_ui()
    
        ############################################### hot key ###################################################
    
        def init_hot_key(self):
            """
            初始化快捷键
            :return: None
            """
            self.master.bind("<Control-s>", lambda event: self.save_gui())
            self.master.bind("<Control-o>", lambda event: self.open_gui())
            self.master.bind("<Control-n>", lambda event: self.new_gui())
            self.master.bind("<Control-p>", lambda event: self.new_project())
            self.master.bind("<Control-Delete>", lambda event: self.delete_control())
            self.master.bind("<Control-comma>", lambda event: self.copy())
            self.master.bind("<Control-period>", lambda event: self.paste())
            for k in ("Up", "Down", "Left", "Right"):
                self.master.bind("<Control-{0}>".format(k), partial(self.move_control, k))
            self.master.bind("<Alt-p>", lambda event: self.change_debug_window_ui())
    
        ########################################### debug window ##################################################
    
        def change_debug_window_ui(self):
            """
            打开关闭new_project界面
            :return: None
            """
            if self.is_debug_window_show:
                self.is_debug_window_show = False
                self.debug_window.withdraw()
                self.debug_window.master.master.wm_attributes('-disabled', False)
                self.debug_window.overrideredirect(False)
                return
            self.is_debug_window_show = True
            self.debug_window.deiconify()
            self.debug_window.master.master.wm_attributes('-disabled', True)
            self.debug_window.overrideredirect(True)
            self.debug_window.geometry("{}x{}+{}+{}".format(self.master.winfo_width(), self.master.winfo_height() + 52, self.master.winfo_x() + 7, self.master.winfo_y()))
            self.debug_window.do_layout()
    
        def init_debug_window(self):
            """
            初始化调试窗口
            :return: None
            """
            self.change_debug_window_ui()
            self.debug_window.bind("<KeyRelease-Escape>", lambda event: self.change_debug_window_ui())
    
    def main():
        root = Tk()
        #root.resizable(0, 0)
        path = os.path.join(os.getcwd(), "tkinterEditor.xml")
        global Editor
        Editor = tkinterEditor(root, path)
        root.mainloop()
    
  5. 添加了一个全局变量Editor,在解释器中就可以使用这个变量调用tkinterEditor类里面的函数

  6. change_debug_window_ui函数处理了解释器控件的打开与关闭,目前我还没有找到怎么判断toplevel是否显示,只能自己先加一个变量控制


上一篇记录 下一篇记录

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

archmage199

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

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

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

打赏作者

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

抵扣说明:

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

余额充值