程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> 關於VC++ >> 如何在編輯框中使用IAutoComplete接口

如何在編輯框中使用IAutoComplete接口

編輯:關於VC++

唉!,就叫我封裝先生吧。

你算是找對地方了。但是我要先聲明我的解決辦法不是你所希望的-甚至也不是我自己所希望的!

什麼是autocomplete呢?你也許已經注意到當你在IE的地址編輯框中敲入什麼東西的時候,就會出現一個下拉組合框顯示所有匹配敲入字符的URLs,亮條落在第一個匹配項上,你只要按下回車鍵就可以了(圖一)。在“文件/打開”對話框及Windows其它地方也有相同的效果。

Autocompletion真是一個偉大的發明。

(圖一)

我第一次看到你的問題時,說句實話,我還從來沒有聽說過IAutoComplete-你是不是覺得我應該熟悉微軟發布的每一個新的COM接口?-對我來說這似乎是個好主意。代碼一中列出了IAutoComplete的一些可選項。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是我寫的一個類,這個類大體上實現了autocompletion,不用COM,也不用shell32.dll,它只是一個簡單的類而已,你可以將它的cpp文件添加到你的應用,DLL或者擴展庫中。它可以工作於任何的Windows版本,甚至是Windows 3.1。

CAutoComplete沒有實現IAutoComplete中的所有的特性。例如,IAutoComplete有一個特性是當用戶按下 Ctrl+Enter 時的快速完成格式串。這個格式串是一個Windows用來轉換用戶輸入的 sprintf 串。如果這個格式串是“http://www.%s.com” 並且用戶敲入“woowoo”,IAutoComplete 將完成整個內容http://www.woowoo.com。另外一個IAutoComplete特性是讓你指定一個串作為注冊鍵來存儲格式串。這些特性都很好,但他們太IE化,似乎不屬於通用的

autocompletion接口,所以我沒有將它們放進CAutoComplete,而是讓CAutoComplete提供更通用的方式來改變它實現autocompletion的方法。

本文的例子程序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的設計不是用來實現這種功能的。

當應用調用CAutoComplete::Init時,CAutoComplete會調用CSubclassWnd::HookWindow函數,它子類化窗口。CSubclassWnd::HookWindow將CSubclassWnd對象作為附件交給窗口(使用類似MFC的機制)以便任何發送到窗口的消息首先被虛函數CSubclassWnd::WindowProc指定路由。CAutoComplete重載這個函數來處理感興趣的消息。

// 重載 CSubclassWnd::WindowProc
LRESULT CAutoComplete::WindowProc(...)
{
  if (/* EN_CHANGE or CBN_EDITCHANGE */))) {
    // try to complete
  }
  return CSubclassWnd::WindowProc(...);
}

請注意CAutoComplete不是一個CWnd派生的對象。它是從CSubclassWnd派生的,而CSubclassWnd又派生於CObject。在處理完消息之後,CAutoComplete調用

CSubclassWnd::WindowProc,它以自己的方式經過原來的窗口過程傳遞消息到任何消息映射中的消息處理器。

一旦你明白了CSubclassWnd截獲消息的方式,剩下的事情就是要完成實際的autocompletion功能了-也就是比較串列表和用戶輸入,CAutoComplete在虛函數OnComplete中完成這個工作。缺省實現(見AutoCompl.cpp代碼)比較用戶輸入與內部串表,如果匹配的話便把匹配項顯示在控制(編輯框)中,如果是組合框,CAutoComplete將所有匹配選擇項顯示在下拉框中。

不管怎麼說,CAutoComplete的實現還是需要一些技巧的,當CAutoComplete截獲消息EN_CHANGE (改變編輯框輸入)或CBN_ EDITCHANGE時,它必須在調用SetWindowText設置新的

內容之前將自己關閉起來。否則,SetWindowText 將觸發另一個CHANGE通知並且控制將離開你而去......嘿、嘿,試一試就知道了。

第二個訣竅是個小聰明。假設用戶敲入“al”,CAutoComplete通過高亮“pha”三個字符來完成整個串“alpha”。用戶現在想按Backspace刪除掉“pha”,可憐的用戶會發現Backspace不起作用。解決辦法是當用戶縮短(刪除)輸入的字符時就忽略掉文本完成動作。也就是說如果輸入的內容匹配以前的輸入,就忽略完成動作。如果我沒有解釋清楚,實在是對不起,你到程序代碼中去找感覺吧。我添加了一個虛函數IgnoreCompletion來測試這種情況;如果這個函數返回FALSE,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來支持一些其它的非編輯、非組合框控制。

羅羅嗦嗦了這麼多,其實關鍵的地方無非就是設計一個API,這個API使你能重載函數來改變特定的行為,這是所有好程序的奧妙所在。你是否在C,C++或COM中寫過類(在C中當然不叫類)?你仍然要設計API,那時最精彩的部分。別看Windows中有那麼多的接口,但正真編程起來好像總覺得API不夠用,一個原因就是這些API被設計用來只滿足Windows或IE本身的需要,或微軟產品偶爾使用到它們,除此之外沒有別的。不管怎麼說,自己寫代碼常常較容易獲得你實際想要的結果。

本文最後對CAutoComplete和微軟在shell32.dll中實現的IAutoComplete做了一個比較,並不是說誰好誰壞,而是哪一個更適合你的需要。

本文配套源碼

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved