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

  1. Upon typing some chars, TAutocompleteEdit will check the typed word agains a word list. When no match found, do nothing.
  2. When one or more matches found, TAutocompleteEdit show the matches in a TListBox.We will call this TListBoxWordList.
  3. User can move between TAutocompleteEdit and WordList using down and up arrow.
  4. User select a complete word from WordList by highlighting the word and press Enter key.
  5. After user selected a word, the word will replace whatever content in TAutocompleteEdit.
  6. If user press Escape in TAutocompleteEdit or in WordList, WordList must dissapear.
  7. If TAutocompleteEdit lost focus, and the new focus is not in WordList, WordList must dissapear.
  8. If WordList lost focus, and the new focus is not in TAutocompleteEdit, WordList must dissapear.
  9. 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.

  1. 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.
  2. HideWordList.
    This method is responsible to hide and clean up WordList.
  3. 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.
  4. 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.
  5. 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.

 

posted @ 2014-10-20 00:05  IAmAProgrammer  阅读(745)  评论(0编辑  收藏  举报