分类: C/C++
2008-03-18 15:48:45
如果可能我想用打包类来实现。唉!,就叫我封装先生吧。
你算是找对地方了。但是我要先声明我的解决办法不是你所希望的-甚至也不是我自己所希望的!
(图一)
我第一次看到你的问题时,说句实话,我还从来没有听说过 IAutoComplete:
IAutocomplete 和 IAutoComplete2 IAutoComplete HRESULT Init( HWND hwndEdit, // 编辑控制或组合框 IUnknown *punkACL, // 实现 IEnumString 的对象指针 LPCOLESTR pwszRegKeyPath, // 存储格式串的注册库路径 LPCOLESTR pwszQuickComplete); // CTRL+Enter的格式化串 // Enable 或 Disable 自动完成功能 HRESULT Enable(BOOL fEnable); enum { ACO_NONE = 0, ACO_AUTOSUGGEST = 0x1, ACO_AUTOAPPEND = 0x2, ACO_SEARCH = 0x4, ACO_FILTERPREFIXES= 0x8, ACO_USETAB = 0x10, ACO_UPDOWNKEYDROPSLIST= 0x20, ACO_RTLREADING = 0x40 } AUTOCOMPLETEOPTIONS; IAutoComplete2 HRESULT SetOptions(DWORD dwFlag); HRESULT GetOptions(DWORD *pdwFlag);你是不是觉得我应该熟悉微软发布的每一个新的COM接口? 对我来说这似乎是个好主意。
选项标志 | 描述 |
ACO_NONE | 没有自动完成 |
ACO_AUTOSUGGEST | 启用自动建议的下拉列表框 |
ACO_AUTOAPPEND | 启用自动添加 |
ACO_SEARCH | 在完成的串中添加搜索项目,选中此项目启动搜索引擎 |
ACO_FILTERPREFIXES | 不匹配逗号前缀,如“www.”,“http://”等 |
ACO_USETAB | 使用Tab键从下拉框清单中选择 |
ACF_UPDOWNKEYDROPSLIST | 使用上下箭头键显示自动建议的下拉框清单 |
ACO_RTLREADING | 常规窗口从左到右显示文本。Windows 可以被映射显示诸如 Hebrew 或 Arabic 这样从右到左阅读的语言。通常,某个控制的文本与其父窗口文本的阅读/显示方向相同。如果设置ACO_RTLREADING,那么文本阅读方向与其父窗口文本阅读方向相反。 |
IAutoComplete 与 IEnumString 一起工作,IEnumString是一个通用的枚举串列表。你只要将一个串枚举器指针和一个 Windows 编辑框或组合框句柄赋给IAutoComplete对象,其它的事情你就不用管了。如果你想设置发烧选项,就使用IAutoComplete2接口。每一个COM接口都是使用二号版本加以完善的,即便它只有两个方法。
IAutoComplete有一个缺陷,它只存在于Windows 2000,具体地说,实现IAutoComplete(CLSID_IAutoComplete)的COM对象位于shell32.dll的5.0版本中,它只随Windows 2000一起发布,Windows 95,Windows 98和Windows NT 4.0中则没有。如果你要使用它,要做的第一件事情是实现IEnumString接口。当我劳神费力处理完 QueryInterface,AddRef,Release以及CLSIDs,CoInitialize,并在构造器中决定了m_dwRef是取0还是1后,然后我使用自己认为还不错的方法,并打算经历所有痛苦和磨难来封装IAutoComplete,如果最终这个类将只能在Windows 2000中运行,那对我所做的努力打击实在是太大了。
这真是个难题,我该怎么办呢?我们的目的是在一个列表串中搜索与用户输入匹配的串。自己来写这种代码有多难啊!现代编程的问题之一是没有人愿意多写代码。不要让我犯错误-COM很棒。但是除非你已经有一个现成的IEnumString,否则对于autocompletion来说似乎是太繁琐了。下面是我写的一个类,CAutoComplete:
//////////////////////////////////////////////////////////////// // AutoCompl.h // #pragma once #include "subclass.h" /////////////////////////////////////////////////////////////////// // 只是个通用可重用类,你可以将它用于编辑框和组合框的自动完成输入应用. // // 使用方法: // - 实例化你想要 hook 的编辑/组合控制 // - 添加一些串到串数组中 // - 调用 Init class CAutoCompleteWnd : public CSubclassWnd { protected: CStringArray m_arStrings; // 串列表(数组) CString m_sPrevious; // 前一个内容 int m_bIgnoreChangeMsg; // 忽略 EN_CHANGE 消息 UINT m_idMyControl; // 子类化的控制 ID int m_iType; // 控制类型(编辑/组合) int m_iCurString; // 当前串的索引 enum { Edit=1,ComboBox }; // hook 函数 virtual LRESULT WindowProc(UINT msg, WPARAM wp, LPARAM lp); // 可以重载的辅助函数 virtual UINT GetMatches(LPCTSTR pszText, CStringArray& arMatches, BOOL bFirstOnly=FALSE); virtual void OnFirstString(); virtual BOOL OnNextString(CString& sNext); virtual BOOL OnMatchTest(const CString& s, const CString& sMatch); virtual BOOL IgnoreCompletion(CString s); virtual void OnComplete(CWnd* pWnd, CString s); public: CAutoCompleteWnd(); ~CAutoCompleteWnd(); void Init(CWnd* pWnd); CStringArray& GetStringList() { return m_arStrings; } }; AutoCompl.cpp //////////////////////////////////////////////////////////////// // VCKBASE August 2000 // Visual C++ 6.0环境编译, 在 Windows 98 或 Windows NT 中运行. // #include "stdafx.h" #include "autocompl.h" ////////////////// // 构造函数: 用于初始化 CAutoComplete::CAutoComplete() { m_bIgnoreChangeMsg=0; m_iType = 0; m_idMyControl = 0; m_iCurString = 0; } CAutoComplete::~CAutoComplete() { } //////////////////////////////////////////////////////////////////////// // 安装 hook. 初始化控制 ID 和基于类名的控制类型 // void CAutoComplete::Init(CWnd* pWnd) { CSubclassWnd::HookWindow(pWnd->GetParent()); CString sClassName; ::GetClassName(pWnd->GetSafeHwnd(), sClassName.GetBuffer(32), 32); sClassName.ReleaseBuffer(); if (sClassName=="Edit") { m_iType = Edit; } else if (sClassName=="ComboBox") { m_iType = ComboBox; } m_idMyControl = pWnd->GetDlgCtrlID(); } ////////////////////////////////////////////////////////////////////////// // 扫描串数组中匹配的文本,并添加匹配项到一个新数组,返回匹配项的数目。 // 对于编辑控制而言, 只需要找到第一个串,在这里使用了一个BOOL参数。 // // UINT CAutoComplete::GetMatches(LPCTSTR pszText, CStringArray& arMatches, BOOL bFirstOnly) { arMatches.RemoveAll(); int nMatch = 0; CString s=pszText; if (s.GetLength()>0) { OnFirstString(); CString sMatch; while (OnNextString(sMatch)) { if (OnMatchString(s, sMatch)) { TRACE("Add %s\n",(LPCTSTR)sMatch); arMatches.Add(sMatch); nMatch++; if (bFirstOnly) break; } } } return nMatch; } /////////////////////////////////////////////////////////////////////////// // 这个虚拟函数的参数是两个字符串,如果有匹配则返回 TRUE. 缺省情况下实现普通的 // 前缀比较-你可以重载它,例如,在忽略掉两个串中的‘www’字符。 // // BOOL CAutoComplete::OnMatchString(const CString& s, const CString& sMatch) { return s==sMatch.Left(s.GetLength()); } void CAutoComplete::OnFirstString() { m_iCurString=0; } BOOL CAutoComplete::OnNextString(CString& sNext) { if (m_iCurString < m_arStrings.GetSize()) { sNext = m_arStrings[m_iCurString++]; return TRUE; } sNext = (LPCTSTR)NULL; return FALSE; } //////////////////////////////////////////////////////////////////////// // "hook" 函数截取发送到编辑控制和组合框的消息.我只对 EN_CHANGE 或 // CBN_EDITCHANGE感兴趣: 编辑控制的内容已经改变. // LRESULT CAutoComplete::WindowProc(UINT msg, WPARAM wp, LPARAM lp) { if ((msg==WM_COMMAND && LOWORD(wp)==m_idMyControl) && ((m_iType==Edit && HIWORD(wp)==EN_CHANGE) || (m_iType==ComboBox && HIWORD(wp)==CBN_EDITCHANGE))) { // 因为我要改变控制的内容,它将触发更多的EN_CHANGE消息, // 当我获得控制时使用 m_bIgnoreChangeMsg 结束处理. if (!m_bIgnoreChangeMsg++) { CString s; CWnd* pWnd = CWnd::FromHandle((HWND)lp); pWnd->GetWindowText(s); OnComplete(pWnd, s); } m_bIgnoreChangeMsg--; } return CSubclassWnd::WindowProc(msg, wp, lp); } //////////////////////////////////////////////////////////////////////// // 这是实现输入完成的主函数. // void CAutoComplete::OnComplete(CWnd* pWnd, CString s) { CStringArray arMatches; // 匹配串 if (s.GetLength()>0 && GetMatches(s, arMatches, m_iType==Edit)>0) { DoCompletion(pWnd, s, arMatches); } m_sPrevious=s; // 记住当前串 } void CAutoComplete::DoCompletion(CWnd* pWnd, CString s, const CStringArray& arMatches) { if (m_iType==ComboBox) { // 这种强制转换从技术上讲是不正确的,但它是一个标准的MFC诀窍. CComboBox* pComboBox = (CComboBox*)pWnd; // 更新下拉框以反映可能的匹配 pComboBox->ResetContent(); for (int i=0; i这个类大体上实现了 autocompletion,不用COM,也不用shell32.dll,它只是一个简单的类而已,你可以将它的cpp文件添加到你的应用,DLL或者扩展库中。它可以工作于任何的Windows版本,甚至是Windows 3.1。 CAutoComplete没有实现IAutoComplete中的所有的特性。例如,IAutoComplete有一个特性是当用户按下 Ctrl+Enter 时的快速完成格式串。这个格式串是一个Windows用来转换用户输入的 sprintf 串。如果这个格式串是“%s.com” 并且用户敲入“woowoo”,IAutoComplete 将完成整个内容woowoo.com。另外一个IAutoComplete特性是让你指定一个串作为注册键来存储格式串。这些特性都很好,但他们太IE化,似乎不属于通用的AddString(arMatches[i]); } // 用户箭头光标,这样用户才能选择 ::SetCursor(LoadCursor(NULL,MAKEINTRESOURCE(IDC_ARROW))); // 显示下拉框 pComboBox->ShowDropDown(); pComboBox->SetWindowText(IgnoreCompletion(s) ? s : arMatches[0]); pComboBox->SetEditSel(s.GetLength(),-1); } else if (m_iType==Edit && !IgnoreCompletion(s)) { // 这种强制转换从技术上讲是不正确的,但它是一个标准的MFC诀窍. CEdit* pEdit = (CEdit*)pWnd; pEdit->SetWindowText(arMatches[0]); pEdit->SetSel(s.GetLength(),-1); } } //////////////////////////////////////////////////////////////////////// // 当用户按下Backspace键删除敲入的字符时,这个函数用于关闭输入完成特性。 // 这时,当前的串将匹配最后输入的(前一个)串。在这种情况下,输入完成特性 // 是被屏蔽掉的。例如,如果用户敲如‘foo’,程序将自动完成‘foobar’,字符 // ‘bar’是高亮,如果用户按下Backspace键或者Delete键删除‘bar’时,程序 // 不会再次完成foobar,而是保留‘foo’,即便是用户继续按Backspace键也一样。 // 这个函数是我写的唯一一个说明比代码本身还长函数:)! // // BOOL CAutoComplete::IgnoreCompletion(CString s) { return s==m_sPrevious.Left(s.GetLength()); }
本文的例子程序 ACTest 使用了CAutoComplete,如图二所示,ACTest是一个基于对话框的迷你程序,实现细节请参考源代码。对话框中包含有一个编辑框和一个组合框,既两个CAutoComplete实例。
(图二)
class CMyDialog : public CDialog { protected: CAutoComplete m_acEdit; // 编辑框实例 CAutoComplete m_acCombo; // 组合框实例 ...... };为了使用CAutoComplete,你必须用窗口(编辑框和组合框)指针初始化每一个实例,然后再添加串。例子中这些都是在 CMyDialog 的OnInitDialog中完成。
// in CMyDialog::OnInitDialog m_acCombo.Init(GetDlgItem(IDC_COMBO1)); m_acEdit.Init(GetDlgItem(IDC_EDIT1)); static LPCTSTR STRINGS[] = { "alpha", "alphabet", ...... NULL }; for (int i=0; STRINGS[i]; i++) { m_acCombo.GetStringList().Add(STRINGS[i]); m_acEdit.GetStringList().Add(STRINGS[i]); }因为ACTest只是个简单的例子,编辑框和组合框在其中不做任何事情,调用CWnd::GetDlgItem 获得对话框控制。在实际应用中,你很可能有对话框类的成员如:m_wndEdit,m_wndCombo,在用SubclassDlgItem子类化(subclassing )这些成员后传递它们的地址。
要做的就这些,不用IEnumString,也不用COM。仅仅做一下初始化和添加一些串。当用户敲入一个字符如“b”,如图二所示,CAutoComplete 会显示与“b”匹配的选择并显示完整的文本串“bata”。
实现 CAutoComplete 的基本思路是从CSubclassWnd类派生,CSubclassWnd是一个通用的子类窗口类,这个类在VC知识库中出现的频率很高。CSubclassWnd让任何对象截获消息发送到窗口。它通过加载一个窗口过程使用普通的窗口子集,这些好东西在MFC的编程中是没有的,因为MFC的设计不是用来实现这种功能的。// 重载 CSubclassWnd::WindowProc LRESULT CAutoComplete::WindowProc(...) { if (/* EN_CHANGE or CBN_EDITCHANGE */))) { // try to complete } return CSubclassWnd::WindowProc(...); }请注意CAutoComplete不是一个CWnd派生的对象。它是从CSubclassWnd派生的,而CSubclassWnd又派生于CObject。在处理完消息之后,CAutoComplete调用
最后一个诀窍是其它的虚函数,它们将OnComplete分割成更小的操作,使你比较容易改变基本的行为。例如,OnComplete 做的第一件事情是调用一个虚函数 GetMatches 来获取与用户输入匹配的列表(CStringArray)。
void CAutoComplete::OnComplete(CWnd* pWnd, CString s) { CStringArray arMatches; // 匹配串 if (s.GetLength()>0 && GetMatches(s, arMatches, m_iType==Edit)>0) { DoCompletion(...); } m_sPrevious=s; // 记住当前串 }GetMatches 调用多个虚函数轮流操作串表:OnFirstString,OnNextString和OnMatchString。缺省实现操作一个内部的CStringArray-与调用CAutoComplete::Add获得的填充数组相同。(记住CMyDialog::OnInitDialog 调用Add来提供串表),最后,你既可以调用Add添加串,也可以派生一个新类,重载OnFirstString和其它的复杂行为。例如,你不想在CAutoComplete的CStringArray中存储串,或者你可能想重载DoCompletion实现实际的匹配完成(设置窗口文本和下拉组合框)动作。你可以重载DoCompletion来支持一些其它的非编辑、非组合框控制。
CAutoCompleteWnd | IAutoComplete |
自己用 C++/MFC 实现,有充分的自主性。 | shell32.dll 中现成的 COM 对象, |
可用于所有 Win32 平台。 | 只有 Windows 2000 才具备 |
容易改写完成行为 | 无法改写特性行为 |
不用格式串 | Ctrl + Enter 格式串,如 www.%s.com |
不用注册表 | 需要默认的注册表格式串 |
不需要串枚举(IEnumString) | 必须实现 IEnumString |
可以进行完全控制 | 必须将就接口提供的功能 |
做这样的比较并不是说谁好谁坏,而是哪一个更适合你的需要。