界面开发—开方公式输入存图软件

界面开发—开方公式输入存图软件

在这里插入图片描述

只是一个简单的小软件。

本文要调用的库:math(内置)、re(内置)、tkinter(内置)、matplotlib、pylatexenc

import math
import re
import tkinter as tk
import tkinter.font as tkFont
import tkinter.filedialog as fg
import matplotlib.pyplot as plt
from matplotlib.pyplot import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from pylatexenc.latex2text import LatexNodes2Text

完整脚本

import math
import re
import tkinter as tk
import tkinter.font as tkFont
import tkinter.filedialog as fg
import matplotlib.pyplot as plt
from matplotlib.pyplot import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from pylatexenc.latex2text import LatexNodes2Text

# 用于转换latex转义字符串
def convert_latex_to_text(latex):
    text = LatexNodes2Text().latex_to_text(latex)
    return text

# 用于判断是否为数字。
def is_number(string):
    try:
        float(string)
        return True
    except:
        return False

# 判定是否取约数。
def is_decimal(num):
    # 分割小数点前后。
    decimal_part = str(num).split('.')
    if len(decimal_part) > 1 and len(decimal_part[1]) > 2:
        return True
    else:
        return False


class LaTexSqrt:
    def __init__(self):
        self.n = "n"
        self.x = "x"
        self.text0 = "$\sqrt[" + "{}]".format(self.n) + "{" + "{}".format(self.x) + "}$"

    def change_n(self, n):
        # 排除掉特殊符号或空造成的错误,以及输入带\的转义字符时,被LatexNodes2Text删除后只剩''造成错误的情况。
        if not bool(re.search('[a-zA-Z]', n)) and not is_number(n) or convert_latex_to_text(n) == '':
            self.n = "n"
        else:
            # 剔除后面输入中不符合Latex格式转义字符串。
            self.n = convert_latex_to_text(n)
        self.text0 = "$\sqrt[" + "{}]".format(self.n) + "{" + "{}".format(self.x) + "}$"

    def change_x(self, x):
        if not bool(re.search('[a-zA-Z]', x)) and not is_number(x) or convert_latex_to_text(x) == '':
            self.x = "x"
        else:
            self.x = convert_latex_to_text(x)
        self.text0 = "$\sqrt[" + "{}]".format(self.n) + "{" + "{}".format(self.x) + "}$"

    def sqrt_result(self):
        # 开方运算,包括开2次方以上和小数
        result = pow(float(self.x), 1 / float(self.n))
        if is_decimal(result):
            self.text0 = "$\sqrt[" + "{}]".format(self.n) + "{" + "{}".format(self.x) + "}" + "\\approx{}$".format(
                round(result, 2))
        else:
            self.text0 = "$\sqrt[" + "{}]".format(self.n) + "{" + "{}".format(self.x) + "}$" + "={}".format(
                round(result, 2))


class AppBuild:
    def __init__(self, master):
        self.master = master
        self.photo = tk.PhotoImage(file=r'.\\img\\sqrt.png')
        self.textvar = tk.StringVar()
        self.setup_wedge()
        self.right_menu()

    # 基本布局
    def setup_wedge(self):
        # 创建和布置画布区域。
        self.fig = Figure(figsize=(1.5, 1.5), dpi=100)
        self.canvas = FigureCanvasTkAgg(self.fig, master=self.master)
        # self.canvas.get_tk_widget()只有通过get_tk_widget()才能使用tkinter的方法属性,如grid和bind。
        self.canvas.get_tk_widget().grid(row=0, column=0, columnspan=3, rowspan=3)

        # 布置选择公式的开始解锁按钮。
        self.btn = tk.Button(self.master, image=self.photo, width=50)
        self.btn.grid(row=3, column=0, rowspan=2, sticky="NSEW")

        # 布置参数的输入框。
        self.entry1 = tk.Entry(width=15)
        self.entry2 = tk.Entry(width=15)
        self.entry1.grid(row=3, column=1, columnspan=2, sticky="NSEW")
        self.entry2.grid(row=4, column=1, columnspan=2, sticky="NSEW")
        # 调整输入框的大小。
        # 获取Entry控件的字体
        font = tkFont.Font(font=self.entry1['font'])
        # 计算一个字符的宽度(以像素为单位),调整entry的长度。
        char_width = font.measure('0')
        self.entry1.config(width=100 // char_width)
        self.entry2.config(width=100 // char_width)

    # 右键菜单
    def right_menu(self):
        self.menu = tk.Menu(self.master,tearoff=0)  # tearoff=0表示菜单不可拆分



class App(AppBuild):
    def __init__(self, master):
        super().__init__(master)
        # 基础变量参数。
        self.result = LaTexSqrt()
        self.n = self.result.n
        self.x = self.result.x
        self.strvar1 = tk.StringVar()
        self.strvar2 = tk.StringVar()
        self.text0 = self.result.text0  # 公式的LaTex字符串。

        # 选择公式,解锁输入框。
        self.btn.bind("<Button-1>", self.origin_draw)

        # entry输入值。
        self.entry1.insert(tk.END, "n=")
        self.entry1.bind("<KeyRelease>", lambda event: self.get_n())
        self.entry1["state"] = "disabled"

        self.entry2.insert(tk.END, "x=")
        self.entry2.bind("<KeyRelease>", lambda event: self.get_x())
        self.entry2["state"] = "disabled"

        # 设置右键菜单命令。
        self.menu.add_cascade(label='保存',command=self.save_image)
        self.canvas.get_tk_widget().bind('<Button-3>', lambda event:self.showPopoutMenu(event))

    # 初始公式图
    def origin_draw(self, event):
        self.fig.text(0.5, 0.3, self.text0, ha="center", fontsize=30)
        self.canvas.draw()
        self.entry1["state"] = "normal"
        self.entry2["state"] = "normal"

    #获取n值。
    def get_n(self):
        self.strvar1 = self.entry1.get()  # 获取n的值
        # 赋予n的值,并设置误删=后恢复原来样子“n=”。
        if "=" in self.strvar1:
            self.n = self.strvar1.split("=")[1]
        else:
            self.n = self.strvar1
            self.entry1.insert(0, "n=")
        # 传入n,更新result的text0.
        self.result.change_n(self.n)
        # 判定x和n是否为数字,并且n不能为0或以下,x不能小于0,符合条件则计算,输出公式和结果。
        if is_number(self.x) and is_number(self.n) and float(self.n) > 0 and float(self.x) >= 0:
            self.result.sqrt_result()
            self.text0 = self.result.text0
            self.sqrt_draw()
        else:
            self.text0 = self.result.text0
            self.sqrt_draw()

    # 获取x值。
    def get_x(self):
        self.strvar2 = self.entry2.get()
        if "=" in self.strvar2:
            self.x = self.strvar2.split("=")[1]
        else:
            self.x = self.strvar2
            self.entry2.insert(0, "x=")
        self.result.change_x(self.x)
        if is_number(self.x) and is_number(self.n) and float(self.n) > 0 and float(self.x) >= 0:
            self.result.sqrt_result()
            self.text0 = self.result.text0
            self.sqrt_draw()
        else:
            self.text0 = self.result.text0
            self.sqrt_draw()

    # 公式绘图。
    def sqrt_draw(self, font_size=30):
        # 调用后清除原来内容。
        self.fig.clear()
        text = self.fig.text(0.5, 0.3, self.text0, ha="center", fontsize=font_size)
        # renderer是Matplotlib中的一个类,用于将图形元素渲染到画布上。
        renderer = self.canvas.get_renderer()
        # 要获取text文本元素边界框需要一个renderer对象(否则会报错)。
        bbox = text.get_window_extent(renderer=renderer)
        # 比较text的宽高跟canvas的宽高,通过循环对字体大小进行调整。
        if bbox.width > self.canvas.get_width_height()[0] or bbox.height > self.canvas.get_width_height()[1]:
            while bbox.width > self.canvas.get_width_height()[0] or bbox.height > self.canvas.get_width_height()[1]:
                self.fig.clear()
                font_size -= 1
                text = self.fig.text(0.5, 0.3, self.text0, ha="center", fontsize=font_size)
                bbox = text.get_window_extent(renderer=renderer)
        # 绘制图像。
        self.canvas.draw()

    def showPopoutMenu(self,event):
        self.menu.post(event.x + self.canvas.get_tk_widget().winfo_rootx(), event.y + self.canvas.get_tk_widget().winfo_rooty())
        self.canvas.get_tk_widget().update()

    def save_image(self):
        filepath = fg.asksaveasfilename(filetypes = [('图像','*png;')])
        if filepath:
            if filepath:
                if not filepath.endswith('.png'):
                    filepath += '.png'
                self.canvas.figure.savefig(filepath, bbox_inches='tight')
                tk.messagebox.showinfo("提示", "保存成功!")

root = tk.Tk()
root.title("Sqrt")
root.resizable(False, False)  # 禁止横向和纵向调整窗口大小
screen_width   = root.winfo_screenwidth()
screen_height  = root.winfo_screenheight()
window_width   = 150  # 设置窗口宽度
window_height  = 200  # 设置窗口高度
x = (screen_width - window_width) // 2
y = (screen_height - window_height) // 2
root.geometry(f"{window_width}x{window_height}+{x}+{y}")
apptk = App(root)

root.mainloop()

脚本分解解析

数据结构

根据LaTex格式,开方函数的公式为"\sqrt[]{}“,而用于python中,标识LaTex格式常使用“ 内容 内容 内容”,而.format()会以“{”作为识别的标识,所以,对于LaTex格式的字符串,必须拆解,由原来的”\sqrt[]{}"拆解为“ ” ”、“ 、““、” \sqrt[”、“]”、“{“、”} ”,随后再插入变量内容。

定义一个LaTexSqrt类作为可编辑的数据结构,构造函数定义n和x两个变量,text0为最终的函数公式式。

class LaTexSqrt:
    def __init__(self):
        self.n = "n"
        self.x = "x"
        self.text0 = "$\sqrt[" + "{}]".format(self.n) + "{" + "{}".format(self.x) + "}$"

    def change_n(self, n):
        # 排除掉特殊符号或空造成的错误,以及输入带\的转义字符时,被LatexNodes2Text删除后只剩''造成错误的情况。
        if not bool(re.search('[a-zA-Z]', n)) and not is_number(n) or convert_latex_to_text(n) == '':
            self.n = "n"
        else:
            # 剔除后面输入中不符合Latex格式转义字符串。
            self.n = convert_latex_to_text(n)
        self.text0 = "$\sqrt[" + "{}]".format(self.n) + "{" + "{}".format(self.x) + "}$"

    def change_x(self, x):
        if not bool(re.search('[a-zA-Z]', x)) and not is_number(x) or convert_latex_to_text(x) == '':
            self.x = "x"
        else:
            self.x = convert_latex_to_text(x)
        self.text0 = "$\sqrt[" + "{}]".format(self.n) + "{" + "{}".format(self.x) + "}$"

    def sqrt_result(self):
        # 开方运算,包括开2次方以上和小数
        result = pow(float(self.x), 1 / float(self.n))
        if is_decimal(result):
            self.text0 = "$\sqrt[" + "{}]".format(self.n) + "{" + "{}".format(self.x) + "}" + "\\approx{}$".format(
                round(result, 2))
        else:
            self.text0 = "$\sqrt[" + "{}]".format(self.n) + "{" + "{}".format(self.x) + "}$" + "={}".format(
                round(result, 2))
-*-判断是否为字母re.search(‘[a-zA-Z]’, x)
re.search('[a-zA-Z]', n)
-*-判断是否为数字not is_number(n)
# 判断方法为将传入的参数尝试转换为浮点数,能转换则为数字。
# 为什么不用n.isdigit(),因为这个只能判断字符串是否全部为数字,对于小数由于有小数点,而判断为false。
def is_number(string):
    try:
        float(string)
        return True
    except:
        return False
-*-判断是否为LaTex格式convert_latex_to_text(n) == ‘’
# 用于转换latex转义字符串
# 当输入\t时,会被LatexNodes2Text().latex_to_text(latex)删除,只剩空字符串“”
# 也就是说该函数存在的目的,就是为了排除LaTex转义字符输入的错误。
def convert_latex_to_text(latex):
    text = LatexNodes2Text().latex_to_text(latex)
    return text

界面布局

class AppBuild:
    def __init__(self, master):
        self.master = master
        self.photo = tk.PhotoImage(file=r'.\\img\\sqrt.png')
        self.textvar = tk.StringVar()
        self.setup_wedge()
        self.right_menu()

    # 基本布局
    def setup_wedge(self):
        # 创建和布置画布区域。
        self.fig = Figure(figsize=(1.5, 1.5), dpi=100)
        self.canvas = FigureCanvasTkAgg(self.fig, master=self.master)
        # self.canvas.get_tk_widget()只有通过get_tk_widget()才能使用tkinter的方法属性,如grid和bind。
        self.canvas.get_tk_widget().grid(row=0, column=0, columnspan=3, rowspan=3)

        # 布置选择公式的开始解锁按钮。
        self.btn = tk.Button(self.master, image=self.photo, width=50)
        self.btn.grid(row=3, column=0, rowspan=2, sticky="NSEW")

        # 布置参数的输入框。
        self.entry1 = tk.Entry(width=15)
        self.entry2 = tk.Entry(width=15)
        self.entry1.grid(row=3, column=1, columnspan=2, sticky="NSEW")
        self.entry2.grid(row=4, column=1, columnspan=2, sticky="NSEW")
        # 调整输入框的大小。
        # 获取Entry控件的字体
        font = tkFont.Font(font=self.entry1['font'])
        # 计算一个字符的宽度(以像素为单位),调整entry的长度。
        # font.measure('0') 方法来测量字符 '0' 在该字体下的宽度
        char_width = font.measure('0')
        self.entry1.config(width=100 // char_width)
        self.entry2.config(width=100 // char_width)

    # 右键菜单
    def right_menu(self):
        self.menu = tk.Menu(self.master,tearoff=0)  # tearoff=0表示菜单不可拆分
-*-手动调整entry长度。

tk.entry的width属性,是规定有多少个字符的宽度,而并非entry这个控件的宽度。所以我们需要获取entry默认下每个字符的宽度,然后在你预想设计entry的像素值除以这个宽度,得到的是entry能放多少个字符,这个数量就是entry的宽度。

char_width = font.measure('0')
entry1.config(width=100 // char_width)

调用与命令布置

class App(AppBuild):
    def __init__(self, master):
        super().__init__(master)
        # 基础变量参数。
        self.result = LaTexSqrt()
        self.n = self.result.n
        self.x = self.result.x
        self.strvar1 = tk.StringVar()
        self.strvar2 = tk.StringVar()
        self.text0 = self.result.text0  # 公式的LaTex字符串。

        # 选择公式,解锁输入框。
        self.btn.bind("<Button-1>", self.origin_draw)

        # entry输入值。
        self.entry1.insert(tk.END, "n=")
        self.entry1.bind("<KeyRelease>", lambda event: self.get_n())
        self.entry1["state"] = "disabled"

        self.entry2.insert(tk.END, "x=")
        self.entry2.bind("<KeyRelease>", lambda event: self.get_x())
        self.entry2["state"] = "disabled"

        # 设置右键菜单命令。
        self.menu.add_cascade(label='保存',command=self.save_image)
        self.canvas.get_tk_widget().bind('<Button-3>', lambda event:self.showPopoutMenu(event))

    # 初始公式图
    def origin_draw(self, event):
        self.fig.text(0.5, 0.3, self.text0, ha="center", fontsize=30)
        self.canvas.draw()
        self.entry1["state"] = "normal"
        self.entry2["state"] = "normal"

    #获取n值。
    def get_n(self):
        self.strvar1 = self.entry1.get()  # 获取n的值
        # 赋予n的值,并设置误删=后恢复原来样子“n=”。
        if "=" in self.strvar1:
            self.n = self.strvar1.split("=")[1]
        else:
            self.n = self.strvar1
            self.entry1.insert(0, "n=")
        # 传入n,更新result的text0.
        self.result.change_n(self.n)
        # 判定x和n是否为数字,并且n不能为0或以下,x不能小于0,符合条件则计算,输出公式和结果。
        if is_number(self.x) and is_number(self.n) and float(self.n) > 0 and float(self.x) >= 0:
            self.result.sqrt_result()
            self.text0 = self.result.text0
            self.sqrt_draw()
        else:
            self.text0 = self.result.text0
            self.sqrt_draw()

    # 获取x值。
    def get_x(self):
        self.strvar2 = self.entry2.get()
        if "=" in self.strvar2:
            self.x = self.strvar2.split("=")[1]
        else:
            self.x = self.strvar2
            self.entry2.insert(0, "x=")
        self.result.change_x(self.x)
        if is_number(self.x) and is_number(self.n) and float(self.n) > 0 and float(self.x) >= 0:
            self.result.sqrt_result()
            self.text0 = self.result.text0
            self.sqrt_draw()
        else:
            self.text0 = self.result.text0
            self.sqrt_draw()

    # 公式绘图。
    def sqrt_draw(self, font_size=30):
        # 调用后清除原来内容。
        self.fig.clear()
        text = self.fig.text(0.5, 0.3, self.text0, ha="center", fontsize=font_size)
        # renderer是Matplotlib中的一个类,用于将图形元素渲染到画布上。
        renderer = self.canvas.get_renderer()
        # 要获取text文本元素边界框需要一个renderer对象(否则会报错)。
        bbox = text.get_window_extent(renderer=renderer)
        # 比较text的宽高跟canvas的宽高,通过循环对字体大小进行调整。
        if bbox.width > self.canvas.get_width_height()[0] or bbox.height > self.canvas.get_width_height()[1]:
            while bbox.width > self.canvas.get_width_height()[0] or bbox.height > self.canvas.get_width_height()[1]:
                self.fig.clear()
                font_size -= 1
                text = self.fig.text(0.5, 0.3, self.text0, ha="center", fontsize=font_size)
                bbox = text.get_window_extent(renderer=renderer)
        # 绘制图像。
        self.canvas.draw()

    def showPopoutMenu(self,event):
        self.menu.post(event.x + self.canvas.get_tk_widget().winfo_rootx(), event.y + self.canvas.get_tk_widget().winfo_rooty())
        self.canvas.get_tk_widget().update()

    def save_image(self):
        filepath = fg.asksaveasfilename(filetypes = [('图像','*png;')])
        if filepath:
            if filepath:
                if not filepath.endswith('.png'):
                    filepath += '.png'
                self.canvas.figure.savefig(filepath, bbox_inches='tight')
                tk.messagebox.showinfo("提示", "保存成功!")
-*-点击按钮解锁输入框。(摘取部分代码)

创建类App(AppBuild)并继承布局类AppBuild的所有组件。

构造函数:

给与按钮命令

把输入框状态“锁上”。

按钮的命令为,在点击后在画板上将text的内容画上,并“解锁”输入框。

		# 选择公式,解锁输入框。
        self.btn.bind("<Button-1>", self.origin_draw)

        # entry输入值。
        self.entry1.insert(tk.END, "n=")
        self.entry1.bind("<KeyRelease>", lambda event: self.get_n())
        self.entry1["state"] = "disabled"
    # 初始公式图
    def origin_draw(self, event):
        self.fig.text(0.5, 0.3, self.text0, ha="center", fontsize=30)
        self.canvas.draw()
        self.entry1["state"] = "normal"
        self.entry2["state"] = "normal"
-*-强制输入框保持部分内容不变:

在这里插入图片描述

# 赋予n的值,并设置误删=后恢复原来样子“n=”。
        if "=" in self.strvar1:
            self.n = self.strvar1.split("=")[1]
        else:
            self.n = self.strvar1
            self.entry1.insert(0, "n=")

分割“=”前后,后为输入值。

(1)当一次性删除太多,如删除“=”,那么会自动在开头补上“n=”,输入框字符变为“n=n”,后面的n为初始没有删除的那个。

(2)当用鼠标框选,一次性全删除,则补充“n=”后,只有补充那部分。

-*-自适应调整text文本的长度
 # 要获取text文本元素边界框需要一个renderer对象(否则会报错)。
        bbox = text.get_window_extent(renderer=renderer)
        # 比较text的宽高跟canvas的宽高,通过循环对字体大小进行调整。
        if bbox.width > self.canvas.get_width_height()[0] or bbox.height > self.canvas.get_width_height()[1]:
            while bbox.width > self.canvas.get_width_height()[0] or bbox.height > self.canvas.get_width_height()[1]:
                self.fig.clear()
                font_size -= 1
                text = self.fig.text(0.5, 0.3, self.text0, ha="center", fontsize=font_size)
                bbox = text.get_window_extent(renderer=renderer)
        # 绘制图像。
        self.canvas.draw()
-*-右键菜单命令
		self.menu.add_cascade(label='保存',command=self.save_image)
        self.canvas.get_tk_widget().bind('<Button-3>', lambda event:self.showPopoutMenu(event))
        
def showPopoutMenu(self,event):
        self.menu.post(event.x + self.canvas.get_tk_widget().winfo_rootx(), event.y + self.canvas.get_tk_widget().winfo_rooty())
        self.canvas.get_tk_widget().update()

画布绑定了命令

event.xevent.y表示鼠标点击事件对于画布(canvas)坐标位置。

canvas.get_tk_widget().winfo_rootx()canvas.get_tk_widget().winfo_rooty()画布在屏幕上的绝对位置。

两者加值,可以得到鼠标点击事件在屏幕上的绝对坐标位置。

-*-打开询问保存命令
def save_image(self):
        filepath = fg.asksaveasfilename(filetypes = [('图像','*png;')])
        if filepath:
            if filepath:
                if not filepath.endswith('.png'):
                    filepath += '.png'
                self.canvas.figure.savefig(filepath, bbox_inches='tight')
                tk.messagebox.showinfo("提示", "保存成功!")

附录文件打包链接

存在不足

在打入latex允许的^符号后。
由于开方函数不能单独存在^符号报错。
不过打包后,这个错误是看不到的,着实是属于在可控范围内的小bug。
在这里插入图片描述

  • 21
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值