01b264fe97
- 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>
101 lines
6.1 KiB
Markdown
101 lines
6.1 KiB
Markdown
# 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"` 直接子 widget(Qt5 私有
|
||
`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)。
|