分类: 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; iAddString(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());
} 这个类大体上实现了 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化,似乎不属于通用的本文的例子程序 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 |
| 可以进行完全控制 | 必须将就接口提供的功能 |
做这样的比较并不是说谁好谁坏,而是哪一个更适合你的需要。