style(HDMI-Tool): 按用户标记统一左列高度与温度实时显示
This commit is contained in:
+4
-3
@@ -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.
@@ -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
@@ -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), 同步回滑条"""
|
||||
|
||||
Reference in New Issue
Block a user