style(HDMI-Tool): 按用户标记统一左列高度与温度实时显示

This commit is contained in:
2026-09-12 02:19:51 +08:00
parent e131646156
commit 68c7909922
4 changed files with 89 additions and 69 deletions
+4 -3
View File
@@ -63,9 +63,10 @@ HDMI-Tool/
点图案按钮全屏投放,按 **ESC** 或点击退出;R/G/B 纯色按钮(55/AA/FF,按通道着色)
用于 RGB 位交换类测试;自定义图片点齿轮按钮配置(图片自动复制到 exe 旁);
4. **光机控制**:右列选串口与参数(波特率/数据位/校验/停止位,默认 115200 8-N-1)→ 连接 →
快捷按钮/预设下拉/自定义输入(回车即发);"查DMD温度""查LAMP温度"读温度
快捷滑条:光强拖动后"下发 M731S"定时秒数后"开光机(定时)"0 秒=不限时 M730S0),
TEC 凝露点滑条/输入框200-350 即 20.0-35.0°C)后"下发 M8S";数值框输入后回车自动限幅同步;
快捷按钮/预设下拉/自定义输入(回车即发);"查DMD温度""查LAMP温度"读温度
(应答实时填入按钮旁的显示框);快捷滑条:光强拖动后"下发 M731S"
定时毫秒后"开光机(定时)"0=不限时 M730S0),TEC 凝露点滑条/输入框
200-350 即 20.0-35.0°C)后"下发 M8S";数值框输入后回车自动限幅同步;
5. 测试卡区域、自定义栏位与串口记忆(含参数)自动保存在 exe 旁的 `pattern_slots.json`
## 4. 典型测试场景
Binary file not shown.
+4 -3
View File
@@ -63,9 +63,10 @@ HDMI-Tool/
点图案按钮全屏投放,按 **ESC** 或点击退出;R/G/B 纯色按钮(55/AA/FF,按通道着色)
用于 RGB 位交换类测试;自定义图片点齿轮按钮配置(图片自动复制到 exe 旁);
4. **光机控制**:右列选串口与参数(波特率/数据位/校验/停止位,默认 115200 8-N-1)→ 连接 →
快捷按钮/预设下拉/自定义输入(回车即发);"查DMD温度""查LAMP温度"读温度
快捷滑条:光强拖动后"下发 M731S"定时秒数后"开光机(定时)"0 秒=不限时 M730S0),
TEC 凝露点滑条/输入框200-350 即 20.0-35.0°C)后"下发 M8S";数值框输入后回车自动限幅同步;
快捷按钮/预设下拉/自定义输入(回车即发);"查DMD温度""查LAMP温度"读温度
(应答实时填入按钮旁的显示框);快捷滑条:光强拖动后"下发 M731S"
定时毫秒后"开光机(定时)"0=不限时 M730S0),TEC 凝露点滑条/输入框
200-350 即 20.0-35.0°C)后"下发 M8S";数值框输入后回车自动限幅同步;
5. 测试卡区域、自定义栏位与串口记忆(含参数)自动保存在 exe 旁的 `pattern_slots.json`
## 4. 典型测试场景
+79 -61
View File
@@ -15,9 +15,10 @@ HDMI-Tool 主界面 — CustomTkinter 双栏仪表盘
import ctypes
import os
import queue
import re
import time
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
from tkinter import filedialog, messagebox
import customtkinter as ctk
@@ -73,7 +74,7 @@ class App(ctk.CTk):
self.dlpc = dlpc_rs485.DLPC485(self._dlpc_on_event, self._dlpc_on_status)
self._dlpc_connected = False
self._poll_job = None
self._tv_style()
self._tl_order = [] # 温度查询应答顺序: 'DMD'/'LAMP' 先到先得
self._build()
self._load_displays()
if dlpc_rs485.HAS_SERIAL:
@@ -84,32 +85,8 @@ class App(ctk.CTk):
# ============================================================
# 布局
# ============================================================
def _tv_style(self):
"""内嵌 ttk.Treeview 的暗/亮配色(跟随外观切换)"""
self._style = ttk.Style(self)
try:
self._style.theme_use('clam')
except Exception:
pass
self._apply_tv_colors()
def _apply_tv_colors(self):
dark = ctk.get_appearance_mode() == 'Dark'
s = self._style
s.configure('HDMI.Treeview',
background='#181818' if dark else '#F4F4F4',
fieldbackground='#181818' if dark else '#F4F4F4',
foreground='#DADADA' if dark else '#1A1A1A',
rowheight=24, borderwidth=0)
s.configure('HDMI.Treeview.Heading',
background='#242424' if dark else '#E8E8E8',
foreground='#B0B0B0' if dark else '#444444',
relief='flat')
s.map('HDMI.Treeview', background=[('selected', '#2A5D8F')])
def _set_appearance(self, mode):
ctk.set_appearance_mode(mode)
self._apply_tv_colors()
def _card(self, parent, title, button=None, command=None, right_label=None):
"""卡片容器: 圆角边框 + 标题行(可带右侧小按钮/右对齐标签), 返回 (卡片, 内容 frame)
@@ -163,9 +140,9 @@ class App(ctk.CTk):
).pack(expand=True)
def _build_left(self, col):
col.grid_rowconfigure(1, weight=3) # 支持的模式
col.grid_rowconfigure(1, weight=2) # 支持的模式(与 EDID 信息等高)
col.grid_rowconfigure(2, weight=2) # EDID 信息
col.grid_rowconfigure(4, weight=1) # EDID 原始数据
col.grid_rowconfigure(4, weight=0) # EDID 原始数据(固定高, 压缩)
col.grid_columnconfigure(0, weight=1)
# -- 显示器选择区域 --
@@ -201,16 +178,8 @@ class App(ctk.CTk):
# -- EDID 信息 --
card, c = self._card(col, 'EDID 信息')
card.grid(row=2, column=0, sticky='nsew', pady=(10, 0))
self.tv_info = ttk.Treeview(c, columns=('k', 'v'), show='headings',
height=8, style='HDMI.Treeview')
self.tv_info.heading('k', text='项目')
self.tv_info.heading('v', text='')
self.tv_info.column('k', width=150, anchor='w')
self.tv_info.column('v', anchor='w')
sb = ttk.Scrollbar(c, orient='vertical', command=self.tv_info.yview)
self.tv_info.configure(yscrollcommand=sb.set)
self.tv_info.pack(side='left', fill='both', expand=True)
sb.pack(side='right', fill='y')
self.edid_scroll = ctk.CTkScrollableFrame(c, height=150)
self.edid_scroll.pack(fill='both', expand=True)
# -- 测试图案 --
card, c = self._card(col,'测试图案 — 黑底 1:1 居中, 按 ESC 退出')
@@ -257,9 +226,9 @@ class App(ctk.CTk):
# -- EDID 原始数据 --
card, c = self._card(col, 'EDID 原始数据',
button='逐字节解析', command=self._show_edid_fields)
card.grid(row=4, column=0, sticky='nsew', pady=(10, 0))
self.txt_hex = ctk.CTkTextbox(c, font=MONO, wrap='none')
self.txt_hex.pack(fill='both', expand=True)
card.grid(row=4, column=0, sticky='ew', pady=(10, 0))
self.txt_hex = ctk.CTkTextbox(c, font=MONO, wrap='none', height=130)
self.txt_hex.pack(fill='x')
self.txt_hex.configure(state='disabled')
def _build_right(self, col):
@@ -322,13 +291,15 @@ class App(ctk.CTk):
for name, cmd in (('开光机', 'M730S0'), ('关光机', 'M730S1'),
('查电流', 'M732'), ('查版本', 'M999'), ('软件重启', 'M888'),
('开TEC', 'M7S0'), ('关TEC', 'M7S1'), ('TEC33.0°C', 'M8S330')):
ctk.CTkButton(row1, text=name, width=68,
ctk.CTkButton(row1, text=name, width=64,
command=lambda cc=cmd: self._dlpc_send(cc)
).pack(side='left', padx=(0, 4))
).pack(side='left', fill='x', expand=True, padx=(0, 4))
row2 = ctk.CTkFrame(c, fg_color='transparent')
row2.pack(fill='x', pady=(6, 0))
row2.grid_columnconfigure(0, weight=6) # 左: 预设(约 6 份)
row2.grid_columnconfigure(1, weight=4) # 右: 温度查询+实时显示(约 4 份)
lf2 = ctk.CTkFrame(row2, fg_color='transparent')
lf2.pack(side='left', fill='x', expand=True)
lf2.grid(row=0, column=0, sticky='ew')
ctk.CTkLabel(lf2, text='预设:', width=52, anchor='w').pack(side='left')
self._dlpc_presets = [(n, cmd) for n, cmd, _ in dlpc_rs485.PRESETS]
self.cbo_dlpc_cmd = ctk.CTkComboBox(
@@ -338,12 +309,20 @@ class App(ctk.CTk):
self.cbo_dlpc_cmd.pack(side='left', fill='x', expand=True, padx=6)
ctk.CTkButton(lf2, text='发送', width=64,
command=self._dlpc_send_preset).pack(side='left')
ctk.CTkButton(row2, text='查DMD温度', width=96,
command=lambda: self._dlpc_send('M201')).pack(side='right')
rf2 = ctk.CTkFrame(row2, fg_color='transparent')
rf2.grid(row=0, column=1, sticky='ew', padx=(8, 0))
ctk.CTkButton(rf2, text='查DMD温度', width=90,
command=self._query_dmd).pack(side='left')
self.var_dmd = tk.StringVar(value='DMD: —')
ctk.CTkLabel(rf2, textvariable=self.var_dmd, width=110, height=28,
corner_radius=6, fg_color=('#F2F2F2', '#181818'),
anchor='w').pack(side='left', fill='x', expand=True, padx=(6, 0))
row3 = ctk.CTkFrame(c, fg_color='transparent')
row3.pack(fill='x', pady=(6, 0))
row3.grid_columnconfigure(0, weight=6)
row3.grid_columnconfigure(1, weight=4)
lf3 = ctk.CTkFrame(row3, fg_color='transparent')
lf3.pack(side='left', fill='x', expand=True)
lf3.grid(row=0, column=0, sticky='ew')
ctk.CTkLabel(lf3, text='自定义:', width=52, anchor='w').pack(side='left')
self.var_custom_cmd = tk.StringVar()
ent = ctk.CTkEntry(lf3, textvariable=self.var_custom_cmd,
@@ -352,8 +331,14 @@ class App(ctk.CTk):
ent.bind('<Return>', lambda _e: self._dlpc_send_custom())
ctk.CTkButton(lf3, text='发送', width=64,
command=self._dlpc_send_custom).pack(side='left')
ctk.CTkButton(row3, text='查LAMP温度', width=96,
command=lambda: self._dlpc_send('M202')).pack(side='right')
rf3 = ctk.CTkFrame(row3, fg_color='transparent')
rf3.grid(row=0, column=1, sticky='ew', padx=(8, 0))
ctk.CTkButton(rf3, text='查LAMP温度', width=90,
command=self._query_lamp).pack(side='left')
self.var_lamp = tk.StringVar(value='LAMP: —')
ctk.CTkLabel(rf3, textvariable=self.var_lamp, width=110, height=28,
corner_radius=6, fg_color=('#F2F2F2', '#181818'),
anchor='w').pack(side='left', fill='x', expand=True, padx=(6, 0))
# -- 快捷滑条 --
card, c = self._card(col,'快捷滑条 — 光强 / 定时投光 / TEC 温度')
@@ -375,11 +360,11 @@ class App(ctk.CTk):
command=self._dlpc_send_light).pack(side='left', padx=(6, 0))
rowb = ctk.CTkFrame(c, fg_color='transparent')
rowb.pack(fill='x', pady=(8, 0))
ctk.CTkLabel(rowb, text='定时投光(秒):').pack(side='left')
self.var_timer = tk.StringVar(value='2')
sld_t = ctk.CTkSlider(rowb, from_=0, to=32, number_of_steps=32,
ctk.CTkLabel(rowb, text='定时投光(秒):').pack(side='left')
self.var_timer = tk.StringVar(value='2000')
sld_t = ctk.CTkSlider(rowb, from_=0, to=32700, number_of_steps=327,
command=lambda v: self.var_timer.set(str(int(round(float(v))))))
sld_t.set(2)
sld_t.set(2000)
sld_t.pack(side='left', fill='x', expand=True, padx=8)
self.sld_timer = sld_t
self.ent_timer = ctk.CTkEntry(rowb, width=56, justify='center',
@@ -450,16 +435,15 @@ class App(ctk.CTk):
self.cbo_mode.set(d['cur_mode'])
elif keys:
self.cbo_mode.set(keys[0])
self.tv_info.delete(*self.tv_info.get_children())
self._clear_edid_rows()
if not d['edid']:
self.tv_info.insert('', 'end',
values=('EDID', '读取失败(注册表无 EDID 数据)'))
self._add_edid_row('EDID', '读取失败(注册表无 EDID 数据)')
self._set_text(self.txt_modes, '')
self._set_text(self.txt_hex, '')
return
e = edid_parse.parse_edid(d['edid'])
for k, v in edid_parse.edid_info_lines(e):
self.tv_info.insert('', 'end', values=(k, v))
self._add_edid_row(k, v)
lines = []
for k, t in enumerate(e['dtds']):
src = 'DTD 首选' if k == 0 else 'DTD'
@@ -474,6 +458,18 @@ class App(ctk.CTk):
self._set_text(self.txt_modes, '\n'.join(lines))
self._set_text(self.txt_hex, edid_parse.hex_dump(d['edid']))
def _clear_edid_rows(self):
for w in self.edid_scroll.winfo_children():
w.destroy()
def _add_edid_row(self, k, v):
row = ctk.CTkFrame(self.edid_scroll, fg_color='transparent')
row.pack(fill='x', pady=1)
ctk.CTkLabel(row, text=k, width=132, anchor='w',
text_color='gray60').pack(side='left')
ctk.CTkLabel(row, text=v, anchor='w').pack(side='left',
fill='x', expand=True)
def _apply_mode(self):
d = self._cur_display()
if d is None or self.cbo_mode.get() not in self._modes:
@@ -620,8 +616,25 @@ class App(ctk.CTk):
# 光机控制(RS485)
# ============================================================
def _dlpc_on_event(self, line):
# 温度查询应答: okTL:<0.1°C>, 依发送顺序归入 DMD/LAMP 实时显示框
m = re.search(r'← okTL:(-?\d+)', line)
if m and self._tl_order:
which = self._tl_order.pop(0)
try:
text = f'{which}: {int(m.group(1)) / 10:.1f}°C'
except ValueError:
text = f'{which}: {m.group(1)}'
self._dlpc_log_q.put(('temp', text))
self._dlpc_log_q.put(('event', line))
def _query_dmd(self):
self._tl_order.append('DMD')
self._dlpc_send('M201')
def _query_lamp(self):
self._tl_order.append('LAMP')
self._dlpc_send('M202')
def _dlpc_on_status(self, msg):
self._dlpc_log_q.put(('status', msg))
@@ -639,6 +652,11 @@ class App(ctk.CTk):
else:
color = 'gray60'
self.lbl_dlpc_st.configure(text=text, text_color=color)
elif kind == 'temp':
if text.startswith('DMD'):
self.var_dmd.set(text)
else:
self.var_lamp.set(text)
self._dlpc_log(text)
except queue.Empty:
pass
@@ -733,13 +751,13 @@ class App(ctk.CTk):
self.var_light.set(str(v))
def _timer_entry_apply(self, _evt=None):
"""定时滑条数值输入框回车: 校验并限幅 0-32, 同步回滑条"""
"""定时滑条数值输入框回车: 校验并限幅 0-32700(ms), 同步回滑条"""
try:
v = int(self.var_timer.get())
except ValueError:
self.var_timer.set(str(int(float(self.sld_timer.get()))))
return
v = max(0, min(32, v))
v = max(0, min(32700, v))
self.sld_timer.set(v)
self.var_timer.set(str(v))
@@ -758,9 +776,9 @@ class App(ctk.CTk):
try:
v = int(self.var_timer.get())
except ValueError:
messagebox.showinfo('定时非法', '定时投光需为 0-32 的整数')
messagebox.showinfo('定时非法', '定时投光需为 0-32700 的整数毫秒(0=不限时)')
return
self._dlpc_send('M730S0' if v <= 0 else f'M730S0T{v * 1000}')
self._dlpc_send('M730S0' if v <= 0 else f'M730S0T{v}')
def _tec_entry_apply(self, _evt=None):
"""TEC 温度输入框回车: 校验并限幅 200-350(0.1°C), 同步回滑条"""