Figure 2 Adding m_ofnEx
int CFileDialogEx::DoModal()
{
......
    // 前面是 MFC 代码
    
    // 拷贝 m_ofn ==> m_ofnEx 并设置
    memset(&m_ofnEx, 0, sizeof(m_ofnEx));
    memcpy(&m_ofnEx, &m_ofn, sizeof(m_ofn));
    if (IsWin2000())
        m_ofnEx.lStructSize = sizeof(m_ofnEx);

    // 使用 m_ofnEx 运行 "打开"或"保存"对话框
    int nResult;
    if (m_bOpenFileDialog)
        nResult = ::GetOpenFileName((OPENFILENAME*)&m_ofnEx);
    else
        nResult = ::GetSaveFileName((OPENFILENAME*)&m_ofnEx);

    // 回拷 m_ofnEx ==> m_ofn
    memcpy(&m_ofn, &m_ofnEx, sizeof(m_ofn));
   m_ofn.lStructSize = sizeof(m_ofn);
......
}

Figure 3 IsWin2000.cpp
////////////////////////////////////////////////////////////////
// 检测操作系统版本(Windows 2000 或以后)的函数
//

BOOL IsWin2000 () 
{
   OSVERSIONINFOEX osvi;
   BOOL bOsVersionInfoEx;

   // 尝试调用 GetVersionEx 函数,使用 OSVERSIONINFOEX 结构,
   // 它被Windows 2000支持.
   //
   // 如果调用失败, 尝试使用 OSVERSIONINFO 结构.

   ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
   osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);

   if( !(bOsVersionInfoEx = GetVersionEx ((OSVERSIONINFO *) &osvi)) )
   {
      // 如果 OSVERSIONINFOEX 不行, 就用 OSVERSIONINFO.

      osvi.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
      if (! GetVersionEx ( (OSVERSIONINFO *) &osvi) ) 
         return FALSE;
   }

   switch (osvi.dwPlatformId)
   {
      case VER_PLATFORM_WIN32_NT:

         if ( osvi.dwMajorVersion >= 5 )
            return TRUE;

         break;
   }
   return FALSE; 
}

Figure 4 FileDialogEx

FileDialogEx.h

// VCKBASE August 2000
// Visual C++ 6.0环境编译,在 Windows 98 或 Windows NT 中运行.
// 
#pragma once

// OPENFILENAME的 Windows 2000 版本.
// 新版本多处三个成员.
// 从commdlg.h文件中拷贝.
struct OPENFILENAMEEX : public OPENFILENAME { 
  void *        pvReserved;
  DWORD         dwReserved;
  DWORD         FlagsEx;
};

///////////////////////////////////////////////////////////////////////////
// CFileDialogEx: 封装 Windows-2000 风格的"打开"对话框.
// 
class CFileDialogEx : public CFileDialog {
      DECLARE_DYNAMIC(CFileDialogEx)
public: 
      CFileDialogEx(BOOL bOpenFileDialog, // TRUE 为"打开", 
                                          // FALSE 为 "另存为"
      LPCTSTR lpszDefExt = NULL,
      LPCTSTR lpszFileName = NULL,
      DWORD dwFlags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
      LPCTSTR lpszFilter = NULL,
      CWnd* pParentWnd = NULL);

   // 重载
   virtual int DoModal();

protected:
   OPENFILENAMEEX m_ofnEx; // 新的 OPENFILENAME Windows 2000 版本

   virtual BOOL OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult);

   // 处理不同通知消息的虚拟函数
   virtual BOOL OnFileNameOK();
   virtual void OnInitDone();
   virtual void OnFileNameChange();
   virtual void OnFolderChange();
   virtual void OnTypeChange();

   DECLARE_MESSAGE_MAP()
   //{{AFX_MSG(CFileDialogEx)
   //}}AFX_MSG
};
FileDialogEx.cpp
#include "stdafx.h"
#include <afxpriv.h>
#include "FileDialogEx.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

static BOOL IsWin2000();

///////////////////////////////////////////////////////////////////////////
// CFileDialogEx

IMPLEMENT_DYNAMIC(CFileDialogEx, CFileDialog)

CFileDialogEx::CFileDialogEx(BOOL bOpenFileDialog,
   LPCTSTR lpszDefExt,
   LPCTSTR lpszFileName,
   DWORD dwFlags, LPCTSTR lpszFilter, CWnd* pParentWnd) :
   CFileDialog(bOpenFileDialog, lpszDefExt, lpszFileName,
      dwFlags, lpszFilter, pParentWnd)
{
}

BEGIN_MESSAGE_MAP(CFileDialogEx, CFileDialog)
   //{{AFX_MSG_MAP(CFileDialogEx)
   //}}AFX_MSG_MAP
END_MESSAGE_MAP()

BOOL IsWin2000() 
{
   OSVERSIONINFOEX osvi;
   BOOL bOsVersionInfoEx;

   // 尝试调用 GetVersionEx 函数,使用 OSVERSIONINFOEX 结构,
   // 它被Windows 2000支持.
   //
   // 如果调用失败, 尝试使用 OSVERSIONINFO 结构.

   ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
   osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);

   if( !(bOsVersionInfoEx = GetVersionEx ((OSVERSIONINFO *) &osvi)) )
   {
      // 如果 OSVERSIONINFOEX 不行, 就用 OSVERSIONINFO.

      osvi.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
      if (! GetVersionEx ( (OSVERSIONINFO *) &osvi) ) 
         return FALSE;
   }

   switch (osvi.dwPlatformId)
   {
      case VER_PLATFORM_WIN32_NT:

         if ( osvi.dwMajorVersion >= 5 )
            return TRUE;

         break;
   }
   return FALSE; 
}

int CFileDialogEx::DoModal()
{
   ASSERT_VALID(this);
   ASSERT(m_ofn.Flags & OFN_ENABLEHOOK);
   ASSERT(m_ofn.lpfnHook != NULL); // 仍然是个用户钩

   // 文件缓冲初始化
   ASSERT(AfxIsValidAddress(m_ofn.lpstrFile, m_ofn.nMaxFile));
   DWORD nOffset = lstrlen(m_ofn.lpstrFile)+1;
   ASSERT(nOffset <= m_ofn.nMaxFile);
   memset(m_ofn.lpstrFile+nOffset, 0, 
         (m_ofn.nMaxFile-nOffset)*sizeof(TCHAR));

   // WINBUG: 这是一种"打开/保存"对话框的特殊情况,
   //  在它disable主窗口之前是需要处理的.
   //  
   HWND hWndFocus = ::GetFocus();
   BOOL bEnableParent = FALSE;
   m_ofn.hwndOwner = PreModal();
   AfxUnhookWindowCreate();
   if (m_ofn.hwndOwner != NULL && ::IsWindowEnabled(m_ofn.hwndOwner))
   {
      bEnableParent = TRUE;
      ::EnableWindow(m_ofn.hwndOwner, FALSE);
   }

   _AFX_THREAD_STATE* pThreadState = AfxGetThreadState();
   ASSERT(pThreadState->m_pAlternateWndInit == NULL);

   if (m_ofn.Flags & OFN_EXPLORER)
      pThreadState->m_pAlternateWndInit = this;
   else
      AfxHookWindowCreate(this);

   memset(&m_ofnEx, 0, sizeof(m_ofnEx));
   memcpy(&m_ofnEx, &m_ofn, sizeof(m_ofn));
   if (IsWin2000())
      m_ofnEx.lStructSize = sizeof(m_ofnEx);

   int nResult;
   if (m_bOpenFileDialog)
      nResult = ::GetOpenFileName((OPENFILENAME*)&m_ofnEx);
   else
      nResult = ::GetSaveFileName((OPENFILENAME*)&m_ofnEx);

   memcpy(&m_ofn, &m_ofnEx, sizeof(m_ofn));
   m_ofn.lStructSize = sizeof(m_ofn);

   if (nResult)
      ASSERT(pThreadState->m_pAlternateWndInit == NULL);
   pThreadState->m_pAlternateWndInit = NULL;

   // WINBUG: "打开/保存"对话框的特殊情况的第二部分.
   if (bEnableParent)
      ::EnableWindow(m_ofnEx.hwndOwner, TRUE);
   if (::IsWindow(hWndFocus))
      ::SetFocus(hWndFocus);

   PostModal();
   return nResult ? nResult : IDCANCEL;
}

BOOL CFileDialogEx::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
   memcpy(&m_ofn, &m_ofnEx, sizeof(m_ofn));
   m_ofn.lStructSize = sizeof(m_ofn);

   return CFileDialog::OnNotify( wParam, lParam, pResult);
}

////////////////////////////////////////////////////////////////////
// 下列函数只是用来说明他们获得了调用,实际上,类库(MFC)内部的对话框
// 过程是被挂钩的;如果你愿意的话,可以删除他们.
//
BOOL CFileDialogEx::OnFileNameOK()
{
   TRACE(_T("CFileDialogEx::OnFileNameOK\n"));
   return CFileDialog::OnFileNameOK();
}

void CFileDialogEx::OnInitDone()
{
   TRACE(_T("CFileDialogEx::OnInitDone\n"));
   CFileDialog::OnInitDone();
}

void CFileDialogEx::OnFileNameChange()
{
   TRACE(_T("CFileDialogEx::OnFileNameChange\n"));
   CFileDialog::OnFileNameChange();
}

void CFileDialogEx::OnFolderChange()
{
   TRACE(_T("CFileDialogEx::OnFolderChange\n"));
   CFileDialog::OnFolderChange();
}

void CFileDialogEx::OnTypeChange()
{
   TRACE(_T("OnTypeChange(), index = %d\n"), m_ofn.nFilterIndex);
   CFileDialog::OnTypeChange();
}

Figure 5 DocMgrEx

DocMgrEx.h

////////////////////////////////////////////////////////////////
// VCKBASE August 2000
// Visual C++ 6.0环境编译, 在 Windows 98 或 Windows NT 中运行.
// 
#pragma once

//////////////////
// CDocManagerEx: 扩展 CDocManager 类使用 CFileOpenEx.
class CDocManagerEx : public CDocManager {
public:
   CDocManagerEx();
   ~CDocManagerEx();

   // 为"打开/保存"对话框重载
   virtual BOOL DoPromptFileName(CString& fileName, UINT nIDSTitle,
       DWORD lFlags, BOOL bOpenFileDialog, CDocTemplate* pTemplate);

protected:
   // 创建"打开"文件对话框的新函数
   virtual CFileDialog* OnCreateFileDialog(BOOL bOpenFileDialog);

   DECLARE_DYNAMIC(CDocManagerEx)
};
DocMgrEx.cpp
////////////////////////////////////////////////////////////////
// VCKBASE August 2000
// Visual C++ 6.0环境编译, 在 Windows 98 或 Windows NT 中运行.
// 
// CDocManagerEx 为了Windows 2000中使用"打开"文件对话框,
// 重载 CDocManager 中的一个函数 DoPromptFileName.
// 使用这个类时, 在你添加任何文档模版之前,添加下列一行代码到应用
// 程序的实例化函数:InitInstance.
//    m_pDocManager = new CDocManagerEx;
// 
#include "stdafx.h"
#include <afxpriv.h>
#include "DocMgrEx.h"
#include "FileDialogEx.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

AFX_STATIC void AFXAPI _AfxAppendFilterSuffix(CString& filter,
   OPENFILENAME& ofn, CDocTemplate* pTemplate, CString* pstrDefaultExt);

IMPLEMENT_DYNAMIC(CDocManagerEx, CDocManagerEx)

CDocManagerEx::CDocManagerEx()
{
}

CDocManagerEx::~CDocManagerEx()
{
}

////////////////////////////////////////////////////////////////////////
// 创建文件对话框的新函数,你可以重载它以创建一些不同类型的CFileDialog,
// 缺省情况下创建 CFileDialogEx.
//
CFileDialog* CDocManagerEx::OnCreateFileDialog(BOOL bOpenFileDialog)
{
   TRACE(_T("CDocManagerEx::OnCreateFileDialog\n"));
   return new CFileDialogEx(bOpenFileDialog);
}

/////////////////////////////////////////////////////////////////////////
// 这个函数是从MFC的 docmgr.cpp文件中考过来的,只对它做了稍微的修改以便
// 使用 CFileDialogEx. 
//
BOOL CDocManagerEx::DoPromptFileName(CString& fileName, UINT nIDSTitle,
   DWORD lFlags, BOOL bOpenFileDialog, CDocTemplate* pTemplate)
{
   // 修改日期 5-15-99 : 调用虚拟函数来创建对话框
   CFileDialog* pDlg = OnCreateFileDialog(bOpenFileDialog);
   ASSERT(pDlg);
   CFileDialog& dlgFile = *pDlg;
   
   CString title;
   VERIFY(title.LoadString(nIDSTitle));

   dlgFile.m_ofn.Flags |= lFlags;

   CString strFilter;
   CString strDefault;
   if (pTemplate != NULL)
   {
      ASSERT_VALID(pTemplate);
      _AfxAppendFilterSuffix(strFilter, dlgFile.m_ofn, pTemplate, &strDefault);
   }
   else
   {
      // 用于所有的文档模板
      POSITION pos = m_templateList.GetHeadPosition();
      BOOL bFirst = TRUE;
      while (pos != NULL)
      {
         CDocTemplate* pTemplate = (CDocTemplate*)m_templateList.GetNext(pos);
         _AfxAppendFilterSuffix(strFilter, dlgFile.m_ofn, pTemplate,
            bFirst ? &strDefault : NULL);
         bFirst = FALSE;
      }
   }

   // 添加 "*.*" 对全部文件过滤
   CString allFilter;
   VERIFY(allFilter.LoadString(AFX_IDS_ALLFILTER));
   strFilter += allFilter;
   strFilter += (TCHAR)'\0';   // 下一个串
   strFilter += _T("*.*");
   strFilter += (TCHAR)'\0';   // 最后一个串
   dlgFile.m_ofn.nMaxCustFilter++;

   dlgFile.m_ofn.lpstrFilter = strFilter;
   dlgFile.m_ofn.lpstrTitle = title;
   dlgFile.m_ofn.lpstrFile = fileName.GetBuffer(_MAX_PATH);

   int nResult = dlgFile.DoModal();
   fileName.ReleaseBuffer();

   // 修改日期 5-15-99 : 删除对话框
   delete pDlg;

   return nResult == IDOK;
}

////////////////////////////////////////////////////////////////////////
// 这个函数是原封不动地从MFC的 docmgr.cpp文件中考过来的,因为它是静态函数,
// 所以在这里是必须的.
//
AFX_STATIC void AFXAPI _AfxAppendFilterSuffix(CString& filter, OPENFILENAME& ofn,
   CDocTemplate* pTemplate, CString* pstrDefaultExt)
{
   ASSERT_VALID(pTemplate);
   ASSERT_KINDOF(CDocTemplate, pTemplate);

   CString strFilterExt, strFilterName;
   if (pTemplate->GetDocString(strFilterExt, CDocTemplate::filterExt) &&
    !strFilterExt.IsEmpty() &&
    pTemplate->GetDocString(strFilterName, CDocTemplate::filterName) &&
    !strFilterName.IsEmpty())
   {
      // 添加一个基于文档模板的文件到过滤器
      ASSERT(strFilterExt[0] == '.');
      if (pstrDefaultExt != NULL)
      {
         // 设置缺省扩展名
         *pstrDefaultExt = ((LPCTSTR)strFilterExt) + 1;  // skip the '.'
         ofn.lpstrDefExt = (LPTSTR)(LPCTSTR)(*pstrDefaultExt);
         ofn.nFilterIndex = ofn.nMaxCustFilter + 1;  // 1 based number
      }

      // 添加过滤器
      filter += strFilterName;
      ASSERT(!filter.IsEmpty());  // 必须有一个文件类型名
      filter += (TCHAR)'\0';  // 下一个串
      filter += (TCHAR)'*';
      filter += strFilterExt;
      filter += (TCHAR)'\0';  // 下一个串
      ofn.nMaxCustFilter++;
   }
}

Figure 7 IAutocomplete and 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);

Figure 8 IAutoComplete

Option Flags
Description
ACO_NONE
No autocomplete.
ACO_AUTOSUGGEST
Enable the autosuggest drop-down list.
ACO_AUTOAPPEND
Enable autoappend.
ACO_SEARCH
Add a search item to the list of completed strings. Selecting this item launches a search engine.
ACO_FILTERPREFIXES
Don't match common prefixes, such as "www.", "http://", and so on.
ACO_USETAB
Use the TAB key to select an item from the dropdown list.
ACF_UPDOWNKEYDROPSLIST
Use the UP ARROW and DOWN ARROW keys to display the autosuggest dropdown list.
ACO_RTLREADING
Normal windows display text left to right (LTR). Windows can be mirrored to display languages such as Hebrew or Arabic that read right to left (RTL). Normally, a control's text is displayed in the same direction as the text in its parent window. If ACO_RTLREADING is set, the text reads in the opposite direction from the text in the parent window.


Figure 9 AutoCompl

AutoCompl.h

////////////////////////////////////////////////////////////////
// VCKBASE August 2000
// Visual C++ 6.0环境编译, 在 Windows 98 或 Windows NT 中运行.
// 
// 
#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<arMatches.GetSize(); i++) {
         pComboBox->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());
}

Figure 10 ACTest.cpp
////////////////////////////////////////////////////////////////
// VCKBASE August 2000
// Visual C++ 6.0环境编译, 在 Windows 98 或 Windows NT 中运行.
//
#include "stdafx.h"
#include "resource.h"
#include "TraceWin.h"
#include "StatLink.h"
#include "AutoCompl.h"
 
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

//////////////////////////////////////////////////////////////
// 测试自动完成输入的 Test 对话框
//
class CMyDialog : public CDialog {
public:
    CMyDialog(CWnd* pParent = NULL);    // 标准构造函数
protected:
    CAutoComplete m_acEdit;             // 编辑框 hook 
    CAutoComplete m_acCombo;            // 组合框 hook
    virtual BOOL OnInitDialog();        // MFC 重载

    // 与本文无关的内容:
    HICON m_hIcon;
    CStaticLink    m_wndLink1;
    CStaticLink    m_wndLink2;
    DECLARE_MESSAGE_MAP()
};

//////////////////////////////////////////////////////
// 典型的 app 
class CMyApp : public CWinApp {
public:
    virtual BOOL InitInstance();
} theApp;

//////////////////////////////////////////////////////
// 初始化应用实例: 运行对话框并退出
BOOL CMyApp::InitInstance()
{
    CMyDialog dlg;
    m_pMainWnd = &dlg;
    dlg.DoModal();
    return FALSE;
}

//////////////////////////////////////////////////////
// 对话框构造函数: 设置 icon
CMyDialog::CMyDialog(CWnd* pParent) : CDialog(IDD_MYDIALOG, pParent)
{
    m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

BEGIN_MESSAGE_MAP(CMyDialog, CDialog)
END_MESSAGE_MAP()

//////////////////////////////////////////////////////
// 初始化对话框: 添加一些字符串到自动完成的 hooker
BOOL CMyDialog::OnInitDialog()
{
    CDialog::OnInitDialog();

    // 初始化自动完成控制
    m_acCombo.Init(GetDlgItem(IDC_COMBO1));
    m_acEdit.Init(GetDlgItem(IDC_EDIT1));
    static LPCTSTR STRINGS[] = {
        "你好!",
        "你好!",
        "你好!",
        "我好!",
        "我好!",
        "我好!",
        "大家好!",
        "大家好!",
        "大家好!",
        "alpha",
        "alphabet",
        "alphabet soup",
        "beta",
        "beta blocker",
        "beta carotine",
        "beta test",
        "one",
        "one of six",
        "one two",
        NULL
    };
    for (int i=0; STRINGS[i]; i++) {
        m_acCombo.GetStringList().Add(STRINGS[i]);
        m_acEdit.GetStringList().Add(STRINGS[i]);
    }

    // 设置输入焦点到编辑框
    GetDlgItem(IDC_EDIT1)->SetFocus();

    // 无关的内容:
    m_wndLink1.m_link = _T("http://www.vckbase.com");
    m_wndLink2.m_link = _T("mailto:vckbase@public.hk.hi.cn");
    m_wndLink1.SubclassDlgItem(IDC_VCKBASE, this);
    m_wndLink2.SubclassDlgItem(IDC_MAIL, this);

    // 设置对话框的 icon .  如果应用程序的主窗口不是对话框,则框架自动设置icon
    // 
    SetIcon(m_hIcon, TRUE);         // 设置大图标
    SetIcon(m_hIcon, FALSE);        // 设置小图标
    
    return FALSE;  // 因为设置了输入焦点,所以要返回 FALSE
}

Figure 12 Comparing AutoComplete

CAutoCompleteWnd
IAutoComplete
Roll-your-own implementation using C++/MFC
COM object in shell32.dll
Works in all Win32 platforms
Windows 2000 only
Easy to override completion behavior
Can't override specific behaviors
No format string
Ctrl + Enter format string like www.%s.com
No registry support
Default registry format string
Doesn't need string enumerator (IEnumString)
You must implement IEnumString
You have total control
You're stuck with what's provided