Files
scuc-qt-course/docs/superpowers/specs/2026-07-08-lineedit-eye-toggle-design.md
T
张宗平 01b264fe97 Day4 教学示例补三个:QDialog 综合 / QLineEdit 小眼睛 / 文件 IO(+顶层构建注册)
- qdialog_showcase:wiki「6 对话框 QDialog」6.2–6.5 综合演示。含两套实现:
  · Designer 表单版(dialog.{h,cpp,ui} + resources.qrc + source.gif,QMovie 播 GIF)
  · 纯代码版(qt_dialog_showcase.cpp,单文件,QWidget 布满按钮逐个演示 QDialog 行为)
- lineedit_eye_toggle:子类化 QLineEdit,paintEvent 自绘小眼睛、mouseEvent 命中切换
  echoMode,并与内置 clear button 动态避让(读 objectName=="clearButton" 子 widget 几何)。
  含独立子目录 CMakeLists.txt 供单独打开(顶层 add_subdirectory 不递归)。
- file_io_demo:QFile/QTextStream/QDataStream/QFileInfo/QDir/QTextCodec/QJson 五主题
  综合示例,每个按钮产出一个文件落到 demo_output/ 便于课堂手动查看。

构建注册:examples/CMakeLists.txt 用 add_teaching_example 注册前两个纯代码示例,
mandelbrot 多源文件单独 add_executable;calculator_mainwindow_qss 加 themes.qrc 与
AUTORCC;all_teaching_examples 依赖列表同步补齐。

附 lineedit_eye_toggle 设计文档(docs/superpowers/specs)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 16:51:09 +08:00

101 lines
6.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# QLineEdit 自绘小眼睛 / echo mode 切换 教学示例 —— 设计
- **日期**2026-07-08
- **对应课程**:Day4 上午「消息机制和事件 + 绘图事件」(OUTLINE_4DAY.md §2 Day4 上午)
- **目标产物**`docs/teaching/examples/lineedit_eye_toggle/`,交付为两种形态——
① 仓库统一构建里的 `qt_lineedit_eye_toggle` target
② 可独立 `cmake -S . -B build` 构建的单独 CMake 工程。
## 1. 背景与目标
需要一个 Day4 上午课堂演示示例,**同时覆盖两个主题**:组件绘制(`paintEvent`)、
事件捕捉(`mousePressEvent` / `mouseMoveEvent`)。载体是 QLineEdit 扩展——在右侧加一个
小眼睛图标,点击动态切换 `echoMode``Password``Normal`),并显式处理与 QLineEdit
内置 clear button 的位置冲突。
> 选 QLineEdit 而非自造控件:密码框是「自绘内嵌图标 + 点击切换」最真实的工业场景,
> 且复用 QLineEdit 的文本/编辑能力,让示例聚焦在「绘制 + 事件」本身。
## 2. 关键设计决策
### 2.1 组织形态:梯度对照版
窗口分三区,先简后深,让学生看清「为什么」:
| 区 | 内容 | 教学作用 |
|---|---|---|
| ① | 4 种 echo mode 静态对照(Normal/NoEcho/Password/PasswordEchoOnEdit | 先给 echo mode 全貌,眼睛只是切换载体 |
| ② | `addAction(QIcon, TrailingPosition)` 一行版 | Qt 内置做法,1 行实现——「为什么还要自绘」的引子 |
| ③ | `PasswordLineEdit : public QLineEdit` 自绘版 + 「启用 clear button」复选框 | **主题落地**:自绘 + 事件捕获 + clear button 避让 |
### 2.2 实现方式锁定:子类化 + 自绘 + 自捕获
`addAction` 那条路由 Qt 全权处理位置/点击/避让,**碰不到**绘制与事件这两个主题。
故核心交付物必须是子类化 QLineEdit、覆写 `paintEvent`/`mouse*Event`。addAction 仅作
②区对照存在。
### 2.3 clear button 避让:读 clearButton 子 widget 几何
QLineEdit 内置 clear button 的宽度随主题/DPI/字号变化,**不能硬编码偏移**(坑卡)。
clear button 是 QLineEdit 的一个 `objectName=="clearButton"` 直接子 widgetQt5 私有
`QLineEditIconButton`),用 `findChild<QWidget*>("clearButton")` 读其 `geometry()`,眼睛紧贴
其左侧;未启用或无文本时贴右边。
> 注:初版误用 `QStyle::subControlRect(CC_LineEdit, …, SC_LineEditClearButton)`,编译期即报错
> ——`QStyle` 没有这两个成员,QLineEdit 不经 complex-control 绘制,无对应 subcontrol。读子 widget 是唯一可靠途径。
### 2.4 眼睛图标:QPainter 纯代码绘制
- 不引入 `.qrc` 资源(YAGNI),完全契合「组件绘制」主题。
- 两态:`closed=true`(密码态)= 眼眶椭圆 + 斜杠;`closed=false`(明文态)= 眼眶椭圆 + 实心瞳孔。
- 绘制逻辑抽成自由函数 `paintEye(QPainter*, QRect, closed, hover)`,③区自绘与②区
`QIcon` pixmap 共用(DRY,并让对照更明显)。
- hover 态描边加深为蓝色(`#0078D7`),需 `setMouseTracking(true)` 才能在无按键时收到 move。
### 2.5 双形态 CMake(源文件零重复)
- **唯一源文件**`qt_lineedit_eye_toggle.cpp`
- **两个 CMake 上下文**引用它:
- 独立工程:`lineedit_eye_toggle/CMakeLists.txt`(手写规范版,C++17,非 Qt Creator 模板)。
- 仓库集成:`examples/CMakeLists.txt``add_teaching_example(qt_lineedit_eye_toggle lineedit_eye_toggle)`
- **零污染**:顶层 `CMakeLists.txt:58` `add_subdirectory(docs/teaching/examples)` 不递归,
子目录 `CMakeLists.txt` 永不被统一构建触发;`examples/CMakeLists.txt` 无 GLOB。
不计入 README/CLAUDE.md 里「173 目标」的生成器计数。
## 3. `PasswordLineEdit` 子类要点
```
构造: setEchoMode(Password); setMouseTracking(true); setTextMargins(0,0,iconSize+8,0)
paintEvent: base::paintEvent 先画原生; 再 recomputeEyeRect(); QPainter 画眼睛
mousePressEvent: recomputeEyeRect(); 命中眼睛 → 切 m_passwordMode + setEchoMode + accept;
否则 base::mousePressEvent(光标定位/双击选词不受影响)
mouseMoveEvent: recomputeEyeRect(); hit-test 更新 m_eyeHover + setCursor; base 保留拖选
leaveEvent: 清 m_eyeHover 局部刷新
recomputeEyeRect: findChild("clearButton") 读其几何 → 眼睛贴其左 / 贴右
信号: passwordModeChanged(bool) 供窗口状态栏显示
```
**关键不变量**:命中眼睛时 `e->accept()``return`**不**调基类——否则 QLineEdit 会
把点击当光标定位;非命中时务必调基类,保留原生编辑行为。这是「事件捕获」教学点的核心。
## 4. 文件清单
```
docs/teaching/examples/lineedit_eye_toggle/
├── qt_lineedit_eye_toggle.cpp # 唯一源文件(paintEye + PasswordLineEdit + ShowcaseWidget + main
├── CMakeLists.txt # 独立顶层工程(手写规范版,可 cmake -S . -B build
└── README.md # 教学点 / 目录结构 / 构建(集成+独立两条)/ 坑卡
```
另:`docs/teaching/examples/CMakeLists.txt` 注册目标 + 加入 `all_teaching_examples` 依赖列表。
## 5. 验证标准(可证伪)
1. **集成构建**`cmake --build build_mw --target qt_lineedit_eye_toggle` 绿。
2. **独立构建**`cmake -S lineedit_eye_toggle -B lineedit_eye_toggle/build ... && cmake --build` 绿。
3. **离屏**:两路径产物 `QT_QPA_PLATFORM=offscreen` 跑 200ms 后退出码 0。
4. **真实交互**(本机 Qt 实跑,截图为证):③区眼睛可见;点击切 `Password↔Normal`
勾选 clear button 后眼睛正确让到其左侧不重叠;hover 高亮生效。
## 6. 显式假设
- 眼睛用 QPainter 代码绘制,不引入资源文件(契合主题 + YAGNI)。
- 独立 `CMakeLists.txt` 手写规范版(C++17 / `cmake_minimum_required(3.16)` / 无 ANDROID 噪音),
不沿用子目录里 Qt Creator 导出的低质量模板。
- ①区 4 个对照框 `setReadOnly(true)` 防误改;`PasswordEchoOnEdit` 的「编辑时明文」效果在
README 注明需取消只读才能体验。
- **不提交 git**(项目红线:未经请求不操作 git)。