前言
进来一段时间,偶尔会遇到一些需要特殊计算的常见,比如计算mm转mil,比如给螺旋线的高度匝数半径,计算螺旋线长度等,一次一次输数字手算是绝对不可能,一般简单点比如单位转化都是直接问AI,复杂一点的或者要用多次的都会写py代码,然后在黑框里运行。不过用起来还是乱糟糟的,这里分享一种把计算器整合到一起的方法,设计一个简单的GUI,同时提供模板方法,后续如果需要加别的计算器的话,直接加进去就能用。
一、UI展示
下图是整合了最近在用的一小部分计算器之后的UI截面。左侧可以选择计算模式,也就是要调用哪个计算器,中间根据计算器不同输入不同的参数,点击运行后,右侧就可以输出结果。当然计算器都是自己实现的,如果需要,可以自己再加。
二、代码实现
main方法(GUI)实现
代码主要由一个main和若干个calculate tool组成,其中main.py文件的内容如下:
import tkinter as tk from dataclasses import dataclass from tkinter import ttk, messagebox from typing import Callable, Dict, List, Any, Optional from tool.CalculateLambada import v_ph, calculate_wavelength_and_length from tool.calculate_dB_dBm import calculate_db from tool.calculate_mil_mm import calculate_mil_mm from tool.calculate_wave_freq import calculate_wave from tool.temp import temp # ---------------------------- # 1) 模式定义(你主要改这里) # ---------------------------- @dataclass class Field: key: str # 输入字段的内部名字(用于函数取值) label: str # 显示在界面上的标签 cast: Callable[[str], Any] = str # 类型转换(int/float/str...) default: str = "" # 默认值 hint: str = "" # 提示(可选) @dataclass class Mode: name: str fields: List[Field] func: Callable[[Dict[str, Any]], str] # 输入dict -> 输出字符串 description: str = "" def mode_sum(inputs: Dict[str, Any]) -> str: a = inputs["a"] b = inputs["b"] return f"模式:加法\n\n结果:{a} + {b} = {a + b}" def mode_quadratic(inputs: Dict[str, Any]) -> str: import math a, b, c = inputs["a"], inputs["b"], inputs["c"] if a == 0: return "错误:a 不能为 0(否则不是二次方程)。" delta = b * b - 4 * a * c if delta < 0: re = -b / (2 * a) im = math.sqrt(-delta) / (2 * a) return f"模式:二次方程\n\na={a}, b={b}, c={c}\nΔ={delta}\n\n实根:无\n复根:x1={re}+{im}i, x2={re}-{im}i" else: x1 = (-b + math.sqrt(delta)) / (2 * a) x2 = (-b - math.sqrt(delta)) / (2 * a) return f"模式:二次方程\n\na={a}, b={b}, c={c}\nΔ={delta}\n\n解:x1={x1}\n解:x2={x2}" def mode_text_repeat(inputs: Dict[str, Any]) -> str: text = inputs["text"] n = inputs["n"] return "模式:文本重复\n\n" + ("\n".join([text] * n)) MODES: List[Mode] = [ Mode( name="计算波长", description="输入频率/GHz,计算波长及1/4波长\n" "输入波长/mm,计算频率", fields=[ Field("frequency", "频率", str, default="0",hint="GHz"), Field("wavelength", "波长", str, default="0",hint="mm"), ], func=calculate_wave, ), Mode( name="dB/dBm", description="输入波长/mm,计算频率", fields=[ Field("db", "db", str, default="0",hint="dB"), Field("dbm", "dbm", str, default="0",hint="dBm"), ], func=calculate_db, ), Mode( name="mil <-> mm ", description="输入mm,输出mil\n输入mil,输出mm", fields=[ Field("mm", "mm", str, default="0",hint="mm"), Field("mil", "mil", str, default="0",hint="mil"), ], func=calculate_mil_mm, ), Mode( name="计算相位长度", description="30度相位和150度相位对应长度计算器\n" "输入频率,计算对应的30度波长和150波长\n" "输入频率,计算对应波长及对应30度和150度波长的频率", fields=[ Field("v_ph", "相速度", str, default=f"{v_ph}"), Field("frequency", "频率/GHz", str, default="0",hint="GHz"), Field("length", "波长/mm", str, default="0",hint="mm"), ], func=calculate_wavelength_and_length, ), Mode( name="模板", description="temp", fields=[ Field("temp_Parameter", "参数", str, default="参数",hint="模板参数单位"), ], func=temp, ), ] # ---------------------------- # 2) 通用 GUI 框架(基本不用改) # ---------------------------- class MultiModeApp(tk.Tk): def __init__(self, modes: List[Mode]): super().__init__() self.title("超级计算器计算器(自用)") self.geometry("820x520") self.modes = modes self.current_mode: Optional[Mode] = None self.inputs_vars: Dict[str, tk.StringVar] = {} self.input_widgets: List[tk.Widget] = [] self._build_layout() self._build_mode_buttons() self.set_mode(self.modes[0].name) def _build_layout(self): # 左侧:模式按钮 self.left = ttk.Frame(self, padding=10) self.left.pack(side=tk.LEFT, fill=tk.Y) ttk.Label(self.left, text="计算模式", font=("Arial", 12, "bold")).pack(anchor="w", pady=(0, 8)) self.mode_btn_frame = ttk.Frame(self.left) self.mode_btn_frame.pack(fill=tk.Y, expand=False) ttk.Separator(self.left).pack(fill=tk.X, pady=10) # 中间:输入区 self.center = ttk.Frame(self, padding=10) self.center.pack(side=tk.LEFT, fill=tk.Y) ttk.Label(self.center, text="输入参数", font=("Arial", 12, "bold")).pack(anchor="w", pady=(0, 8)) self.inputs_frame = ttk.Frame(self.center) self.inputs_frame.pack(fill=tk.Y, expand=False) self.run_btn = ttk.Button(self.center, text="运行", command=self.run_current_mode) self.run_btn.pack(anchor="w", pady=(12, 4)) self.clear_btn = ttk.Button(self.center, text="清空输出", command=self.clear_output) self.clear_btn.pack(anchor="w", pady=(0, 4)) # 右侧:大输出框 self.right = ttk.Frame(self, padding=10) self.right.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) ttk.Label(self.right, text="输出结果", font=("Arial", 12, "bold")).pack(anchor="w", pady=(0, 8)) self.output = tk.Text(self.right, wrap="word", height=10) self.output.pack(fill=tk.BOTH, expand=True) # 让输出框更像“日志/结果面板” self.output.configure(font=("Consolas", 11)) self.output.insert("end", "选择模式 → 填参数 → 点击运行\n") self.output.configure(state="disabled") def _build_mode_buttons(self): # 每个模式一个按钮 for w in self.mode_btn_frame.winfo_children(): w.destroy() self.mode_buttons: Dict[str, ttk.Button] = {} for m in self.modes: btn = ttk.Button(self.mode_btn_frame, text=m.name, command=lambda name=m.name: self.set_mode(name)) btn.pack(fill=tk.X, pady=3) self.mode_buttons[m.name] = btn def set_mode(self, mode_name: str): mode = next((m for m in self.modes if m.name == mode_name), None) if not mode: messagebox.showerror("错误", f"找不到模式:{mode_name}") return self.current_mode = mode self._rebuild_inputs(mode) self._append_output(f"\n=== 切换到模式:{mode.name} ===\n") self._append_output(f"\n{mode.description}\n") def _rebuild_inputs(self, mode: Mode): # 清掉旧输入控件 for w in self.input_widgets: w.destroy() self.input_widgets.clear() self.inputs_vars.clear() # 动态生成输入行 for i, field in enumerate(mode.fields): row = ttk.Frame(self.inputs_frame) row.pack(fill=tk.X, pady=4) lbl = ttk.Label(row, text=field.label, width=12) lbl.pack(side=tk.LEFT) var = tk.StringVar(value=field.default) ent = ttk.Entry(row, textvariable=var, width=24) ent.pack(side=tk.LEFT, padx=(6, 0)) if field.hint: hint = ttk.Label(row, text=field.hint, foreground="#666") hint.pack(side=tk.LEFT, padx=(8, 0)) self.input_widgets.append(hint) self.inputs_vars[field.key] = var self.input_widgets.extend([row, lbl, ent]) def run_current_mode(self): if not self.current_mode: return # 读取输入 + 类型转换 inputs: Dict[str, Any] = {} for field in self.current_mode.fields: raw = self.inputs_vars[field.key].get() try: val = field.cast(raw) if raw != "" else field.cast(field.default) except Exception as e: messagebox.showerror("输入错误", f"{field.label} 无法转换:{raw}\n\n{e}") return inputs[field.key] = val # 调用模式函数 try: result = self.current_mode.func(inputs) except Exception as e: result = f"运行异常:{e}" # 输出到大文本框 self._append_output("\n" + result + "\n") def clear_output(self): self.output.configure(state="normal") self.output.delete("1.0", "end") self.output.configure(state="disabled") def _append_output(self, text: str): self.output.configure(state="normal") self.output.insert("end", text) self.output.see("end") self.output.configure(state="disabled") if __name__ == "__main__": app = MultiModeApp(MODES) app.mainloop()与main同目录下有一个tool文件夹,分别放calculate_dB_dBm.py;calculate_mil_mm.py;calculate_wave_freg.py;calculate_Lambada.py,命名可能不规范,仅供参考。具体路径或名称,也可参考main的import部分。
计算器实现
计算器实现部分如下:
calculate_wave_freq.py
from typing import Dict def calculate_wave(inputs: Dict[str, any]) -> str: frequency = float(inputs['frequency']) wavelength = float(inputs['wavelength']) try: if frequency !=0: wavelength = 299792458 / (frequency * 1e9) * 1000 return (f"-> 频率: {frequency:.3f} GHz \n" f"-> 波长: {wavelength:.3f} mm \n" f"-> 1/4波长: {wavelength / 4:.3f} mm") elif wavelength != 0: frequency = 299792458 / (wavelength * 1e-3) / 1e9 return (f"-> 波长: {wavelength:.3f} mm \n" f"-> 频率: {frequency:.3f} GHz") else: return "请先输入数据" except Exception as e: return f"输入错误,请重新输入!错误: {e}"calculate_mil_mm.py
def calculate_mil_mm(inputs: dict[str, any]) -> str: """长度单位转换器:mil 和 mm 双向转换(GUI模式版)""" mm = float(inputs["mm"]) mil = float(inputs["mil"]) try: if "mm" != 0: mil_value = mm * 39.37007874 return ( "模式:长度单位转换(mm -> mil)\n\n" f"输入:{mm} mm\n" f"结果:{mil_value:.6f} mil\n" ) elif mil != 0: mil_value = mil * 0.0254 return ( "模式:长度单位转换(mil -> mm)\n\n" f"输入:{mil} mil\n" f"结果:{mil_value:.6f} mm\n" ) else: return "错误:请包含正确的单位 (mil 或 mm),例如:10mil / 0.254mm / 0.254 mm" except ValueError: return "错误:输入格式不正确,请确保包含数字和单位,例如:10mil 或 0.254 mm" except Exception as e: return f"发生错误:{e}"CalculateLambada.py
from typing import Dict # 相速度 v_ph = 158.8 * 10 ** 6 # m/s def calculate_wavelength_and_length(inputs: Dict[str, any]) -> str: v_pph = float(inputs['v_ph']) frequency_str = inputs['frequency'] length_str = inputs['length'] try: if frequency_str: frequency = float(frequency_str) * 1e9 # 转换为Hz wavelength = v_pph / frequency # 波长计算 l_30 = wavelength / 12 # 对应30°相位差的延时线长度 l_150 = wavelength / 2.4 # 对应150°相位差的延时线长度 return f"频率: {frequency / 1e9:.2f} GHz, 波长: {wavelength * 1000:.2f} mm, \n" \ f"30° 波长: {l_30 * 1000:.2f} mm, 150° 波长: {l_150 * 1000:.2f} mm\n\n" elif length_str: # 解析延时线长度输入 length_mm = float(length_str) # 输入单位是mm length_m = length_mm * 1e-3 # 转换为m # 对应30°和150°的长度比例计算 wavelength_30 = length_m * 12 # 对应30°的波长 wavelength_150 = length_m * 2.4 # 对应150°的波长 frequency_30 = v_pph / wavelength_30 # 对应30°的频率 frequency_150 = v_pph / wavelength_150 # 对应150°的频率 return f"Length: {length_mm} mm, " \ f"30° 波长: {frequency_30 / 1e9:.2f} GHz, 波长: {wavelength_30 * 1000:.2f} mm, \n" \ f"150° 波长: {frequency_150 / 1e9:.2f} GHz, 波长: {wavelength_150 * 1000:.2f} mm\nn" except TypeError: return "请输入数字"calculate_dB_dBm.py
from typing import Dict def calculate_db(inputs: Dict[str, any]) -> str: db_value = float(inputs["db"]) dbm_value = float(inputs["dbm"]) try: if db_value != 0: # 提取 'db' 前面的数值部分并计算 # dB 到 P1/P2 比值的计算 # P1/P2 = 10^(dB_value / 10) power_ratio = 10 ** (db_value / 10) return f"{db_value:.3f} dB -> 功率比值 P1/P2: {power_ratio:.3f}" elif dbm_value != 0: # dBm 到 瓦特 的计算 # P(W) = 10^((dBm_value - 30) / 10) power_watts = 10 ** ((dbm_value - 30) / 10) return f"{dbm_value:.3f} dBm -> 功率: {power_watts:.3f} W" else: return "请先输入数据" except Exception as e: return f"输入错误或表达式无效: {e},请重新输入!"temp.py
from typing import Dict def temp(inputs:Dict[str,any])->str: num1=inputs['temp_Parameter'] return num1三、添加自己的计算器
可以在main里的MODES的mode里添加一个Mode,就会在GUI左侧添加一个新的计算器了。其中name表示计算器名称,description为描述,Fields包含若干参数其中Fied对应的第一个参数分别为key,提示标签(在输入框前边显示),类型,默认值,单位(在输入框后边显示)。func及调用的方法的名称。
Mode( name="模板", description="temp", fields=[ Field("temp_Parameter", "参数", str, default="参数",hint="模板参数单位"), ], func=temp, ),上文已经提供了temp.py的模板,参数传递的方法为
num1=inputs['temp_Parameter']
注意temp_Parameter是key,要与Field里的key对应。如果需要输入不止一个参数,那就多加几个Field就好了。