天天看點

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

如果可能我想用打包類來實作。唉!,就叫我封裝先生吧。

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

什麼是autocomplete呢?你也許已經注意到當你在IE的位址編輯框中敲入什麼東西的時候,就會出現一個下拉組合框顯示所有比對敲入字元的URLs,亮條落在第一個比對項上,你隻要按下Enter鍵就可以了(圖一)。在“檔案/打開”對話框及Windows其它地方也有相同的效果。Autocompletion真是個了不起的東西。可惜 到了Windows 2000才有,來得太遲了。

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

(圖一)

我第一次看到你的問題時,說句實話,我還從來沒有聽說過 IAutoComplete:

<a href="http://www.vckbase.com/index.php/wv/27.html#viewSource">view source</a>

<code>01.</code><code>IAutocomplete 和 IAutoComplete2 </code>

<code>02.</code><code> </code> 

<code>03.</code><code>IAutoComplete </code>

<code>04.</code><code>HRESULT</code> <code>Init(</code>

<code>05.</code><code>   </code><code>HWND</code> <code>hwndEdit,               </code><code>// 編輯控制或組合框</code>

<code>06.</code><code>   </code><code>IUnknown *punkACL,           </code><code>// 實作 IEnumString 的對象指針</code>

<code>07.</code><code>   </code><code>LPCOLESTR pwszRegKeyPath,    </code><code>// 存儲格式串的注冊庫路徑</code>

<code>08.</code><code>   </code><code>LPCOLESTR pwszQuickComplete);</code><code>// CTRL+Enter的格式化串</code>

<code>09.</code><code>         </code> 

<code>10.</code><code>   </code><code>// Enable 或 Disable 自動完成功能</code>

<code>11.</code><code>HRESULT</code> <code>Enable(</code><code>BOOL</code> <code>fEnable);</code>

<code>12.</code><code> </code> 

<code>13.</code><code>enum</code> <code>{</code>

<code>14.</code><code>   </code><code>ACO_NONE = 0,</code>

<code>15.</code><code>   </code><code>ACO_AUTOSUGGEST   = 0x1,</code>

<code>16.</code><code>   </code><code>ACO_AUTOAPPEND    = 0x2,</code>

<code>17.</code><code>   </code><code>ACO_SEARCH        = 0x4,</code>

<code>18.</code><code>   </code><code>ACO_FILTERPREFIXES= 0x8,</code>

<code>19.</code><code>   </code><code>ACO_USETAB        = 0x10,</code>

<code>20.</code><code>   </code><code>ACO_UPDOWNKEYDROPSLIST= 0x20,</code>

<code>21.</code><code>   </code><code>ACO_RTLREADING    = 0x40</code>

<code>22.</code><code>}  AUTOCOMPLETEOPTIONS;</code>

<code>23.</code><code> </code> 

<code>24.</code><code>IAutoComplete2 </code>

<code>25.</code><code>HRESULT</code> <code>SetOptions(</code><code>DWORD</code> <code>dwFlag);</code>

<code>26.</code><code>HRESULT</code> <code>GetOptions(</code><code>DWORD</code> <code>*pdwFlag);</code>

你是不是覺得我應該熟悉微軟釋出的每一個新的COM接口? 對我來說這似乎是個好主意。

下面是 IAutoComplete 接口的一些選項:  

選項标志

描述

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:

<code>001.</code><code>////////////////////////////////////////////////////////////////</code>

<code>002.</code><code>// AutoCompl.h </code>

<code>003.</code><code>// </code>

<code>004.</code><code>#pragma once</code>

<code>005.</code><code> </code> 

<code>006.</code><code>#include "subclass.h"</code>

<code>007.</code><code> </code> 

<code>008.</code><code>///////////////////////////////////////////////////////////////////</code>

<code>009.</code><code>// 隻是個通用可重用類,你可以将它用于編輯框群組合框的自動完成輸入應用.</code>

<code>010.</code><code>// </code>

<code>011.</code><code>// 使用方法:</code>

<code>012.</code><code>// - 執行個體化你想要 hook 的編輯/組合控制</code>

<code>013.</code><code>// - 添加一些串到串數組中</code>

<code>014.</code><code>// - 調用 Init</code>

<code>015.</code><code>class</code> <code>CAutoCompleteWnd :</code><code>public</code> <code>CSubclassWnd {</code>

<code>016.</code><code>protected</code><code>:</code>

<code>017.</code><code>   </code><code>CStringArray m_arStrings;  </code><code>// 串清單(數組)</code>

<code>018.</code><code>   </code><code>CString      m_sPrevious;  </code><code>// 前一個内容</code>

<code>019.</code><code>   </code><code>int</code>   <code>m_bIgnoreChangeMsg;  </code><code>// 忽略 EN_CHANGE 消息</code>

<code>020.</code><code>   </code><code>UINT</code>  <code>m_idMyControl;       </code><code>// 子類化的控制 ID </code>

<code>021.</code><code>   </code><code>int</code>   <code>m_iType;             </code><code>// 控制類型(編輯/組合)</code>

<code>022.</code><code>   </code><code>int</code>   <code>m_iCurString;        </code><code>// 目前串的索引</code>

<code>023.</code><code>   </code><code>enum</code> <code>{ Edit=1,ComboBox };</code>

<code>024.</code><code> </code> 

<code>025.</code><code>   </code><code>// hook 函數</code>

<code>026.</code><code>   </code><code>virtual</code> <code>LRESULT</code> <code>WindowProc(</code><code>UINT</code> <code>msg,</code><code>WPARAM</code> <code>wp,</code><code>LPARAM</code> <code>lp);</code>

<code>027.</code><code> </code> 

<code>028.</code><code>   </code><code>// 可以重載的輔助函數</code>

<code>029.</code><code>   </code><code>virtual</code> <code>UINT</code> <code>GetMatches(</code><code>LPCTSTR</code> <code>pszText, CStringArray&amp; arMatches,</code>

<code>030.</code><code>      </code><code>BOOL</code> <code>bFirstOnly=FALSE);</code>

<code>031.</code><code>   </code><code>virtual</code> <code>void</code> <code>OnFirstString();</code>

<code>032.</code><code>   </code><code>virtual</code> <code>BOOL</code> <code>OnNextString(CString&amp; sNext);</code>

<code>033.</code><code>   </code><code>virtual</code> <code>BOOL</code> <code>OnMatchTest(</code><code>const</code> <code>CString&amp; s,</code><code>const</code> <code>CString&amp; sMatch);</code>

<code>034.</code><code>   </code><code>virtual</code> <code>BOOL</code> <code>IgnoreCompletion(CString s);</code>

<code>035.</code><code>   </code><code>virtual</code> <code>void</code> <code>OnComplete(CWnd* pWnd, CString s);</code>

<code>036.</code><code> </code> 

<code>037.</code><code>public</code><code>:</code>

<code>038.</code><code>   </code><code>CAutoCompleteWnd();</code>

<code>039.</code><code>   </code><code>~CAutoCompleteWnd();</code>

<code>040.</code><code>   </code><code>void</code> <code>Init(CWnd* pWnd);</code>

<code>041.</code><code>   </code><code>CStringArray&amp; GetStringList() {</code><code>return</code> <code>m_arStrings; }</code>

<code>042.</code><code>};</code>

<code>043.</code><code> </code> 

<code>044.</code><code>AutoCompl.cpp </code>

<code>045.</code><code>////////////////////////////////////////////////////////////////</code>

<code>046.</code><code>// VCKBASE August 2000</code>

<code>047.</code><code>// Visual C++ 6.0環境編譯, 在 Windows 98 或 Windows NT 中運作.</code>

<code>048.</code><code>// </code>

<code>049.</code><code>#include "stdafx.h"</code>

<code>050.</code><code>#include "autocompl.h"</code>

<code>051.</code><code> </code> 

<code>052.</code><code>//////////////////</code>

<code>053.</code><code>// 構造函數: 用于初始化</code>

<code>054.</code><code>CAutoComplete::CAutoComplete()</code>

<code>055.</code><code>{</code>

<code>056.</code><code>   </code><code>m_bIgnoreChangeMsg=0;</code>

<code>057.</code><code>   </code><code>m_iType = 0;</code>

<code>058.</code><code>   </code><code>m_idMyControl = 0;</code>

<code>059.</code><code>   </code><code>m_iCurString = 0;</code>

<code>060.</code><code>}</code>

<code>061.</code><code> </code> 

<code>062.</code><code>CAutoComplete::~CAutoComplete()</code>

<code>063.</code><code>{</code>

<code>064.</code><code>}</code>

<code>065.</code><code> </code> 

<code>066.</code><code>///////////////////////////////////////////////////////////////</code>

<code>067.</code><code>// 安裝 hook. 初始化控制 ID 和基于類名的控制類型</code>

<code>068.</code><code>// </code>

<code>069.</code><code>void</code> <code>CAutoComplete::Init(CWnd* pWnd)</code>

<code>070.</code><code>{</code>

<code>071.</code><code>   </code><code>CSubclassWnd::HookWindow(pWnd-&gt;GetParent());</code>

<code>072.</code><code>   </code><code>CString sClassName;</code>

<code>073.</code><code>   </code><code>::GetClassName(pWnd-&gt;GetSafeHwnd(), sClassName.GetBuffer(32), 32);</code>

<code>074.</code><code>   </code><code>sClassName.ReleaseBuffer();</code>

<code>075.</code><code>   </code><code>if</code> <code>(sClassName==</code><code>"Edit"</code><code>) {</code>

<code>076.</code><code>      </code><code>m_iType = Edit;</code>

<code>077.</code><code>   </code><code>}</code><code>else</code> <code>if</code> <code>(sClassName==</code><code>"ComboBox"</code><code>) {</code>

<code>078.</code><code>      </code><code>m_iType = ComboBox;</code>

<code>079.</code><code>   </code><code>}</code>

<code>080.</code><code>   </code><code>m_idMyControl = pWnd-&gt;GetDlgCtrlID();</code>

<code>081.</code><code>}</code>

<code>082.</code><code> </code> 

<code>083.</code><code>/////////////////////////////////////////////////////////////////</code>

<code>084.</code><code>// 掃描串數組中比對的文本,并添加比對項到一個新數組,傳回比對項的數目。</code>

<code>085.</code><code>// 對于編輯控制而言, 隻需要找到第一個串,在這裡使用了一個BOOL參數。</code>

<code>086.</code><code>// </code>

<code>087.</code><code>// </code>

<code>088.</code><code>UINT</code> <code>CAutoComplete::GetMatches(</code><code>LPCTSTR</code> <code>pszText, CStringArray&amp; arMatches,</code>

<code>089.</code><code>   </code><code>BOOL</code> <code>bFirstOnly)</code>

<code>090.</code><code>{</code>

<code>091.</code><code>   </code><code>arMatches.RemoveAll();</code>

<code>092.</code><code>   </code><code>int</code> <code>nMatch = 0;</code>

<code>093.</code><code>   </code><code>CString s=pszText;</code>

<code>094.</code><code>   </code><code>if</code> <code>(s.GetLength()&gt;0) {</code>

<code>095.</code><code>      </code><code>OnFirstString();</code>

<code>096.</code><code>      </code><code>CString sMatch;</code>

<code>097.</code><code>      </code><code>while</code> <code>(OnNextString(sMatch)) {</code>

<code>098.</code><code>         </code><code>if</code> <code>(OnMatchString(s, sMatch)) {</code>

<code>099.</code><code>            </code><code>TRACE(</code><code>"Add %s\n"</code><code>,(</code><code>LPCTSTR</code><code>)sMatch);</code>

<code>100.</code><code>            </code><code>arMatches.Add(sMatch);</code>

<code>101.</code><code>            </code><code>nMatch++;</code>

<code>102.</code><code>            </code><code>if</code> <code>(bFirstOnly)</code>

<code>103.</code><code>               </code><code>break</code><code>;</code>

<code>104.</code><code>         </code><code>}</code>

<code>105.</code><code>      </code><code>}</code>

<code>106.</code><code>   </code><code>}</code>

<code>107.</code><code>   </code><code>return</code> <code>nMatch;</code>

<code>108.</code><code>}</code>

<code>109.</code><code> </code> 

<code>110.</code><code>//////////////////////////////////////////////////////////////////</code>

<code>111.</code><code>// 這個虛拟函數的參數是兩個字元串,如果有比對則傳回 TRUE. 預設情況下實作普通的</code>

<code>112.</code><code>// 字首比較-你可以重載它,例如,在忽略掉兩個串中的‘www’字元。</code>

<code>113.</code><code>// </code>

<code>114.</code><code>// </code>

<code>115.</code><code>BOOL</code> <code>CAutoComplete::OnMatchString(</code><code>const</code> <code>CString&amp; s,</code><code>const</code> <code>CString&amp; sMatch)</code>

<code>116.</code><code>{</code>

<code>117.</code><code>   </code><code>return</code> <code>s==sMatch.Left(s.GetLength());</code>

<code>118.</code><code>}</code>

<code>119.</code><code> </code> 

<code>120.</code><code>void</code> <code>CAutoComplete::OnFirstString()</code>

<code>121.</code><code>{</code>

<code>122.</code><code>   </code><code>m_iCurString=0;</code>

<code>123.</code><code>}</code>

<code>124.</code><code> </code> 

<code>125.</code><code>BOOL</code> <code>CAutoComplete::OnNextString(CString&amp; sNext)</code>

<code>126.</code><code>{</code>

<code>127.</code><code>   </code><code>if</code> <code>(m_iCurString &lt; m_arStrings.GetSize()) {</code>

<code>128.</code><code>      </code><code>sNext = m_arStrings[m_iCurString++];</code>

<code>129.</code><code>      </code><code>return</code> <code>TRUE;</code>

<code>130.</code><code>   </code><code>}</code>

<code>131.</code><code>   </code><code>sNext = (</code><code>LPCTSTR</code><code>)NULL;</code>

<code>132.</code><code>   </code><code>return</code> <code>FALSE;</code>

<code>133.</code><code>}</code>

<code>134.</code><code> </code> 

<code>135.</code><code>//////////////////////////////////////////////////////////////</code>

<code>136.</code><code>// "hook" 函數截取發送到編輯控制群組合框的消息.我隻對 EN_CHANGE 或 </code>

<code>137.</code><code>// CBN_EDITCHANGE感興趣: 編輯控制的内容已經改變.</code>

<code>138.</code><code>// </code>

<code>139.</code><code>LRESULT</code> <code>CAutoComplete::WindowProc(</code><code>UINT</code> <code>msg,</code><code>WPARAM</code> <code>wp,</code><code>LPARAM</code> <code>lp)</code>

<code>140.</code><code>{</code>

<code>141.</code><code>   </code><code>if</code> <code>((msg==WM_COMMAND &amp;&amp; LOWORD(wp)==m_idMyControl) &amp;&amp;</code>

<code>142.</code><code>       </code><code>((m_iType==Edit   &amp;&amp; HIWORD(wp)==EN_CHANGE) ||</code>

<code>143.</code><code>        </code><code>(m_iType==ComboBox &amp;&amp; HIWORD(wp)==CBN_EDITCHANGE))) {</code>

<code>144.</code><code> </code> 

<code>145.</code><code>      </code><code>// 因為我要改變控制的内容,它将觸發更多的EN_CHANGE消息,</code>

<code>146.</code><code>      </code><code>// 當我獲得控制時使用 m_bIgnoreChangeMsg 結束處理.</code>

<code>147.</code><code>      </code><code>if</code> <code>(!m_bIgnoreChangeMsg++) {</code>

<code>148.</code><code>         </code><code>CString s;</code>

<code>149.</code><code>         </code><code>CWnd* pWnd = CWnd::FromHandle((</code><code>HWND</code><code>)lp);</code>

<code>150.</code><code>         </code><code>pWnd-&gt;GetWindowText(s);</code>

<code>151.</code><code>         </code><code>OnComplete(pWnd, s);</code>

<code>152.</code><code>      </code><code>}</code>

<code>153.</code><code>      </code><code>m_bIgnoreChangeMsg--;</code>

<code>154.</code><code>   </code><code>}</code>

<code>155.</code><code>   </code><code>return</code> <code>CSubclassWnd::WindowProc(msg, wp, lp);</code>

<code>156.</code><code>}</code>

<code>157.</code><code> </code> 

<code>158.</code><code>//////////////////////////////////////////////////////////////</code>

<code>159.</code><code>// 這是實作輸入完成的主函數.</code>

<code>160.</code><code>//</code>

<code>161.</code><code>void</code> <code>CAutoComplete::OnComplete(CWnd* pWnd, CString s)</code>

<code>162.</code><code>{</code>

<code>163.</code><code>   </code><code>CStringArray arMatches;</code><code>// 比對串</code>

<code>164.</code><code>   </code><code>if</code> <code>(s.GetLength()&gt;0 &amp;&amp; GetMatches(s, arMatches, m_iType==Edit)&gt;0) {</code>

<code>165.</code><code>      </code><code>DoCompletion(pWnd, s, arMatches);</code>

<code>166.</code><code>   </code><code>}</code>

<code>167.</code><code>   </code><code>m_sPrevious=s;</code><code>// 記住目前串</code>

<code>168.</code><code>}</code>

<code>169.</code><code> </code> 

<code>170.</code><code>void</code> <code>CAutoComplete::DoCompletion(CWnd* pWnd, CString s,</code>

<code>171.</code><code>   </code><code>const</code> <code>CStringArray&amp; arMatches)</code>

<code>172.</code><code>{</code>

<code>173.</code><code>   </code><code>if</code> <code>(m_iType==ComboBox) {</code>

<code>174.</code><code>      </code><code>// 這種強制轉換從技術上講是不正确的,但它是一個标準的MFC訣竅.</code>

<code>175.</code><code>      </code><code>CComboBox* pComboBox = (CComboBox*)pWnd;</code>

<code>176.</code><code> </code> 

<code>177.</code><code>      </code><code>// 更新下拉框以反映可能的比對</code>

<code>178.</code><code>      </code><code>pComboBox-&gt;ResetContent();</code>

<code>179.</code><code>      </code><code>for</code> <code>(</code><code>int</code> <code>i=0; iAddString(arMatches[i]);</code>

<code>180.</code><code>      </code><code>}</code>

<code>181.</code><code>      </code><code>// 使用者箭頭光标,這樣使用者才能選擇</code>

<code>182.</code><code>      </code><code>::SetCursor(LoadCursor(NULL,MAKEINTRESOURCE(IDC_ARROW))); </code>

<code>183.</code><code>      </code><code>// 顯示下拉框</code>

<code>184.</code><code>      </code><code>pComboBox-&gt;ShowDropDown();</code>

<code>185.</code><code>      </code><code>pComboBox-&gt;SetWindowText(IgnoreCompletion(s) ? s : arMatches[0]);</code>

<code>186.</code><code>      </code><code>pComboBox-&gt;SetEditSel(s.GetLength(),-1);</code>

<code>187.</code><code> </code> 

<code>188.</code><code>   </code><code>}</code><code>else</code> <code>if</code> <code>(m_iType==Edit &amp;&amp; !IgnoreCompletion(s)) {</code>

<code>189.</code><code>      </code><code>// 這種強制轉換從技術上講是不正确的,但它是一個标準的MFC訣竅.</code>

<code>190.</code><code>      </code><code>CEdit* pEdit = (CEdit*)pWnd;</code>

<code>191.</code><code>      </code><code>pEdit-&gt;SetWindowText(arMatches[0]);</code>

<code>192.</code><code>      </code><code>pEdit-&gt;SetSel(s.GetLength(),-1);</code>

<code>193.</code><code>   </code><code>}</code>

<code>194.</code><code>}</code>

<code>195.</code><code> </code> 

<code>196.</code><code>//////////////////////////////////////////////////////////////</code>

<code>197.</code><code>// 當使用者按下Backspace鍵删除敲入的字元時,這個函數用于關閉輸入完成特性。</code>

<code>198.</code><code>// 這時,目前的串将比對最後輸入的(前一個)串。在這種情況下,輸入完成特性</code>

<code>199.</code><code>// 是被屏蔽掉的。例如,如果使用者敲如‘foo’,程式将自動完成‘foobar’,字元</code>

<code>200.</code><code>// ‘bar’是高亮,如果使用者按下Backspace鍵或者Delete鍵删除‘bar’時,程式 </code>

<code>201.</code><code>// 不會再次完成foobar,而是保留‘foo’,即便是使用者繼續按Backspace鍵也一樣。</code>

<code>202.</code><code>// 這個函數是我寫的唯一一個說明比代碼本身還長函數:)!</code>

<code>203.</code><code>//</code>

<code>204.</code><code>//</code>

<code>205.</code><code>BOOL</code> <code>CAutoComplete::IgnoreCompletion(CString s)</code>

<code>206.</code><code>{</code>

<code>207.</code><code>   </code><code>return</code> <code>s==m_sPrevious.Left(s.GetLength());</code>

<code>208.</code><code>}</code>

這個類大體上實作了 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執行個體。

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

(圖二)

<code>1.</code><code>class</code> <code>CMyDialog :</code><code>public</code> <code>CDialog {</code>

<code>2.</code><code>protected</code><code>:</code>

<code>3.</code><code>    </code><code>CAutoComplete m_acEdit; </code><code>// 編輯框執行個體</code>

<code>4.</code><code>    </code><code>CAutoComplete m_acCombo;</code><code>// 組合框執行個體</code>

<code>5.</code><code>......</code>

<code>6.</code><code>};</code>

為了使用CAutoComplete,你必須用視窗(編輯框群組合框)指針初始化每一個執行個體,然後再添加串。例子中這些都是在 CMyDialog 的OnInitDialog中完成。

<code>01.</code><code>// in CMyDialog::OnInitDialog</code>

<code>02.</code><code>m_acCombo.Init(GetDlgItem(IDC_COMBO1));</code>

<code>03.</code><code>m_acEdit.Init(GetDlgItem(IDC_EDIT1));</code>

<code>04.</code><code>static</code> <code>LPCTSTR</code> <code>STRINGS[] = {</code>

<code>05.</code><code>    </code><code>"alpha"</code><code>,</code>

<code>06.</code><code>    </code><code>"alphabet"</code><code>,</code>

<code>07.</code><code>    </code><code>......</code>

<code>08.</code><code>    </code><code>NULL</code>

<code>09.</code><code>};</code>

<code>10.</code><code>for</code> <code>(</code><code>int</code> <code>i=0; STRINGS[i]; i++) {</code>

<code>11.</code><code>    </code><code>m_acCombo.GetStringList().Add(STRINGS[i]);</code>

<code>12.</code><code>    </code><code>m_acEdit.GetStringList().Add(STRINGS[i]);</code>

<code>13.</code><code>}</code>

因為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重載這個函數來處理感興趣的消息。

<code>1.</code><code>// 重載 CSubclassWnd::WindowProc</code>

<code>2.</code><code>LRESULT</code> <code>CAutoComplete::WindowProc(...)</code>

<code>3.</code><code>{</code>

<code>4.</code><code>    </code><code>if</code> <code>(</code><code>/* EN_CHANGE or CBN_EDITCHANGE */</code><code>))) {</code>

<code>5.</code><code>        </code><code>// try to complete</code>

<code>6.</code><code>    </code><code>}</code>

<code>7.</code><code>    </code><code>return</code> <code>CSubclassWnd::WindowProc(...);</code>

<code>8.</code><code>}</code>

請注意CAutoComplete不是一個CWnd派生的對象。它是從CSubclassWnd派生的,而CSubclassWnd又派生于CObject。在處理完消息之後,CAutoComplete調用CSubclassWnd::WindowProc,它以自己的方式經過原來的視窗過程傳遞消息到任何消息映射中的消息處理器。

一旦你明白了CSubclassWnd截獲消息的方式,剩下的事情就是要完成實際的autocompletion功能了,也就是比較串清單和使用者輸入,CAutoComplete在虛函數 OnComplete 中完成這個工作。預設實作 晴參見源代碼。通過比較使用者輸入與内部串表,如果比對的話便把比對項顯示在控制(編輯框)中,如果是組合框,CAutoComplete将所有比對選擇項顯示在下拉框中。

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

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

第二個訣竅是個小聰明。假設使用者敲入“al”,CAutoComplete通過高亮“pha”三個字元來完成整個串“alpha”。使用者現在想按Backspace删除掉“pha”,可憐的使用者會發現Backspace不起作用。解決辦法是當使用者縮短(删除)輸入的字元時就忽略掉文本完成動作。也就是說如果輸入的内容比對以前的輸入,就忽略完成動作。如果我沒有解釋清楚,實在是對不起,你到程式代碼中去找感覺吧。我添加了一個虛函數IgnoreCompletion來測試這種情況;如果這個函數傳回FALSE,CAutoComplete隻實作了文本完成,那明擺着算法不正确,我把它做成虛函數以便讓你能重載。

最後一個訣竅是其它的虛函數,它們将OnComplete分割成更小的操作,使你比較容易改變基本的行為。例如,OnComplete 做的第一件事情是調用一個虛函數 GetMatches 來擷取與使用者輸入比對的清單(CStringArray)。

<code>01.</code><code>void</code> <code>CAutoComplete::OnComplete(CWnd* pWnd, CString s)</code>

<code>02.</code><code>{</code>

<code>03.</code><code>    </code><code>CStringArray arMatches;</code><code>// 比對串</code>

<code>04.</code><code>    </code><code>if</code> <code>(s.GetLength()&gt;0 &amp;&amp; </code>

<code>05.</code><code>      </code><code>GetMatches(s, arMatches, m_iType==Edit)&gt;0) {</code>

<code>06.</code><code>            </code><code>DoCompletion(...);</code>

<code>07.</code><code>    </code><code>}</code>

<code>08.</code><code>    </code><code>m_sPrevious=s;</code><code>// 記住目前串</code>

<code>09.</code><code>}</code>

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 接口做了一個比較,見下表:

 

CAutoCompleteWnd

IAutoComplete

自己用 C++/MFC 實作,有充分的自主性。

shell32.dll 中現成的 COM 對象,

可用于所有 Win32 平台。

隻有 Windows 2000 才具備

容易改寫完成行為

無法改寫特性行為

不用格式串

Ctrl + Enter 格式串,如 www.%s.com

不用系統資料庫

需要預設的系統資料庫格式串

不需要串枚舉(IEnumString)

必須實作 IEnumString

可以進行完全控制

必須将就接口提供的功能

做這樣的比較并不是說誰好誰壞,而是哪一個更适合你的需要。