Autocomplete TEdit
http://forum.codecall.net/topic/75946-autocomplete-tedit/
Overview
Autocomplete feature really helpful for us speeding up our typing job.
For you who is not familiar with the term autocomplete,
it's when you type partial part of a word and then
you will be presented with a list of possible complete words.
You can just select the correct word from the list,
and that partial word will be automatically completed.
In programming, this feature very helpful to
"remember" class names, routine names, and variable name.
Not only to speed up the typing, autocomplete also very helpful to avoid typo.
In this tutorial I will show you a technique to implement autocomplete
in your Delphi program in order to provide your users the benefits of autocomplete.
I will implement autocomplete in a descendant of TEdit. I name it TAutocompleteEdit.
TAutoCompleteEdit
Behaviors
- Upon typing some chars, TAutocompleteEdit will check the typed word agains a word list. When no match found, do nothing.
- When one or more matches found, TAutocompleteEdit show the matches in a TListBox.We will call this TListBoxWordList.
- User can move between TAutocompleteEdit and WordList using down and up arrow.
- User select a complete word from WordList by highlighting the word and press Enter key.
- After user selected a word, the word will replace whatever content in TAutocompleteEdit.
- If user press Escape in TAutocompleteEdit or in WordList, WordList must dissapear.
- If TAutocompleteEdit lost focus, and the new focus is not in WordList, WordList must dissapear.
- If WordList lost focus, and the new focus is not in TAutocompleteEdit, WordList must dissapear.
- If later user type in another character and no match found, WordList must dissapear.
Key Methods
From the above behaviors, we decided to have the following methods.
- ShowWordList(AWords: TStrings).
This method is responsible to create WordList TListBox when needed,
populate it with words contained in AWords, and
also to patch its events so we can achieve behavior #3, #4, #5, #6, and #8. - HideWordList.
This method is responsible to hide and clean up WordList. - Change.
This is where to respond when the content of TAutocompleteEdit changed.
So this is where we do the checking.
This method actually already exist in TAutocompleteEdit's parent.
So what we are going to do is override it, and introduce our behavior. - DoExit.
This method also already exist in TAutocompleteEdit's parent.
We are going to override it and introduce new behavior, in order to achieve behavior #7. - KeyDown(var Key: Word; Shift: TShiftState).
This method also already exist in TAutocompleteEdit's parent.
We are going to override it to achieve behavior #3 and #6.
Key Methods Implementations
1. ShowWordList(AWords: TStrings).
procedure TAutocompleteEdit.ShowWordList(AWords: TStrings); begin if FWordList=nil then begin FWordList := TListBox.Create(Self); FWordList.ParentCtl3D := False; FWordList.Ctl3D := False; FWordList.Parent := Self.Parent; FWordList.TabStop := False; FWordList.OnExit := HandleWordListLostFocus; FWordList.OnKeyPress := HandleWordListKeyPress; FWordList.OnKeyDown := HandleWordListKeyDown; end; FWordList.Items.Assign(AWords); if FWordListWidth < 1 then FWordList.SetBounds(Self.Left, Self.Top + Self.Height, Self.Width, FWordListHeight) else FWordList.SetBounds(Self.Left, Self.Top + Self.Height, FWordListWidth, FWordListHeight); FWordList.Show; end;
2. HideWordList
procedure TAutocompleteEdit.HideWordList; begin PostMessage(Self.Handle, MSG_HIDEWORDLIST, 0, 0); end;
Note that in the above method we only post a custom message. The custom message handler in turn will call this private method.
procedure TAutocompleteEdit.HandleHideWordList; begin FWordList.Free; FWordList := nil; end;
3. Change.
procedure TAutocompleteEdit.Change; var S: TStrings; begin inherited; if AutocompleteMan.IsRecognized(Self.Text) then begin S := TStringList.Create; try if AutocompleteMan.IsRecognized(Self.Text, S) then ShowWordList(S); finally S.Free; end; end else HideWordList; end;
4. DoExit.
procedure TAutocompleteEdit.DoExit; begin if Assigned(FWordList) and FWordList.Visible and not FWordList.Focused then HideWordList; inherited; end;
5. KeyDown(var Key: Word; Shift: TShiftState).
procedure TAutocompleteEdit.KeyDown(var Key: Word; Shift: TShiftState); begin if Key=VK_ESCAPE then HideWordList else if (Key=VK_DOWN) and Assigned(FWordList) and FWordList.Visible then begin FCaretPos := Self.SelStart; FWordList.SetFocus; if FWordList.ItemIndex < 0 then FWordList.ItemIndex := 0; end else inherited; end;
Here is the complete source code of TAutocompleteEdit:
TEdit with Autocomplete.zip 1.84MB 603 downloads.
Feel free to use it or improve it for any kind of use.
Cheers!
1 unit AutocompleteEdit; 2 3 interface 4 5 uses 6 Windows 7 , Classes 8 , Vcl.StdCtrls 9 , SysUtils 10 , StrUtils 11 , Messages 12 ; 13 14 const 15 MSG_HIDEWORDLIST = WM_USER + 222; 16 17 type 18 TAutocompleteEdit=class(TEdit) 19 private 20 FWordList: TListBox; 21 FCaretPos: Integer; 22 FWordListHeight: Integer; 23 FWordListWidth: Integer; 24 25 procedure HandleWordListLostFocus(ASender: TObject); 26 procedure HandleWordListSelectItem(ASender: TObject); 27 procedure HandleWordListKeyPress(Sender: TObject; var Key: Char); 28 procedure HandleWordListKeyDown(ASender: TObject; var Key: Word; Shift: TShiftState); 29 procedure HandleHideWordList(var AMsg); overload; message MSG_HIDEWORDLIST; 30 procedure HandleHideWordList; overload; 31 procedure SetWordListHeight(const Value: Integer); 32 procedure SetWordListWidth(const Value: Integer); 33 34 procedure RegainFocus; 35 protected 36 procedure ShowWordList(AWords: TStrings); 37 procedure HideWordList; 38 procedure Change; override; 39 procedure KeyDown(var Key: Word; Shift: TShiftState); override; 40 procedure DoExit; override; 41 public 42 constructor Create(AOwner: TComponent); override; 43 published 44 property WordListHeight: Integer read FWordListHeight write SetWordListHeight; 45 property WordListWidth: Integer read FWordListWidth write SetWordListWidth; 46 end; 47 48 TAutocompleteMan=class 49 private 50 FWords: TStrings; 51 public 52 constructor Create; 53 destructor Destroy; override; 54 55 function IsRecognized(AWord: string): Boolean; overload; 56 function IsRecognized(AWord: string; AWordList: TStrings): Boolean; overload; 57 58 procedure LoadFromFile(const AFilename: string); 59 procedure AddWord(const AWord: string); 60 61 property Words: TStrings read FWords; 62 end; 63 64 procedure Register; 65 66 var 67 AutocompleteMan: TAutocompleteMan; 68 69 70 implementation 71 72 procedure Register; 73 begin 74 RegisterComponents('CodeCall', [TAutocompleteEdit]); 75 end; 76 77 { TAutocompleteMan } 78 79 procedure TAutocompleteMan.AddWord(const AWord: string); 80 begin 81 FWords.Add(UpperCase(AWord) + '=' + AWord); 82 end; 83 84 constructor TAutocompleteMan.Create; 85 begin 86 FWords := TStringList.Create; 87 TStringList(FWords).Duplicates := dupIgnore; 88 end; 89 90 destructor TAutocompleteMan.Destroy; 91 begin 92 FWords.Free; 93 inherited; 94 end; 95 96 function TAutocompleteMan.IsRecognized(AWord: string): Boolean; 97 var 98 i: Integer; 99 begin 100 Result := False; 101 AWord := UpperCase(AWord); 102 for i := 0 to FWords.Count-1 do 103 begin 104 Result := System.Pos(AWord, FWords.Names[i]) > 0; 105 if Result then 106 Break; 107 end; 108 end; 109 110 function TAutocompleteMan.IsRecognized(AWord: string; 111 AWordList: TStrings): Boolean; 112 var 113 i: Integer; 114 begin 115 Result := False; 116 AWord := UpperCase(AWord); 117 AWordList.Clear; 118 for i := 0 to FWords.Count-1 do 119 begin 120 if System.Pos(AWord, FWords.Names[i]) > 0 then 121 begin 122 Result := True; 123 AWordList.Add(FWords.ValueFromIndex[i]); 124 end; 125 end; 126 end; 127 128 procedure TAutocompleteMan.LoadFromFile(const AFilename: string); 129 var 130 i: Integer; 131 F: TStrings; 132 begin 133 F := TStringList.Create; 134 try 135 F.LoadFromFile(AFilename); 136 for i := 0 to F.Count-1 do 137 AddWord(F[i]); 138 finally 139 F.Free; 140 end; 141 end; 142 143 { TAutocompleteEdit } 144 145 procedure TAutocompleteEdit.Change; 146 var 147 S: TStrings; 148 begin 149 inherited; 150 if AutocompleteMan.IsRecognized(Self.Text) then 151 begin 152 S := TStringList.Create; 153 try 154 if AutocompleteMan.IsRecognized(Self.Text, S) then 155 ShowWordList(S); 156 finally 157 S.Free; 158 end; 159 end 160 else 161 HideWordList; 162 end; 163 164 procedure TAutocompleteEdit.HandleHideWordList(var AMsg); 165 begin 166 HandleHideWordList; 167 end; 168 169 constructor TAutocompleteEdit.Create(AOwner: TComponent); 170 begin 171 inherited; 172 FWordListHeight := 60; 173 end; 174 175 procedure TAutocompleteEdit.DoExit; 176 begin 177 if Assigned(FWordList) and FWordList.Visible and not FWordList.Focused then 178 HideWordList; 179 inherited; 180 end; 181 182 procedure TAutocompleteEdit.HandleHideWordList; 183 begin 184 FWordList.Free; 185 FWordList := nil; 186 end; 187 188 procedure TAutocompleteEdit.HandleWordListKeyDown(ASender: TObject; 189 var Key: Word; Shift: TShiftState); 190 begin 191 if (Key=VK_UP) and (FWordList.ItemIndex=0) then 192 RegainFocus; 193 end; 194 195 procedure TAutocompleteEdit.HandleWordListKeyPress(Sender: TObject; 196 var Key: Char); 197 begin 198 case Key of 199 #13: begin 200 Key := #0; 201 Self.Text := FWordList.Items[FWordList.ItemIndex]; 202 Self.SetFocus; 203 Self.SelStart := Length(Self.Text); 204 Self.SelLength := 0; 205 HideWordList; 206 end; 207 #27: begin 208 RegainFocus; 209 HideWordList; 210 end; 211 else begin 212 RegainFocus; 213 end; 214 end; 215 end; 216 217 procedure TAutocompleteEdit.HandleWordListLostFocus(ASender: TObject); 218 begin 219 if not Self.Focused then 220 HideWordList; 221 end; 222 223 procedure TAutocompleteEdit.HandleWordListSelectItem(ASender: TObject); 224 begin 225 Self.Text := FWordList.Items[FWordList.ItemIndex]; 226 HideWordList; 227 end; 228 229 procedure TAutocompleteEdit.HideWordList; 230 begin 231 PostMessage(Self.Handle, MSG_HIDEWORDLIST, 0, 0); 232 end; 233 234 procedure TAutocompleteEdit.KeyDown(var Key: Word; Shift: TShiftState); 235 begin 236 if Key=VK_ESCAPE then 237 HideWordList 238 else if (Key=VK_DOWN) and Assigned(FWordList) and FWordList.Visible then 239 begin 240 FCaretPos := Self.SelStart; 241 FWordList.SetFocus; 242 if FWordList.ItemIndex < 0 then 243 FWordList.ItemIndex := 0; 244 end 245 else 246 inherited; 247 end; 248 249 procedure TAutocompleteEdit.RegainFocus; 250 begin 251 Self.SetFocus; 252 Self.SelStart := FCaretPos; 253 Self.SelLength := 0; 254 end; 255 256 procedure TAutocompleteEdit.SetWordListHeight(const Value: Integer); 257 begin 258 if FWordListHeight <> Value then 259 begin 260 FWordListHeight := Value; 261 if Assigned(FWordList) then 262 FWordList.Height := FWordListHeight; 263 end; 264 end; 265 266 procedure TAutocompleteEdit.SetWordListWidth(const Value: Integer); 267 begin 268 if FWordListWidth <> Value then 269 begin 270 FWordListWidth := Value; 271 if Assigned(FWordList) then 272 begin 273 if FWordListWidth < 1 then 274 FWordList.Width := Self.Width 275 else 276 FWordList.Width := FWordListWidth; 277 end; 278 end; 279 end; 280 281 procedure TAutocompleteEdit.ShowWordList(AWords: TStrings); 282 begin 283 if FWordList=nil then 284 begin 285 FWordList := TListBox.Create(Self); 286 FWordList.ParentCtl3D := False; 287 FWordList.Ctl3D := False; 288 FWordList.Parent := Self.Parent; 289 FWordList.TabStop := False; 290 FWordList.OnExit := HandleWordListLostFocus; 291 FWordList.OnKeyPress := HandleWordListKeyPress; 292 FWordList.OnKeyDown := HandleWordListKeyDown; 293 end; 294 295 FWordList.Items.Assign(AWords); 296 if FWordListWidth < 1 then 297 FWordList.SetBounds(Self.Left, Self.Top + Self.Height, Self.Width, FWordListHeight) 298 else 299 FWordList.SetBounds(Self.Left, Self.Top + Self.Height, FWordListWidth, FWordListHeight); 300 301 FWordList.Show; 302 end; 303 304 initialization 305 AutocompleteMan := TAutocompleteMan.Create; 306 307 finalization 308 AutocompleteMan.Free; 309 end.