使用者在 TComboBox (Style = csDropDown) 輸入文字時,自動從下拉清單中尋找符合前綴的項目並補全,游標停在補全部分並反白,方便接受或繼續輸入。VCL 內建 AutoComplete / AutoCompleteOptions (BCB 2009+) 功能有限,舊版本需自行實作。
// Unit1.h
class TForm1 : public TForm
{
__published:
TComboBox *ComboBox1;
void __fastcall ComboBox1Change(TObject *Sender);
void __fastcall ComboBox1KeyDown(TObject *Sender, WORD &Key, TShiftState Shift);
private:
WORD FLastKey; // 記錄上一個按鍵
public:
__fastcall TForm1(TComponent* Owner);
};
// Unit1.cpp
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
__fastcall TForm1::TForm1(TComponent* Owner) : TForm(Owner), FLastKey(0) {}
void __fastcall TForm1::ComboBox1KeyDown(TObject *Sender, WORD &Key, TShiftState Shift)
{
FLastKey = Key; // 記錄按鍵,供 Change 事件判斷
}
//---------------------------------------------------------------------------
void __fastcall TForm1::ComboBox1Change(TObject *Sender)
{
AnsiString Text = ComboBox1->Text;
// 1. 若使用者按 Backspace 或 Delete,視為刪除操作,不自動補全
if (FLastKey == VK_BACK || FLastKey == VK_DELETE) {
FLastKey = 0;
return;
}
FLastKey = 0;
// 2. 僅在游標位於文字末尾時觸發 (避免中間插入字元時誤觸發)
if (ComboBox1->SelStart != Text.Length())
return;
// 3. 從下拉清單尋找以目前文字為前綴的項目 (不區分大小寫)
int Index = SendMessage(ComboBox1->Handle, CB_FINDSTRING, (WPARAM)-1, (LPARAM)Text.c_str());
if (Index >= 0) {
// 4. 找到:設定 ItemIndex 顯示完整項目
ComboBox1->ItemIndex = Index;
// 5. 設定編輯區選取範圍:從原輸入長度到結尾反白
// 使用者可直接輸入覆蓋,或按 Enter/Tab 接受
SendMessage(ComboBox1->Handle, CB_SETEDITSEL, 0,
MAKELPARAM(Text.Length(), -1)); // -1 表示到結尾
}
// 未找到則保持原輸入,不做任何事
}
| 訊息/函數 | 用途 |
|---|---|
CB_FINDSTRING | 在 ComboBox 清單中搜尋以指定字串開頭的項目,傳回索引 (0-based),找不到傳回 CB_ERR (-1)。wParam = -1 從頭搜尋。 |
CB_SETEDITSEL | 設定編輯區選取範圍。lParam 低字 = 起始位置,高字 = 結束位置 (-1 表結尾)。 |
VK_BACK (0x08), VK_DELETE (0x2E) | 虛擬鍵碼,判斷刪除鍵避免誤觸發補全。 |
// 表單私有欄位
bool FCaseSensitive; // 預設 false
void __fastcall TForm1::ComboBox1Change(TObject *Sender)
{
// ... 前略 ...
// 3. 自訂比較函式 (支援大小寫敏感)
int Index = -1;
for (int i = 0; i < ComboBox1->Items->Count; ++i) {
AnsiString Item = ComboBox1->Items->Strings[i];
if (FCaseSensitive) {
if (Item.SubString(1, Text.Length()) == Text) { Index = i; break; }
} else {
if (Item.SubString(1, Text.Length()).LowerCase() == Text.LowerCase()) { Index = i; break; }
}
}
// ... 後略 ...
}
VCL 內建 IAutoComplete2 介面支援更強大的自動完成 (檔案系統、歷史記錄、自訂來源):
#include <shlobj.hpp> // IAutoComplete, IAutoComplete2
void __fastcall TForm1::EnableShellAutoComplete(TComboBox* Combo)
{
_di_IAutoComplete2 ac;
HRESULT hr = CoCreateInstance(CLSID_AutoComplete, NULL, CLSCTX_INPROC_SERVER,
IID_IAutoComplete2, (void**)&ac);
if (SUCCEEDED(hr)) {
_di_IEnumString enumStr;
// 可掛載檔案系統、URL 歷史、或自訂 IEnumString 實作
hr = SHCreateStdEnumString(NULL, NULL, &enumStr); // 範例:檔案系統
if (SUCCEEDED(hr)) {
ac->Init(Combo->Handle, enumStr, NULL, NULL);
ac->SetOptions(ACO_AUTOSUGGEST | ACO_AUTOAPPEND | ACO_SEARCH);
}
}
}
需連結 shell32.lib、ole32.lib,並在專案啟用 COM (CoInitialize)。
SelStart == Text.Length() 才觸發,否則中間插入字元會導致選取範圍錯亂。Change 會多次觸發,建議檢查 GetCompositionString 或簡單忽略 SelLength > 0 時的自動補全。CB_FINDSTRING 為線性搜尋,項目超過數千時明顯延遲。改用 TStringList 排序後二分搜尋,或資料庫 LIKE 'prefix%' 查詢。