今天在 Day 24 的基礎上,加入溫度換算(°C/°F/K),並增加日常實用的小功能:
轉換原理:溫度為什麼特別?
溫度不是單純「倍數關係」,需要平移+縮放:
程式碼(存成 unit_converter_pro.py)
可獨立執行,不必先安裝 Day 22;已包含長度、重量、溫度與新功能。
import tkinter as tk
from tkinter import ttk, messagebox
# ---- 資料定義 ----
CATEGORIES = ["長度", "重量", "溫度"]
LENGTH_TO_M = {
"mm": 1e-3, "cm": 1e-2, "m": 1.0, "km": 1e3,
"inch": 0.0254, "foot": 0.3048, "yard": 0.9144, "mile": 1609.344
}
MASS_TO_KG = {
"g": 1e-3, "kg": 1.0, "oz": 0.028349523125, "lb": 0.45359237, "ton": 1000.0
}
TEMP_UNITS = ["°C", "°F", "K"]
UNITS_BY_CAT = {
"長度": list(LENGTH_TO_M.keys()),
"重量": list(MASS_TO_KG.keys()),
"溫度": TEMP_UNITS,
}
# ---- 轉換 ----
def convert_length(val, from_u, to_u):
return (val * LENGTH_TO_M[from_u]) / LENGTH_TO_M[to_u]
def convert_mass(val, from_u, to_u):
return (val * MASS_TO_KG[from_u]) / MASS_TO_KG[to_u]
def c_to_k(c): return c + 273.15
def f_to_k(f): return (f - 32) * 5/9 + 273.15
def k_to_c(k): return k - 273.15
def k_to_f(k): return (k - 273.15) * 9/5 + 32
def convert_temp(val, from_u, to_u):
k = c_to_k(val) if from_u == "°C" else (f_to_k(val) if from_u == "°F" else val)
return k if to_u == "K" else (k_to_c(k) if to_u == "°C" else k_to_f(k))
# ---- UI 行為 ----
def on_category_change(_=None):
cat = cat_var.get()
units = UNITS_BY_CAT[cat]
from_var.set(units[0])
to_var.set(units[1] if len(units) > 1 else units[0])
from_box["values"] = units; to_box["values"] = units
result_var.set(""); detail_var.set("")
val_entry.focus()
auto_convert()
def auto_convert(_=None):
"""單位或數值變動即換算"""
try:
val = float(val_var.get())
except ValueError:
result_var.set(""); detail_var.set(""); return
frm, to = from_var.get(), to_var.get()
cat = cat_var.get()
try:
if cat == "長度":
ans = convert_length(val, frm, to)
result_var.set(f"{ans:.6g} {to}")
detail_var.set(f"{val} {frm} × (to m) / (to {to})")
elif cat == "重量":
ans = convert_mass(val, frm, to)
result_var.set(f"{ans:.6g} {to}")
detail_var.set(f"{val} {frm} × (to kg) / (to {to})")
else:
ans = convert_temp(val, frm, to)
result_var.set(f"{ans:.2f} {to}")
detail_var.set("溫度以 K 為中繼:C↔K、F↔K 公式換算")
except Exception as e:
messagebox.showerror("轉換錯誤", str(e))
return
def swap_units():
a, b = from_var.get(), to_var.get()
from_var.set(b); to_var.set(a)
auto_convert()
def copy_result():
text = result_var.get().strip()
if not text:
messagebox.showinfo("提示", "沒有可複製的結果"); return
root.clipboard_clear(); root.clipboard_append(text); root.update()
messagebox.showinfo("已複製", f"已複製:{text}")
def add_history():
text = result_var.get().strip()
if not text: return
src = f"{val_var.get()} {from_var.get()}"
dst = text
hist_list.insert(0, f"{src} → {dst}")
# ---- 介面 ----
root = tk.Tk()
root.title("單位換算器 - 進階版")
main = ttk.Frame(root, padding=16); main.grid(sticky="nsew")
root.rowconfigure(0, weight=1); root.columnconfigure(0, weight=1)
# 左側:操作
left = ttk.Frame(main); left.grid(row=0, column=0, sticky="nsew", padx=(0,12))
# 右側:歷史
right = ttk.Frame(main); right.grid(row=0, column=1, sticky="nsew")
main.columnconfigure(0, weight=1); main.columnconfigure(1, weight=1)
main.rowconfigure(0, weight=1)
# 類別
ttk.Label(left, text="類別").grid(row=0, column=0, sticky="e", pady=4)
cat_var = tk.StringVar(value=CATEGORIES[0])
cat_box = ttk.Combobox(left, textvariable=cat_var, values=CATEGORIES, state="readonly", width=8)
cat_box.grid(row=0, column=1, sticky="w", pady=4)
cat_box.bind("<<ComboboxSelected>>", on_category_change)
# 數值
ttk.Label(left, text="數值").grid(row=1, column=0, sticky="e", pady=4)
val_var = tk.StringVar(value="1")
val_entry = ttk.Entry(left, textvariable=val_var, width=14, justify="center")
val_entry.grid(row=1, column=1, sticky="w", pady=4)
val_entry.bind("<KeyRelease>", auto_convert)
# 單位
ttk.Label(left, text="從").grid(row=2, column=0, sticky="e", pady=4)
from_var = tk.StringVar(); from_box = ttk.Combobox(left, textvariable=from_var, state="readonly", width=8)
from_box.grid(row=2, column=1, sticky="w", pady=4)
from_box.bind("<<ComboboxSelected>>", auto_convert)
ttk.Label(left, text="到").grid(row=3, column=0, sticky="e", pady=4)
to_var = tk.StringVar(); to_box = ttk.Combobox(left, textvariable=to_var, state="readonly", width=8)
to_box.grid(row=3, column=1, sticky="w", pady=4)
to_box.bind("<<ComboboxSelected>>", auto_convert)
# 按鈕
btns = ttk.Frame(left); btns.grid(row=4, column=0, columnspan=2, pady=8)
ttk.Button(btns, text="交換單位", command=swap_units).grid(row=0, column=0, padx=4)
ttk.Button(btns, text="複製結果", command=copy_result).grid(row=0, column=1, padx=4)
# 結果
result_var = tk.StringVar(value="")
detail_var = tk.StringVar(value="")
ttk.Label(left, text="結果").grid(row=5, column=0, sticky="e", pady=6)
ttk.Label(left, textvariable=result_var, font=("Segoe UI", 12)).grid(row=5, column=1, sticky="w", pady=6)
ttk.Label(left, textvariable=detail_var, foreground="#666").grid(row=6, column=0, columnspan=2, sticky="w")
# 右側:歷史紀錄(最新在最上方)
ttk.Label(right, text="歷史紀錄").grid(row=0, column=0, sticky="w")
hist_list = tk.Listbox(right, height=10)
hist_list.grid(row=1, column=0, sticky="nsew")
right.rowconfigure(1, weight=1); right.columnconfigure(0, weight=1)
# 每次換算後把結果加到歷史(Enter 也會)
def confirm_and_add(_=None):
auto_convert(); add_history()
root.bind("<Return>", confirm_and_add)
# 初始化
on_category_change()
val_entry.focus()
root.mainloop()
使用
python unit_converter_pro.py
實作:
小技巧
溫度顯示我用 小數點兩位,長度/重量顯示 有效位數 6;可依需求調整。
若想新增「面積/體積」,用同樣「基準單位」思路(面積→m²、體積→m³)即可一秒擴充。
想要「輸入即時格式化」(例如自動把 1..2 修成 1.2),可在 val_entry 的 KeyRelease 裡加規則。
兩天小結
Day 24打底:用「基準單位法」完成長度、重量轉換,UI 簡潔。
Day 25加溫度(平移+縮放)、加上快用功能(即時換算、交換、複製、歷史),提升可用性。