- UID
- 5043
- 积分
- 1349
- 精华
- 贡献
-
- 威望
-
- 活跃度
-
- D豆
-
- 在线时间
- 小时
- 注册时间
- 2002-5-13
- 最后登录
- 1970-1-1
|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
本帖最后由 LoveArx 于 2025-11-27 14:42 编辑
🔤 AutoCAD 输入法自动切换 - 技术总结
ObjectARX 开发技术文档
一、功能概述
在 AutoCAD 中,当焦点进入命令行或动态输入框时,自动将输入法切换为英文模式,避免用户手动切换输入法。
目标区域:
| 区域 | 说明 | | 传统命令行 | AutoCAD 2015 及以前版本的 MFC 命令行 | | WPF 命令行 | AutoCAD 2016 及以后版本的 WPF 命令行 | | 动态输入框 | 在绘图区光标旁边弹出的输入框 |
二、技术路线
整体架构:
┌─────────────────────────────────────────────────────────────┐
│ 整体架构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ CBT Hook │───→│ 焦点变化检测 │───→│ IME 状态切换 │ │
│ │ (WH_CBT) │ │ │ │ (IMM API) │ │
│ └──────────┘ └──────────────┘ └──────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ HCBT_SETFOCUS IsTargetEditArea() ImmSetOpenStatus() │
│ │
└─────────────────────────────────────────────────────────────┘
核心流程:
用户按键 L
│
▼
AutoCAD 激活 WPF 命令行
│
▼
CBT Hook 捕捉 HCBT_SETFOCUS
│
├─ hWndGainFocus = HwndWrapper (目标:是)
├─ hWndLoseFocus = 绘图区 (目标:否)
│
▼
IsTargetEditArea() 判断
│
├─ IsWpfCommandLine() → 类名含 "HwndWrapper" → true
│
▼
OnEnterEditArea()
│
├─ IsChineseMode() → 检查当前是否中文
├─ SetEnglishMode() → ImmSetOpenStatus(FALSE)
├─ m_bInEditBox = TRUE
│
▼
用户输入 L(此时已是英文)
│
▼
焦点切换到 Edit(动态输入框)
│
├─ 两者都是目标区域 → 不处理
│
▼
命令完成,焦点离开
│
▼
OnLeaveEditArea()
│
├─ m_bInEditBox = FALSE
└─ 不恢复 IME 状态(保持当前状态)
三、核心组件
1. Hook 机制 使用 Windows CBT Hook 监控焦点变化:  -
- // 安装 Hook
- m_hHook = SetWindowsHookEx(
- WH_CBT, // CBT Hook 类型
- CBTHookProc, // 回调函数
- NULL, // 本进程
- GetCurrentThreadId() // 当前线程
- );
-
- // Hook 回调
- LRESULT CALLBACK CBTHookProc(int nCode, WPARAM wParam, LPARAM lParam)
- {
- if (nCode == HCBT_SETFOCUS)
- {
- HWND hWndGainFocus = (HWND)wParam; // 获得焦点的窗口
- HWND hWndLoseFocus = (HWND)lParam; // 失去焦点的窗口
- // 处理焦点变化...
- }
- return CallNextHookEx(m_hHook, nCode, wParam, lParam);
- }
2. 目标区域识别
| 区域 | 识别方法 | | 传统命令行 | acedGetAcadDockCmdLine() / acedGetAcadTextCmdLine() 的子窗口 | | WPF 命令行 | 类名包含 HwndWrapper | | 动态输入框 | 父窗口链中有 CAcDynInputWndControl |
 -
- // 判断是否为 WPF 命令行
- bool IsWpfCommandLine(HWND hWnd) const
- {
- if (!hWnd) return false;
- if (! IsAutoCADWindow(hWnd)) return false;
-
- TCHAR szClassName[256] = {0};
- GetClassName(hWnd, szClassName, _countof(szClassName));
-
- // 只要是 HwndWrapper 就认为是目标区域
- if (_tcsstr(szClassName, _T(\"HwndWrapper\")) != nullptr)
- return true;
-
- return false;
- }
-
- // 判断是否为动态输入窗口
- bool IsDynamicInputWindow(HWND hWnd) const
- {
- if (! hWnd) return false;
- if (!IsAutoCADWindow(hWnd)) return false;
-
- TCHAR szClassName[256] = {0};
- GetClassName(hWnd, szClassName, _countof(szClassName));
-
- // Edit 控件,检查父窗口链
- if (_tcsicmp(szClassName, _T(\"Edit\")) == 0)
- {
- HWND hParent = GetParent(hWnd);
- while (hParent)
- {
- TCHAR szParentText[64] = {0};
- GetWindowText(hParent, szParentText, _countof(szParentText));
- if (_tcsstr(szParentText, _T(\"CAcDynInputWndControl\")) != nullptr)
- return true;
- hParent = GetParent(hParent);
- }
- }
-
- return false;
- }
3. IME 操作
 -
- // 检查当前是否为中文模式
- bool IsChineseMode() const
- {
- HWND hWnd = GetFocus();
- HIMC hImc = ImmGetContext(hWnd);
- if (!hImc) return false;
-
- BOOL bOpen = ImmGetOpenStatus(hImc);
- DWORD dwConv = 0, dwSent = 0;
- ImmGetConversionStatus(hImc, &dwConv, &dwSent);
-
- ImmReleaseContext(hWnd, hImc);
-
- return bOpen && (dwConv & IME_CMODE_NATIVE);
- }
-
- // 设置为英文模式
- void SetEnglishMode()
- {
- HWND hWnd = GetFocus();
- HIMC hImc = ImmGetContext(hWnd);
- if (!hImc) return;
-
- ImmSetOpenStatus(hImc, FALSE);
- ImmReleaseContext(hWnd, hImc);
- }
四、关键注意点
⚠️ 1. 不要在 Hook 回调中调用 AutoCAD API
在 Hook 回调中调用 AutoCAD API(如 acedGetAcadDockCmdLine())会触发命令行激活,导致首字母丢失。
❌ 错误写法: -
- LRESULT CALLBACK CBTHookProc(... ) {
- acedGetAcadDockCmdLine(); // 不要在这里调用!
- }
✅ 正确写法: -
- bool CImeAutoSwitch::Enable() {
- CWnd* p = acedGetAcadDockCmdLine();
- s_hDockCmdLine = p ? p->m_hWnd : NULL;
- }
⚠️ 2. 使用 m_hWnd 而不是 GetSafeHwnd()
根据 ObjectARX 文档,跨 MFC DLL 时直接使用 m_hWnd 更安全:
✅ 推荐: -
- CWnd* pCmdLine = acedGetAcadDockCmdLine();
- HWND hWnd = pCmdLine ? pCmdLine->m_hWnd : NULL;
⚠️ 可能有问题: -
- HWND hWnd = pCmdLine->GetSafeHwnd();
- // 跨 MFC DLL 调用成员函数可能出问题
⚠️ 3. 区分目标区域内部切换
目标区域内部切换(如从 WPF 命令行切到动态输入框的 Edit)不应该触发"进入/离开"事件:  -
- bool bGainInTarget = IsTargetEditArea(hWndGainFocus);
- bool bLoseInTarget = IsTargetEditArea(hWndLoseFocus);
-
- if (bGainInTarget && bLoseInTarget) {
- // 目标区域内部切换(如 WPF→Edit),不处理
- }
- else if (bGainInTarget && !bLoseInTarget) {
- // 进入目标区域,切换到英文
- OnEnterEditArea(hWndGainFocus);
- }
- else if (!bGainInTarget && bLoseInTarget) {
- // 离开目标区域
- OnLeaveEditArea(hWndLoseFocus);
- }
⚠️ 4. 使用状态标志避免重复处理 -
- // 进入时
- if (! m_bInEditBox) {
- OnEnterEditArea(hWnd);
- }
-
- // 离开时
- if (m_bInEditBox) {
- OnLeaveEditArea(hWnd);
- }
⚠️ 5. 正确的启用时机
✅ 正确:kLoadDwgMsg -
- case AcRx::kLoadDwgMsg:
- CImeAutoSwitch::GetInstance().Enable();
- break;
- // 此时命令行已创建
❌ 错误:kInitAppMsg -
- case AcRx::kInitAppMsg:
- // 太早了!命令行可能未创建
- // 可能导致输入法冻结
⚠️ 6. DockCmdLine 和 TextCmdLine 的区别
| 函数 | 返回窗口 | | acedGetAcadDockCmdLine() | 可停靠的命令行面板(外层容器) | | acedGetAcadTextCmdLine() | 文本命令行窗口(实际输入区域) |
┌─────────────────────────────────────────────────┐
│ AutoCAD 命令行面板 (DockCmdLine) │
│ ┌───────────────────────────────────────────┐ │
│ │ 命令: LINE │ │ ← TextCmdLine
│ │ 指定第一点: │ │ (文本输入区)
│ │ _ │ │
│ └───────────────────────────────────────────┘ │
│ [最近输入] [历史记录] [其他按钮...] │
└─────────────────────────────────────────────────┘
↑
DockCmdLine (整个可停靠面板)
五、代码结构
CImeAutoSwitch (单例)
│
├── Enable() // 启用,获取命令行句柄,安装 Hook
├── Disable() // 禁用,卸载 Hook
│
├── CBTHookProc() // 静态回调,监控焦点变化
│ ├── IsTargetEditArea() // 判断是否在目标区域
│ │ ├── IsCommandLineWindow() // 传统命令行
│ │ ├── IsWpfCommandLine() // WPF 命令行
│ │ └── IsDynamicInputWindow() // 动态输入
│ │
│ ├── OnEnterEditArea() // 进入:切换到英文
│ └── OnLeaveEditArea() // 离开:不做操作
│
├── IsChineseMode() // 检查当前是否中文
├── SetEnglishMode() // 切换到英文
│
└── 静态成员
├── s_hDockCmdLine // 命令行面板句柄
└── s_hTextCmdLine // 文本命令行句柄
六、兼容性
| AutoCAD 版本 | 命令行类型 | 识别方法 | | 2015 及以前 | 传统 MFC | acedGetAcadDockCmdLine() 子窗口 | | 2016 及以后 | WPF | 类名含 HwndWrapper | | 所有版本 | 动态输入 | 父窗口含 CAcDynInputWndControl |
七、常见问题
Q1: 首字母丢失 原因:在 Hook 回调中调用了 AutoCAD API,触发了命令行激活。 解决:命令行句柄在 Enable() 时获取并缓存,Hook 回调中不调用任何 AutoCAD API。
Q2: 输入法冻结 原因:在 kInitAppMsg 时调用 IMM API,AutoCAD 尚未完全初始化。 解决:在 kLoadDwgMsg 时启用功能。
Q3: HwndWrapper 未被识别
原因:之前的代码用位置判断 HwndWrapper 是否在命令行区域内,但获取的命令行句柄可能不正确。 解决:简化判断,只要类名包含 HwndWrapper 且属于 AutoCAD 进程就认为是目标区域。
Q4: 目标区域内部切换触发重复处理
原因:焦点从 WPF 命令行切到动态输入 Edit 时,被判断为"离开+进入"。 解决:判断时检查 bGainInTarget && bLoseInTarget,这种情况不处理。
八、依赖
 -
- #include
- #include
- #pragma comment(lib, \"imm32. lib\")
-
- // ObjectARX
- #include // acedGetAcadDockCmdLine, acedGetAcadTextCmdLine
AutoCAD 输入法自动切换 - 技术总结 | ObjectARX 开发 | 2025 |
|