Решил создать свой TextEdit с AutoComplete по некой аналогии с Яндексом.
Поиск всегда происходит по содержимому строки, а не только по её началу.
Так же добавил новый Event - OnDelayedChange
Это аналог OnChage, только срабатывает через 750 милисекунд после последнего изменения.
Обычно, используется что бы дождаться окончательного ввода данных, и сработать 1 раз, а не при каждом изменении.
ДропДаун взял из ACComboBox, который я когда-то скачал в интернете. Если честно, не помню от куда.
Пример
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
procedure TForm1.FormCreate(Sender: TObject);
var
AC:TAutoCompleteTextEdit;
begin
AC:=TAutoCompleteTextEdit.Create(Self);
AC.Parent:=Self;
AC.Width:=300;
AC.OnDelayedChange:=OnDelayedChange;
AC.Items.Add('Автоперевозки');
AC.Items.Add('Высокоскоростная автомагистраль');
AC.Items.Add('Коробка автоматического переключения передач');
AC.Items.Add('Обязательное страхование гражданской автоответственности (ОСАГО)');
AC.Items.Add('Полуавтомат');
AC.Items.Add('Полупулемёт');
AC.Items.Add('Автомат Калашникова');
AC.Items.Add('Помидор');
AC.Items.Add('Огурец');
AC.Items.Add('Картофель');
end;
procedure TForm1.OnDelayedChange(Sender: TObject);
begin
Label1.Caption:= (Sender as TAutoCompleteTextEdit).Text;
end;
CustomControl.AutoCompleteTextEdit.pas
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164.
165.
166.
167.
168.
169.
170.
171.
172.
173.
174.
175.
176.
177.
178.
179.
180.
181.
182.
183.
184.
185.
186.
187.
188.
189.
190.
191.
192.
193.
194.
195.
196.
197.
198.
199.
200.
201.
202.
203.
204.
205.
206.
207.
208.
209.
210.
211.
212.
213.
214.
215.
216.
217.
218.
219.
220.
221.
222.
223.
224.
225.
226.
227.
228.
229.
230.
231.
232.
233.
234.
235.
236.
237.
238.
239.
240.
241.
242.
243.
244.
245.
246.
247.
248.
249.
250.
251.
252.
253.
254.
255.
256.
257.
258.
259.
260.
261.
262.
263.
264.
unit CustomControl.AutoCompleteTextEdit;
interface
uses SysUtils, Windows, Messages, Classes, Controls, StdCtrls, cxTextEdit,
cxDBEdit, ExtCtrls, CustomControl.AutoCompleteDropDown;
type
TOnShowDropDownEvent = procedure (Sender:TObject; ItemsToShow:TStrings) of object;
TAutoCompleteTextEdit = class(TcxTextEdit, IHaveDropDown)
private
FDropped: Boolean;
FApplyingText:Boolean;
FItems: TStringList;
FOnShowDropDown: TOnShowDropDownEvent;
FMaxDropDownItems: Integer;
FOnDelayedChange: TNotifyEvent;
FTimer:TTimer;
FLastTick:Int64;
FDelayChangeInterval: Word;
procedure InternalShowDropDown(Count:Integer);
procedure InternalCreateTimer;
procedure SetOnDelayedChange(const Value: TNotifyEvent);
protected
FDropDown: TDropDownListBox;
procedure TryHandleUserInput;
procedure WMMouseWheel(var M: TWMMouseWheel); message WM_MOUSEWHEEL;
procedure CMExit(var M: TCMExit); message CM_EXIT;
procedure KeyDown(var Key: Word; Shift: TShiftState); override;
procedure KeyPress(var Key: Char); override;
function PrepareACStrings(const AText: String):Integer; virtual;
procedure SendKeyToDropDown(var Key:Word);
procedure DoChange; override;
procedure DoOnShowDropDown; virtual;
procedure DoOnDelayedChange; virtual;
procedure HandleTimer(Sender:TObject);
function GetInnerEditClass: TControlClass;override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure ShowDropDown;
procedure HideDropDown(ApplyText:Boolean);
procedure FinishTimer;
property Dropped:Boolean read FDropped;
published
property Items: TStringList read FItems;
property DelayChangeInterval:Word read FDelayChangeInterval write FDelayChangeInterval;
property MaxDropDownItems:Integer read FMaxDropDownItems write FMaxDropDownItems;
property OnShowDropDown:TOnShowDropDownEvent read FOnShowDropDown write FOnShowDropDown;
property OnDelayedChange:TNotifyEvent read FOnDelayedChange write SetOnDelayedChange;
end;
implementation
type
TInterceptKeyTextEdit = class(TcxCustomInnerTextEdit) //Для перехвата ENTER в модальном окне с TButton.Defualt:=True
private
procedure CNKeyDown(var Message: TWMKeyDown); message CN_KEYDOWN;
function OwnerHasDropDownOpened:Boolean;
end;
{ TInterceptKeyTextEdit }
procedure TInterceptKeyTextEdit.CNKeyDown(var Message: TWMKeyDown);
begin
if OwnerHasDropDownOpened=False then
inherited;
end;
function TInterceptKeyTextEdit.OwnerHasDropDownOpened: Boolean;
begin
Result:=(Owner is TAutoCompleteTextEdit) and (Owner as TAutoCompleteTextEdit).Dropped=True;
end;
{ TAutoCompleteTextEdit }
procedure TAutoCompleteTextEdit.CMExit(var M: TCMExit);
begin
inherited;
HideDropDown(False);
end;
constructor TAutoCompleteTextEdit.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FDropDown:=TDropDownListBox.Create(Self);
FItems:=TStringList.Create;
DelayChangeInterval:=750;
FDropped:=False;
FApplyingText:=False;
MaxDropDownItems:=7;
end;
destructor TAutoCompleteTextEdit.Destroy;
begin
FItems.Free;
inherited Destroy;
end;
procedure TAutoCompleteTextEdit.DoChange;
begin
inherited DoChange;
TryHandleUserInput;
if FApplyingText=False then
ShowDropDown;
end;
procedure TAutoCompleteTextEdit.DoOnShowDropDown;
begin
if Assigned(OnShowDropDown) then
OnShowDropDown(Self,FDropDown.Items);
end;
procedure TAutoCompleteTextEdit.DoOnDelayedChange;
begin
If Assigned(FTimer)then FTimer.Enabled:=False;
If Assigned(OnDelayedChange) then
OnDelayedChange(Self);
end;
function TAutoCompleteTextEdit.GetInnerEditClass: TControlClass;
begin
Result:=TInterceptKeyTextEdit;
end;
procedure TAutoCompleteTextEdit.HandleTimer(Sender: TObject);
begin
if (GetTickCount >= FLastTick+DelayChangeInterval) and (Dropped=False) and Enabled then
DoOnDelayedChange;
end;
procedure TAutoCompleteTextEdit.HideDropDown(ApplyText: Boolean);
var
I: Integer;
begin
ShowWindow(FDropDown.Handle, SW_HIDE);
if ApplyText then
begin
I:=FDropDown.ItemIndex;
if I<>-1 then
begin
FApplyingText:=True;
Text:=FDropDown.Items[I];
FApplyingText:=False;
SelStart:=Length(Text);
SelLength:=Length(Text);
end;
end;
FDropped:=False;
end;
procedure TAutoCompleteTextEdit.InternalShowDropDown(Count:Integer);
var
P:TPoint;
begin
If Count>MaxDropDownItems then
Count:=MaxDropDownItems;
FDropped:=True;
P.X:=1;
P.Y:=Height-1;
P:=ClientToScreen(P);
SetWindowPos(FDropDown.Handle, HWND_TOPMOST, P.X, P.Y, Width-GetSystemMetrics(SM_CXVSCROLL)-2, Count*FDropDown.ItemHeight+2, SWP_SHOWWINDOW);
end;
procedure TAutoCompleteTextEdit.KeyDown(var Key: Word; Shift: TShiftState);
begin
if (FDropped) then
case Key of
VK_ESCAPE: HideDropDown(False);
VK_RETURN: HideDropDown(True);
VK_UP, VK_DOWN, VK_NEXT, VK_PRIOR: SendKeyToDropDown(Key);
else
inherited KeyDown(Key, Shift);
end
else
inherited KeyDown(Key, Shift)
end;
procedure TAutoCompleteTextEdit.KeyPress(var Key: Char);
begin
if ((Ord(Key)=VK_RETURN) or (Ord(Key)=VK_ESCAPE)) then
Key:=#0;
inherited KeyPress(Key);
end;
function TAutoCompleteTextEdit.PrepareACStrings(const AText: String):Integer;
var
x: Integer;
begin
FDropDown.Items.Clear;
for x:=0 to FItems.Count-1 do
If Pos(AnsiLowerCase(AText), AnsiLowerCase(FItems[x]))>0 then
FDropDown.Items.Add(FItems[x]);
DoOnShowDropDown;
Result:= FDropDown.Items.Count;
end;
procedure TAutoCompleteTextEdit.SendKeyToDropDown(var Key: Word);
var
M: TWMKeyDown;
begin
FillChar(M, SizeOf(M), 0);
M.Msg:=WM_KEYDOWN;
M.CharCode:=Key;
SendMessage(FDropDown.Handle, TMessage(M).Msg, TMessage(M).WParam, TMessage(M).LParam);
FillChar(M, SizeOf(M), 0);
M.Msg:=WM_KEYUP;
M.CharCode:=Key;
SendMessage(FDropDown.Handle, TMessage(M).Msg, TMessage(M).WParam, TMessage(M).LParam);
Key:=0;
end;
procedure TAutoCompleteTextEdit.ShowDropDown;
begin
if (Text='') or (PrepareACStrings(Text)=0) then
HideDropDown(False)
else
InternalShowDropDown(FDropDown.Items.Count);
end;
procedure TAutoCompleteTextEdit.TryHandleUserInput;
begin
FLastTick:=GetTickCount;
if Assigned(FTimer) then
FTimer.Enabled:=Assigned(OnDelayedChange);
end;
procedure TAutoCompleteTextEdit.WMMouseWheel(var M: TWMMouseWheel);
begin
TMessage(M).Result:=SendMessage(FDropDown.Handle, TMessage(M).Msg, TMessage(M).WParam, TMessage(M).LParam);
FDropDown.UpdateItemIndex
end;
procedure TAutoCompleteTextEdit.FinishTimer;
begin
If Assigned(FTimer) and (FTimer.Enabled) then
begin
FTimer.Enabled:=False;
DoOnDelayedChange;
end;
end;
procedure TAutoCompleteTextEdit.InternalCreateTimer;
begin
If not Assigned(FTimer) then
begin
FTimer:=TTimer.Create(Self);
FTimer.OnTimer:=HandleTimer;
FTimer.Interval:=500;
FTimer.Enabled:=False;
FLastTick:=GetTickCount;
end;
end;
procedure TAutoCompleteTextEdit.SetOnDelayedChange(const Value: TNotifyEvent);
begin
FOnDelayedChange := Value;
If Assigned(Value) then
InternalCreateTimer;
end;
end.
CustomControl.AutoCompleteDropDown.pas
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
unit CustomControl.AutoCompleteDropDown;
interface
uses SysUtils, Windows, Messages, Classes, Controls, StdCtrls, Forms;
type
IHaveDropDown = interface
['{DB4435AC-3B5A-4541-9C7E-37A42202CC9C}']
procedure ShowDropDown;
procedure HideDropDown(ApplyText:Boolean);
end;
TDropDownListBox = class(TListBox)
protected
procedure WMActivateApp(var M: TMessage); message WM_ACTIVATEAPP;
procedure WMMouseMove(var M: TWMMouseMove); message WM_MOUSEMOVE;
procedure WMLButtonUp(var M: TWMLButtonUp); message WM_LBUTTONUP;
procedure CreateParams(var Params: TCreateParams); override;
procedure HideDropDown(ApplyText:Boolean);
public
procedure UpdateItemIndex;
end;
implementation
procedure TDropDownListBox.HideDropDown(ApplyText:Boolean);
begin
if Assigned(Owner) and Supports(Owner,IHaveDropDown) and (Visible)then
(Owner as IHaveDropDown).HideDropDown(True);
end;
procedure TDropDownListBox.UpdateItemIndex;
var
P: TPoint;
I: Integer;
begin
GetCursorPos(P);
P:=ScreenToClient(P);
I:=ItemAtPos(P, True);
if I<>-1 then
ItemIndex:=I;
end;
procedure TDropDownListBox.WMLButtonUp(var M: TWMLButtonUp);
begin
inherited;
if (ItemIndex<>-1) then
HideDropDown(True);
end;
procedure TDropDownListBox.WMMouseMove(var M: TWMMouseMove);
begin
inherited;
UpdateItemIndex;
end;
procedure TDropDownListBox.CreateParams(var Params: TCreateParams);
begin
inherited CreateParams(Params);
Params.ExStyle:=WS_EX_TOOLWINDOW;
Params.WndParent:=GetDesktopWindow;
Params.Style:=WS_CHILD or WS_BORDER or WS_CLIPSIBLINGS or WS_OVERLAPPED or WS_VSCROLL or LBS_NOINTEGRALHEIGHT;
end;
procedure TDropDownListBox.WMActivateApp(var M: TMessage);
begin
inherited;
HideDropDown(False);
end;
end.