# QLineEdit 自绘小眼睛 / echo mode 切换(qt_lineedit_eye_toggle) Day4 上午教学示例(OUTLINE_4DAY.md Day4 上午「消息机制和事件 + 绘图事件」),**同时覆盖两个主题**: - **组件绘制**——覆写 `paintEvent`,用 QPainter 在 QLineEdit 右侧自绘眼睛图标; - **事件捕捉**——覆写 `mousePressEvent` / `mouseMoveEvent`,几何 hit-test 命中眼睛时切换 `echoMode`,且不阻断基类的光标定位 / 拖选。 载体是 QLineEdit 密码框:右侧一个自绘小眼睛,点击在 `Password` ↔ `Normal` 间切换,并显式 处理与 QLineEdit 内置 clear button 的位置冲突。 ## 三区对照(先简后深) | 区 | 内容 | 教学作用 | |---|---|---| | ① echo mode 四态 | Normal / NoEcho / Password / PasswordEchoOnEdit 静态对照 | 先给 echo mode 全貌,眼睛只是切换载体 | | ② addAction 一行版 | `addAction(QIcon, TrailingPosition)` 加眼睛,点击切换 | Qt 内置、1 行实现——「为什么还要自绘」的引子 | | ③ 自绘版主秀 | `PasswordLineEdit` 自绘眼睛 + 「启用 clear button」复选框 | 主题落地 + clear button 避让 | ## 关键教学点 ### 1. 组件绘制 - `paintEvent` **先调基类**画原生内容(文本 / 边框 / clear button),再用 QPainter 叠加眼睛。 顺序不能反——基类画完后 clear button 几何才最终确定,眼睛避让才准。 - 眼睛用 QPainter 纯代码绘制(眼眶椭圆 + 瞳孔 / 斜杠两态),无外部资源。绘制逻辑抽成自由函数 `paintEye()`,③区自绘与②区 `QIcon` pixmap 共用(DRY,且对照更直观)。 ### 2. 事件捕捉 - `mousePressEvent`:命中眼睛矩形 → 切 `echoMode` + `e->accept()` + `return`(**不**调基类, 否则 QLineEdit 把这次点击当光标定位);非命中 → 调基类(光标定位 / 双击选词 / 右键菜单照常)。 这是「自绘交互区与原生编辑行为共存」的核心。 - `mouseMoveEvent` + `setMouseTracking(true)`:实现 hover 高亮。**坑卡**:`mouseTracking` 默认关, 不按鼠标键就收不到 `mouseMoveEvent`,hover 高亮出不来。 ### 3. clear button 避让(读 clearButton 子 widget 几何) QLineEdit 内置 clear button 的宽度随主题 / DPI / 字号变化,**不能硬编码偏移**(硬编码是反面教材, 换主题即错位)。正确做法:clear button 是 QLineEdit 的一个 `objectName=="clearButton"` 直接子 widget (Qt5 私有 `QLineEditIconButton`),用 `findChild` 读它的真实几何: ```cpp if (QWidget* cb = findChild(QStringLiteral("clearButton"), Qt::FindDirectChildrenOnly)) { rightEdge = cb->x() - 4; // 眼睛贴 clear button 左侧,留 4px 缝 } ``` 眼睛紧贴其左侧;未启用 clear button 或无文本(clear button 不占位)时贴右边。勾选③区复选框即可实时 观察眼睛从「贴右」移到「贴 clear button 左侧」、两者不重叠。 > 为什么不用 `QStyle::subControlRect`?QLineEdit 不经 QStyle 的 complex-control 绘制, > `QStyle` 没有 `CC_LineEdit` / `SC_LineEditClearButton`,无对应 subcontrol 可查——读子 widget 是唯一可靠途径。 ## 目录结构 ``` lineedit_eye_toggle/ ├── qt_lineedit_eye_toggle.cpp # 唯一源文件(paintEye + PasswordLineEdit + ShowcaseWidget + main) ├── CMakeLists.txt # 独立顶层工程(可单独 cmake -S . -B build,不走仓库统一构建) └── README.md # 本文档 ``` ## 构建与运行(Windows + MinGW 7.3.0 + Qt 5.14.2) > 必须用与 Qt 5.14.2 ABI 匹配的 mingw730_64,不可用 PATH 中更高版本的 g++。 ### A. 集成构建(仓库统一构建树) ```bash # 配置(在仓库根) cmake -S . -B build_mw -G "MinGW Makefiles" \ -DCMAKE_PREFIX_PATH=E:/Qt/Qt5.14.2/5.14.2/mingw73_64 \ -DCMAKE_CXX_COMPILER=E:/Qt/Qt5.14.2/Tools/mingw730_64/bin/g++.exe \ -DCMAKE_C_COMPILER=E:/Qt/Qt5.14.2/Tools/mingw730_64/bin/gcc.exe cmake --build build_mw --target qt_lineedit_eye_toggle # 运行(GUI,点 ②③ 的眼睛、勾选 clear button 体验) PATH=/e/Qt/Qt5.14.2/5.14.2/mingw73_64/bin:$PATH \ QT_QPA_PLATFORM_PLUGIN_PATH=/e/Qt/Qt5.14.2/5.14.2/mingw73_64/plugins/platforms \ ./build_mw/docs/teaching/examples/qt_lineedit_eye_toggle.exe # 离屏自动退出验证(不交互,退出码应为 0) QT_QPA_PLATFORM=offscreen \ QT_QPA_PLATFORM_PLUGIN_PATH=/e/Qt/Qt5.14.2/5.14.2/mingw73_64/plugins/platforms \ PATH=/e/Qt/Qt5.14.2/5.14.2/mingw73_64/bin:$PATH \ ./build_mw/docs/teaching/examples/qt_lineedit_eye_toggle.exe; echo "exit=$?" ``` ### B. 独立构建(单独 cmake 工程) ```bash cd docs/teaching/examples/lineedit_eye_toggle cmake -S . -B build -G "MinGW Makefiles" \ -DCMAKE_PREFIX_PATH=E:/Qt/Qt5.14.2/5.14.2/mingw73_64 \ -DCMAKE_CXX_COMPILER=E:/Qt/Qt5.14.2/Tools/mingw730_64/bin/g++.exe \ -DCMAKE_C_COMPILER=E:/Qt/Qt5.14.2/Tools/mingw730_64/bin/gcc.exe cmake --build build PATH=/e/Qt/Qt5.14.2/5.14.2/mingw73_64/bin:$PATH \ QT_QPA_PLATFORM_PLUGIN_PATH=/e/Qt/Qt5.14.2/5.14.2/mingw73_64/plugins/platforms \ ./build/qt_lineedit_eye_toggle.exe ``` > 两条路径编译同一份 `qt_lineedit_eye_toggle.cpp`,行为一致。集成路径进入仓库统一构建; > 独立路径可把这个目录单独拷走即用(仅需本目录三文件 + 一个 Qt 5.14.x)。