PDA

Ver la Versión Completa : Autocompletar en TEdit


santiago14
10-09-2013, 00:36:48
Buenas, estoy buscando la forma de hacer el autocompletado de un TEdit.
Lo cuento en detalle.
Ya he logrado usar la librería de Windows "Shlwapi.dll" pero a medias.

Cuando estoy en la caja de texto y presiono algunas teclas me hace el autocompletado, pero de cosas que ya hay guardadas en algún lugar del S.O. Esto está bien y es una parte de lo que quiero hacer. Vale recordar que se va llenando con textos que se le cargan, digamos, desde el IE.

Lo que quiero ahora es que también "memorice" lo que yo le pongo en mi caja de texto para que en una próxima intervención también sea parte de la lista.

Un poco de código:


//...
function SHAutoComplete(hwndEdit: HWnd; dwFlags: DWORD): HResult; stdcall; external 'Shlwapi.dll';

//...

const
SHACF_AUTOSUGGEST_FORCE_ON = $10000000;
SHACF_AUTOSUGGEST_FORCE_OFF = $20000000;
SHACF_AUTOAPPEND_FORCE_ON = $40000000;
SHACF_AUTOAPPEND_FORCE_OFF = $80000000;
SHACF_DEFAULT = $0;
SHACF_FILESYSTEM = $1;
SHACF_URLHISTORY = $2;
SHACF_URLMRU = $4;

//...

procedure TfrmPrincipal.FormCreate(Sender: TObject);
var
Options: dWord;
begin
//...

Options := SHACF_FILESYSTEM or SHACF_URLHISTORY or SHACF_URLMRU or
SHACF_AUTOSUGGEST_FORCE_ON or SHACF_AUTOAPPEND_FORCE_ON;
SHAutoComplete(txtAuto.Handle, Options);

end;

//...



Mi caja de texto "txtAuto" funciona como lo he mencionado, mientras escribo me va haciendo sugerencias de lo que tiene guardado en algún lugar.
Si pongo ms ya me sugiere msconfig, este comportamiento es conocido.
Ahora pongo "santiago", esta palabra no está registrada aún por lo cual no me hace sugerencia. Lo que quiero es registrarla para que la próxima vez ya me la ponga como parte de las sugerencias. O sea, pongo "sa" y ya tenga "santiago" en la lista que aparece.

Espero haber sido claro.

Gracias.

ecfisa
10-09-2013, 02:14:33
Hola Santiago.

A primera vista la complicación que creo se te va a presentar con el uso de SHAutoComplete (http://msdn.microsoft.com/en-us/library/windows/desktop/bb759862%28v=vs.85%29.aspx) es que, para que memorice tus ingresos, vas a tener que almacenarlos en el archivo de historial de IE.

Esto último, que seguramente sea posible (aunque desconozco como), pienso que tiene al menos dos desventajas. La primera es que quién escriba en el navegador recibiría el autocompletado de tu "diccionario personalizado" sin que sea necesariamente una URL. La segunda es que algún usuario podría borrar el historial o seleccionar la opción "Eliminar el historial de navegación al salir"...

Tal vez exista una solución alternativa mas simple... ¿ Cuál es la finalidad del código ?

Saludos :)

santiago14
10-09-2013, 02:43:34
Hola Santiago.

A primera vista la complicación que creo se te va a presentar con el uso de SHAutoComplete (http://msdn.microsoft.com/en-us/library/windows/desktop/bb759862%28v=vs.85%29.aspx) es que, para que memorice tus ingresos, vas a tener que almacenarlos en el archivo de historial de IE.

Esto último, que seguramente sea posible (aunque desconozco como), pienso que tiene al menos dos desventajas. La primera es que quién escriba en el navegador recibiría el autocompletado de tu "diccionario personalizado" sin que sea necesariamente una URL. La segunda es que algún usuario podría borrar el historial o seleccionar la opción "Eliminar el historial de navegación al salir"...

Tal vez exista una solución alternativa mas simple... ¿ Cuál es la finalidad del código ?

Saludos :)

Entiendo lo de las desventajas, pero para mi caso no importaría. Que en IE vean mi diccionario personal no tiene importancia. Que lo borren, bueno, es un riesgo que habrá que correr.

Si hay una solución mas simple, mejor.
La finalidad del código es llenar unas cajitas de texto en una ventana de facturación que no se pueden dejar libres. Nombre del cliente, teléfono, dirección. La manera mas "fácil", a priori, era esto.
Sería bueno encontrar una buena solución para poder escribir en ese archivo.

Santiago.

ecfisa
10-09-2013, 03:31:07
Hola Santiago.

No sé si cumplirá tus espectativas, pero una alternativa simple es usar un TComboBox, por ejemplo:

...
const
HISTORIAL = 'C:\CARPETA\HISTORIAL.TXT';

procedure TForm1.FormCreate(Sender: TObject);
begin
with ComboBox1 do
begin
Clear;
if FileExists(HISTORIAL) then
Items.LoadFromFile(HISTORIAL);
Sorted := True;
Style := csDropDown;
end;
end;

procedure TForm1.ComboBox1Exit(Sender: TObject);
begin
with ComboBox1.Items do
begin
Add(ComboBox1.Text);
SaveToFile(HISTORIAL);
end;
end;
...


Saludos :)

nlsgarcia
10-09-2013, 05:02:13
santiago14,


...estoy buscando la forma de hacer el autocompletado de un TEdit...he logrado usar la librería de Windows "Shlwapi.dll" pero a medias...


Revisa este link:

The AutoComplete COM object : http://users.skynet.be/oleole/AutoComplete.html
Espero sea útil :)

Nelson.

santiago14
10-09-2013, 14:10:46
Hola Santiago.

No sé si cumplirá tus espectativas, pero una alternativa simple es usar un TComboBox, por ejemplo:
Código Delphi [-] (http://www.clubdelphi.com/foros/#)... const HISTORIAL = 'C:\CARPETA\HISTORIAL.TXT'; procedure TForm1.FormCreate(Sender: TObject); begin with ComboBox1 do begin Clear; if FileExists(HISTORIAL) then Items.LoadFromFile(HISTORIAL); Sorted := True; Style := csDropDown; end; end; procedure TForm1.ComboBox1Exit(Sender: TObject); begin with ComboBox1.Items do begin Add(ComboBox1.Text); SaveToFile(HISTORIAL); end; end; ...


Saludos :)

La verdad es que esto sí que está interesante. Lo voy a probar y les digo. Sencillo, potente...
Gracias.

santiago14
10-09-2013, 14:11:55
santiago14,



Revisa este link:
Espero sea útil :)

Nelson.
Gracias, voy a revisarlo y les cuento.

ecfisa
10-09-2013, 21:12:34
Hola.

Mirando el código anterior, que hice al vuelo y sin mucha prueba :o, veo que se le puede hacer una mejora significativa:

procedure TForm1.ComboBox1Exit(Sender: TObject);
begin
with ComboBox1 do
begin
if Items.IndexOf(Text) = - 1 then
begin
Items.Add(ComboBox1.Text);
Items.SaveToFile(HISTORIAL);
end;
end;
end;

De este modo se evita el guardado de ítems duplicados y por consiguiente el crecimiento innecesario del archivo de texto.

Saludos :)

santiago14
10-09-2013, 23:31:32
Hola.

Mirando el código anterior, que hice al vuelo y sin mucha prueba :o, veo que se le puede hacer una mejora significativa:

procedure TForm1.ComboBox1Exit(Sender: TObject);
begin
with ComboBox1 do
begin
if Items.IndexOf(Text) = - 1 then
begin
Items.Add(ComboBox1.Text);
Items.SaveToFile(HISTORIAL);
end;
end;
end;

De este modo se evita el guardado de ítems duplicados y por consiguiente el crecimiento innecesario del archivo de texto.

Saludos :)

Gracias compañero, eso lo mejora mucho.
Saludos.

nlsgarcia
11-09-2013, 19:56:52
santiago14,


...estoy buscando la forma de hacer el autocompletado...Si hay una solución mas simple, mejor...


Revisa este código basado en la solución sugerida en el Msg #4 y en el comportamiento de la función SHAutoComplete (Windows API):

unit Unit1;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, StrUtils, ExtCtrls;

type
TComboBox = class(StdCtrls.TComboBox)
private
VK : Boolean;
AuxText : String;
History : String;
FStoredItems : TStringList;
procedure FilterItems;
procedure StoredItemsChange(Sender: TObject);
procedure SetStoredItems(const Value: TStringList);
procedure ComboExit(Sender: TObject);
procedure ComboCloseUp(Sender: TObject);
procedure ComboKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
procedure CNCommand(var Message: TWMCommand); Message CN_COMMAND;
protected
public
constructor Create(Owner: TComponent); override;
destructor Destroy; override;
property StoredItems : TStringList read FStoredItems write SetStoredItems;
end;

type
TForm1 = class(TForm)
ComboBox1 : TComboBox;
Button1 : TButton;
procedure Button1Click(Sender: TObject);
private
public
end;

var
Form1: TForm1;

implementation

{$R *.dfm}

constructor TComboBox.Create(Owner: TComponent);
var
FS : TFileStream;

begin

inherited;

AutoComplete := False;
FStoredItems := TStringList.Create;
FStoredItems.OnChange := StoredItemsChange;
History := 'History.txt';
Self.OnExit := ComboExit;
Self.OnCloseUp := ComboCloseUp;
Self.OnKeyDown := ComboKeyDown;
Self.Sorted := True;
Self.DropDownCount := 30;

if not FileExists(History) then
begin
FS := TFileStream.Create(History, fmCreate);
FS.Free;
end;

Self.StoredItems.LoadFromFile(History);

end;

destructor TComboBox.Destroy;
begin
FStoredItems.Free;
inherited;
end;

procedure TComboBox.CNCommand(var Message: TWMCommand);
begin

inherited;

if Message.NotifyCode = CBN_EDITUPDATE then
FilterItems;

end;

procedure TComboBox.FilterItems;
var
i : Integer;
StartPos, EndPos : Integer;

begin

SendMessage(Handle, CB_GETEDITSEL, WPARAM(@StartPos), LPARAM(@EndPos));

AuxText := Text;

if Text <> '' then
begin

Items.Clear;

for i := 0 to FStoredItems.Count - 1 do
begin
if PosEx(LowerCase(Text), LowerCase(FStoredItems)) > 0 then
Items.Add(FStoredItems[i]);
end;

end
else
Items.Assign(FStoredItems);

SendMessage(Handle, CB_SHOWDROPDOWN, Integer(True), 0);

Text := AuxText;

SendMessage(Handle, CB_SETEDITSEL, 0, MakeLParam(StartPos, EndPos));

end;

procedure TComboBox.StoredItemsChange(Sender: TObject);
begin

if Assigned(FStoredItems) then
FilterItems;

end;

procedure TComboBox.SetStoredItems(const Value: TStringList);
begin
if Assigned(FStoredItems) then
FStoredItems.Assign(Value)
end;

procedure TComboBox.ComboExit(Sender: TObject);
begin

if (Items.IndexOf(Text) = -1) and (Text <> EmptyStr) then
begin
FStoredItems.Add(Text);
FStoredItems.SaveToFile(History);
end;

end;

procedure TComboBox.ComboCloseUp(Sender: TObject);
begin

If (AuxText <> EmptyStr) and (VK = False) then
begin
Text := AuxText;
end;

VK := False;

end;

procedure TComboBox.ComboKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
if (Key = VK_UP) or (Key = VK_DOWN) then
VK := True;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
ShowMessage(ComboBox1.Text);
end;

end.

El código anterior [I]simula de forma básica la función SHAutoComplete en un componente TComboBox modificado, como se muestra en la siguiente imagen:

http://img543.imageshack.us/img543/4535/7667.jpg

El ejemplo esta disponible en el link: http://terawiki.clubdelphi.com/Delphi/Componentes-Funciones/?download=AutoComplete_ComboBox.rar

Espero sea útil :)

Nelson.

ecfisa
11-09-2013, 22:21:57
Buén código Nelson ^\||/

Saludos :)

santiago14
12-09-2013, 00:54:30
Espero sea útil :)
Nelson.

Realmente ha sido muy útil.

Una pregunta. Si quiero decidir yo mismo el nombre del archivo de texto en el cual voy a guardar la lista ¿Cómo hago?
Por ejemplo que quiera tener un archivo de texto para cada combo, y tengo dos combo's.

Saludos.

santiago14
12-09-2013, 01:45:28
Una pregunta. Si quiero decidir yo mismo el nombre del archivo de texto en el cual voy a guardar la lista ¿Cómo hago?
Por ejemplo que quiera tener un archivo de texto para cada combo, y tengo dos combo's.
Saludos.
Bueno, hice esto...

TComboBox = class(StdCtrls.TComboBox)
private
FHistory: String;
...
public
property History : String read FHistory write SetHistory;

...

procedure TComboBox.SetHistory(const Value: String);
var
FS : TFileStream;
begin
FHistory := Value;
if not FileExists(History) then
begin
FS := TFileStream.Create(FHistory, fmCreate);
FS.Free;
end;
Self.StoredItems.LoadFromFile(FHistory);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
ComboBox1.History:='Mi_historial.txt';
end;


Ahora puedo poner el nombre del .txt que yo quiera, y además, cada combo podría tener un archivo de texto particular.
Gracias.

santiago14
12-09-2013, 01:51:37
Que va, faltaría dar una vuelta de tuerca mas para cuando no me envíen un nombre para mi archivo de texto...

nlsgarcia
12-09-2013, 08:14:38
santiago14,


...Una pregunta. Si quiero decidir yo mismo el nombre del archivo de texto en el cual voy a guardar la lista ¿Cómo hago?...


Revisa este código:

unit Unit1;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, StrUtils, ExtCtrls;

type
TComboBox = class(StdCtrls.TComboBox)
private
CtrlLoad : Boolean;
UnFlicker : Boolean;
UpDown : Boolean;
AuxText : String;
FHistoryName : String;
StoredItems : TStringList;
procedure FilterItems;
procedure StoredItemsChange(Sender: TObject);
procedure LoadHistory;
procedure ComboExit(Sender: TObject);
procedure ComboCloseUp(Sender: TObject);
procedure ComboKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
procedure CNCommand(var Message: TWMCommand); Message CN_COMMAND;
protected
public
constructor Create(Owner: TComponent); override;
destructor Destroy; override;
property History : String read FHistoryName write FHistoryName;
end;

type
TForm1 = class(TForm)
ComboBox1 : TComboBox;
Button1 : TButton;
ComboBox2: TComboBox;
ComboBox3: TComboBox;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
public
end;

var
Form1: TForm1;

implementation

{$R *.dfm}

constructor TComboBox.Create(Owner: TComponent);
begin

inherited;

AutoComplete := False;
StoredItems := TStringList.Create;
StoredItems.OnChange := StoredItemsChange;
Self.OnExit := ComboExit;
Self.OnCloseUp := ComboCloseUp;
Self.OnKeyDown := ComboKeyDown;
Self.Sorted := True;
Self.DropDownCount := 30;
UnFlicker := False;
CtrlLoad := False;

end;

destructor TComboBox.Destroy;
begin
StoredItems.Free;
inherited;
end;

procedure TComboBox.LoadHistory;
begin

CtrlLoad := True;

if FHistoryName = EmptyStr then
FHistoryName := 'History.txt';

if FileExists(FHistoryName) then
begin
Self.StoredItems.LoadFromFile(FHistoryName);
SendMessage(Handle, CB_SHOWDROPDOWN, Integer(False), 0);
end;

end;

procedure TComboBox.CNCommand(var Message: TWMCommand);
begin

inherited;

if Message.NotifyCode = CBN_EDITUPDATE then
FilterItems;

end;

procedure TComboBox.FilterItems;
var
i : Integer;
StartPos, EndPos : Integer;

begin

SendMessage(Handle, CB_GETEDITSEL, WPARAM(@StartPos), LPARAM(@EndPos));

AuxText := Text;

if Text <> EmptyStr then
begin

Items.Clear;

for i := 0 to StoredItems.Count - 1 do
begin
if PosEx(LowerCase(Text), LowerCase(StoredItems)) > 0 then
Items.Add(StoredItems);
end;

end
else
Items.Assign(StoredItems);

if UnFlicker then
SendMessage(Handle, CB_SHOWDROPDOWN, Integer(True), 0);

Text := AuxText;

SendMessage(Handle, CB_SETEDITSEL, 0, MakeLParam(StartPos, EndPos));

UnFlicker := True;

end;

procedure TComboBox.StoredItemsChange(Sender: TObject);
begin

if Assigned(StoredItems) then
FilterItems;

end;

procedure TComboBox.ComboExit(Sender: TObject);
begin

if (Items.IndexOf(Text) = -1) and (Text <> EmptyStr) and (FHistoryName <> EmptyStr) and CtrlLoad then
begin
StoredItems.Add(Text);
StoredItems.SaveToFile(FHistoryName);
end;

end;

procedure TComboBox.ComboCloseUp(Sender: TObject);
begin

If (AuxText <> EmptyStr) and (UpDown = False) then
begin
Text := AuxText;
end;

UpDown := False;

end;

procedure TComboBox.ComboKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
if (Key = VK_UP) or (Key = VK_DOWN) then
UpDown := True;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin

if ComboBox1.Text <> EmptyStr then
ShowMessage(ComboBox1.Text);

if ComboBox2.Text <> EmptyStr then
ShowMessage(ComboBox2.Text);

if ComboBox3.Text <> EmptyStr then
ShowMessage(ComboBox3.Text);

end;

procedure TForm1.FormCreate(Sender: TObject);
begin

ComboBox1.History := 'History_1.txt';
ComboBox1.LoadHistory;

ComboBox2.History := 'History_2.txt';
ComboBox2.LoadHistory;

ComboBox3.History := 'History_3.txt';
ComboBox3.LoadHistory;

end;

end.

El código anterior es la Versión 2 del código sugerido en el Msg #10 que permite: [I]Simular de forma básica la función SHAutoComplete en un componente TComboBox modificado.

Mejoras realizadas:

1- Se incluyo la propiedad History que permite asignar un archivo Histórico a un ComboBox.

2- Se incluyo el método LoadHistory que permite cargar el archivo Histórico asociado al ComboBox.

3- Se elimino el efecto de flickering al cargar el ComboBox.

4- Se optimizo el código a nivel general.

Notas:

1- Si se omite el método LoadHistory, el ComboBox Modificado funcionara como un ComboBox estándar.

2- Si se omite la propiedad History, el ComboBox Modificado [I]funcionara con el archivo Histórico por Defecto 'History.txt'.

3- Se debe asignar primero la propiedad History y luego llamar el método LoadHistory, para un correcto funcionamiento del Histórico.

El ejemplo esta disponible en el link: http://terawiki.clubdelphi.com/Delphi/Componentes-Funciones/?download=AutoComplete_ComboBox+V2.rar

Espero sea útil :)

Nelson.

nlsgarcia
12-09-2013, 18:23:59
santiago14,

Continuación del Msg #15:

Revisa este código:

unit Unit1;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, StrUtils, ExtCtrls;

type
TComboBox = class(StdCtrls.TComboBox)
private
CtrlLoad : Boolean;
UnFlicker : Boolean;
UpDown : Boolean;
AuxText : String;
HistoryName : String;
StoredItems : TStringList;
procedure FilterItems;
procedure StoredItemsChange(Sender: TObject);
procedure LoadHistory(FileHistory : String = 'History.txt');
procedure ComboExit(Sender: TObject);
procedure ComboCloseUp(Sender: TObject);
procedure ComboKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
procedure CNCommand(var Message: TWMCommand); Message CN_COMMAND;
protected
public
constructor Create(Owner: TComponent); override;
destructor Destroy; override;
end;

type
TForm1 = class(TForm)
ComboBox1 : TComboBox;
ComboBox2: TComboBox;
ComboBox3: TComboBox;
ComboBox4: TComboBox;
Button1 : TButton;
ComboBox5: TComboBox;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
public
end;

var
Form1: TForm1;

implementation

{$R *.dfm}

constructor TComboBox.Create(Owner: TComponent);
begin

inherited;

AutoComplete := False;
StoredItems := TStringList.Create;
StoredItems.OnChange := StoredItemsChange;
Self.OnExit := ComboExit;
Self.OnCloseUp := ComboCloseUp;
Self.OnKeyDown := ComboKeyDown;
Self.Sorted := True;
Self.DropDownCount := 30;
UnFlicker := False;
CtrlLoad := False;

end;

destructor TComboBox.Destroy;
begin
StoredItems.Free;
inherited;
end;

procedure TComboBox.LoadHistory(FileHistory : String = 'History.txt');
begin

CtrlLoad := True;

HistoryName := FileHistory;

if FileExists(HistoryName) then
begin
Self.StoredItems.LoadFromFile(HistoryName);
SendMessage(Handle, CB_SHOWDROPDOWN, Integer(False), 0);
end;

end;

procedure TComboBox.CNCommand(var Message: TWMCommand);
begin

inherited;

if Message.NotifyCode = CBN_EDITUPDATE then
FilterItems;

end;

procedure TComboBox.FilterItems;
var
i : Integer;
StartPos, EndPos : Integer;

begin

SendMessage(Handle, CB_GETEDITSEL, WPARAM(@StartPos), LPARAM(@EndPos));

AuxText := Text;

if Text <> EmptyStr then
begin

Items.Clear;

for i := 0 to StoredItems.Count - 1 do
begin
if PosEx(LowerCase(Text), LowerCase(StoredItems)) > 0 then
Items.Add(StoredItems);
end;

end
else
Items.Assign(StoredItems);

if UnFlicker then
SendMessage(Handle, CB_SHOWDROPDOWN, Integer(True), 0);

Text := AuxText;

SendMessage(Handle, CB_SETEDITSEL, 0, MakeLParam(StartPos, EndPos));

UnFlicker := True;

end;

procedure TComboBox.StoredItemsChange(Sender: TObject);
begin

if Assigned(StoredItems) then
FilterItems;

end;

procedure TComboBox.ComboExit(Sender: TObject);
begin

if (Items.IndexOf(Text) = -1) and (Text <> EmptyStr) and CtrlLoad then
begin
StoredItems.Add(Text);
StoredItems.SaveToFile(HistoryName);
end;

end;

procedure TComboBox.ComboCloseUp(Sender: TObject);
begin

If (AuxText <> EmptyStr) and (UpDown = False) then
begin
Text := AuxText;
end;

UpDown := False;

end;

procedure TComboBox.ComboKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
if (Key = VK_UP) or (Key = VK_DOWN) then
UpDown := True;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin

if ComboBox1.Text <> EmptyStr then
ShowMessage(ComboBox1.Text);

if ComboBox2.Text <> EmptyStr then
ShowMessage(ComboBox2.Text);

if ComboBox3.Text <> EmptyStr then
ShowMessage(ComboBox3.Text);

if ComboBox4.Text <> EmptyStr then
ShowMessage(ComboBox4.Text);

if ComboBox5.Text <> EmptyStr then
ShowMessage(ComboBox5.Text);

end;

procedure TForm1.FormCreate(Sender: TObject);
begin

ComboBox1.LoadHistory('History_1.txt');

ComboBox2.LoadHistory('History_2.txt');

ComboBox3.LoadHistory('History_3.txt');

ComboBox4.LoadHistory; // History.txt es el archivo Histórico por defecto.

// ComboBox5 no usa el método LoadHistory y por lo tanto se comporta como un ComboBox Estándar

end;

end.

El código anterior es la Versión 3 del código sugerido en el Msg #15 que permite: [I]Simular de forma básica la función SHAutoComplete en un componente TComboBox modificado.

Notas:

1- El método LoadHistory permite definir el archivo histórico asociado al ComboBox Modificado.

2- Si se llama el método LoadHistory sin parámetros, [I]se cargara el archivo Histórico por Defecto 'History.txt'.

3- Si se omite el método LoadHistory, el ComboBox Modificado funcionara como un ComboBox estándar.

4- Se elimino la propiedad History, la cual fue sustituida por el parámetro FileHistory del método LoadHistory.

El ejemplo esta disponible en el link: http://terawiki.clubdelphi.com/Delphi/Componentes-Funciones/?download=AutoComplete_ComboBox+V3.rar

Espero sea útil :)

Nelson.

santiago14
13-09-2013, 23:31:42
Lo que propuso Nelson está realmente bueno.
Haciendo un poco de futurología...

¿No perderá rendimiento cuando el listado se haga medio grande? Para eso había pensado ponerle un botón de "eliminar historial" y volver a empezar...
Si el historial es grande, hay dos momentos que yo veo que pueden ser conflictivos:
1) Cuando se carga el historial, por cada combo, al principio.
2) Cuando se agrega un elemento nuevo al combo. Esto es en realidad sobrescribir el archivo de texto anterior con uno nuevo, que tiene una línea mas.

Por otro lado, sería bueno tener un historial por cada combo ¿sería bueno realmente? y que el archivo de texto se cree automáticamente sin que el usuario lo defina. Cuando el usuario defina explícitamente un archivo de historial, el creado por defecto desaparezca.

Si no es razonable tener un historial por cada combo, la propuesta aquí expuesta es la mas indicada, ya que si no se define un archivo de texto, el combo actúa de manera natural.

Santiago.

nlsgarcia
14-09-2013, 02:10:07
santiago14,


...si no se define un archivo de texto, el combo actúa de manera natural...

Recuerda que en las versiones anteriores (v1, v2 y v3) siempre existió el archivo histórico por defecto 'History.txt', pero si no se llama el método LoadHistory (v2 y v3) el ComboBox Modificado funcionara como un ComboBox estándar.


...¿No perderá rendimiento cuando el listado se haga medio grande?...

...sería bueno tener un historial por cada combo...y que el archivo de texto se cree automáticamente...


Revisa este código:

unit Unit1;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, StrUtils, ExtCtrls;

type
TComboBox = class(StdCtrls.TComboBox)
private
CtrlLoad : Boolean;
UnFlicker : Boolean;
UpDown : Boolean;
AuxText : String;
HistoryName : String;
StoredItems : TStringList;
CountItems : Byte;
procedure FilterItems;
procedure StoredItemsChange(Sender: TObject);
procedure LoadHistory(FileHistory : String = ''; CountHistory : Byte = 10);
procedure ComboExit(Sender: TObject);
procedure ComboCloseUp(Sender: TObject);
procedure ComboKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
procedure CNCommand(var Message: TWMCommand); Message CN_COMMAND;
protected
public
constructor Create(Owner: TComponent); override;
destructor Destroy; override;
end;

type
TForm1 = class(TForm)
ComboBox1 : TComboBox;
ComboBox2: TComboBox;
ComboBox3: TComboBox;
ComboBox4: TComboBox;
Button1 : TButton;
ComboBox5: TComboBox;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
public
end;

var
Form1: TForm1;

implementation

{$R *.dfm}

constructor TComboBox.Create(Owner: TComponent);
begin

inherited;

AutoComplete := False;
StoredItems := TStringList.Create;
StoredItems.OnChange := StoredItemsChange;
Self.OnExit := ComboExit;
Self.OnCloseUp := ComboCloseUp;
Self.OnKeyDown := ComboKeyDown;
UnFlicker := False;
CtrlLoad := False;

end;

destructor TComboBox.Destroy;
begin
StoredItems.Free;
inherited;
end;

procedure TComboBox.LoadHistory(FileHistory : String = ''; CountHistory : Byte = 10);
begin

CtrlLoad := True;

Self.DropDownCount := CountHistory;
CountItems := CountHistory;

if FileHistory = EmptyStr then
HistoryName := 'History_' + Self.Name + '.txt'
else
HistoryName := FileHistory;

if FileExists(HistoryName) then
Self.StoredItems.LoadFromFile(HistoryName);

end;

procedure TComboBox.CNCommand(var Message: TWMCommand);
begin

inherited;

if Message.NotifyCode = CBN_EDITUPDATE then
FilterItems;

end;

procedure TComboBox.FilterItems;
var
i : Integer;
StartPos, EndPos : Integer;

begin

SendMessage(Handle, CB_GETEDITSEL, WPARAM(@StartPos), LPARAM(@EndPos));

AuxText := Text;

if Text <> EmptyStr then
begin

Items.Clear;

for i := 0 to StoredItems.Count - 1 do
begin
if PosEx(LowerCase(Text), LowerCase(StoredItems)) > 0 then
Items.Add(StoredItems[i]);
end;

end
else
Items.Assign(StoredItems);

if UnFlicker then
SendMessage(Handle, CB_SHOWDROPDOWN, Integer(True), 0);

Text := AuxText;

SendMessage(Handle, CB_SETEDITSEL, 0, MakeLParam(StartPos, EndPos));

UnFlicker := True;

end;

procedure TComboBox.StoredItemsChange(Sender: TObject);
begin

if Assigned(StoredItems) then
FilterItems;

end;

procedure TComboBox.ComboExit(Sender: TObject);
var
i : Integer;

begin

if (Items.IndexOf(Text) = -1) and (Text <> EmptyStr) and CtrlLoad then
begin
StoredItems.Insert(0,Text);
for i := StoredItems.Count - 1 downto CountItems do
StoredItems.Delete(i);
StoredItems.SaveToFile(HistoryName);
Self.ItemIndex := 0;
end;

end;

procedure TComboBox.ComboCloseUp(Sender: TObject);
begin

If (AuxText <> EmptyStr) and (UpDown = False) then
begin
Text := AuxText;
end;

UpDown := False;

end;

procedure TComboBox.ComboKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
if (Key = VK_UP) or (Key = VK_DOWN) then
UpDown := True;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
i : Integer;

begin

for i := 0 to ComponentCount - 1 do
begin
if Components[i] is TComboBox then
begin
if TComboBox(Components[i]).Text <> EmptyStr then
ShowMessage(TComboBox(Components[i]).Text);
end;
end;

end;

procedure TForm1.FormCreate(Sender: TObject);
begin

// Archivo Histórico definido por el usuario y por defecto 10 items por archivo
ComboBox1.LoadHistory('History_1.txt');

// Archivo Histórico definido por el usuario y 12 items por archivo
ComboBox2.LoadHistory('History_2.txt',12);

// Archivo Histórico definido por defecto ('History_' + Self.Name + '.txt') y 10 items por archivo
ComboBox3.LoadHistory;

// Archivo Histórico definido por defecto ('History_' + Self.Name + '.txt') y 5 items por archivo
ComboBox4.LoadHistory('',5);

// ComboBox5 no usa el método LoadHistory y por lo tanto se comporta como un Combobox Estándar

end;

end.

El código anterior es la Versión 4 del código sugerido en el Msg #16 que permite: [I]Simular de forma básica la función SHAutoComplete en un componente TComboBox modificado.

Notas:

1- El método LoadHistory permite definir el Archivo Histórico y su Cantidad Máxima de Items, asociado al ComboBox Modificado.

2- Si se llama el método LoadHistory sin parámetros, se creara por defecto el archivo 'History_' + Self.Name + '.txt' con un límite máximo de 10 items.

3- Los Items ahora son Insertados al principio de la lista en lugar de Adicionados al final de esta, por lo tanto esta será de reiniciación cíclica en función del número máximo de items por archivo definidos explicita o implícitamente en el método LoadHistory.

4- El número máximo de Items por archivo histórico es 255.

5- Si se omite el método LoadHistory, el ComboBox Modificado funcionara como un ComboBox estándar.

El ejemplo esta disponible en el link: http://terawiki.clubdelphi.com/Delphi/Componentes-Funciones/?download=AutoComplete_ComboBox+V4.rar

Espero sea útil :)

Nelson.

RedVenom
05-10-2013, 17:50:47
Y este método seria posible implementarlo pero alimentado desde una base de datos en lugar de un txt?? y además se podría hacer algo como lo hace google que si tu pones un nombre te aparece en la lista todo lo que lleve ese nombre no importando en que parte del texto este ese nombre.

nlsgarcia
05-10-2013, 18:26:47
RedVenom,


...¿Y este método seria posible implementarlo pero alimentado desde una base de datos en lugar de un txt?...


Con las adaptaciones necesarias propias de una BD, si es posible.

Espero sea útil :)

Nelson.

santiago14
17-03-2014, 13:35:22
santiago14,


Recuerda que en las versiones anteriores (v1, v2 y v3) siempre existió el archivo histórico por defecto 'History.txt', pero si no se llama el método LoadHistory (v2 y v3) el ComboBox Modificado funcionara como un ComboBox estándar.



Revisa este código:

unit Unit1;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, StrUtils, ExtCtrls;

type
TComboBox = class(StdCtrls.TComboBox)
private
CtrlLoad : Boolean;
UnFlicker : Boolean;
UpDown : Boolean;
AuxText : String;
HistoryName : String;
StoredItems : TStringList;
CountItems : Byte;
procedure FilterItems;
procedure StoredItemsChange(Sender: TObject);
procedure LoadHistory(FileHistory : String = ''; CountHistory : Byte = 10);
procedure ComboExit(Sender: TObject);
procedure ComboCloseUp(Sender: TObject);
procedure ComboKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
procedure CNCommand(var Message: TWMCommand); Message CN_COMMAND;
protected
public
constructor Create(Owner: TComponent); override;
destructor Destroy; override;
end;

type
TForm1 = class(TForm)
ComboBox1 : TComboBox;
ComboBox2: TComboBox;
ComboBox3: TComboBox;
ComboBox4: TComboBox;
Button1 : TButton;
ComboBox5: TComboBox;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
public
end;

var
Form1: TForm1;

implementation

{$R *.dfm}

constructor TComboBox.Create(Owner: TComponent);
begin

inherited;

AutoComplete := False;
StoredItems := TStringList.Create;
StoredItems.OnChange := StoredItemsChange;
Self.OnExit := ComboExit;
Self.OnCloseUp := ComboCloseUp;
Self.OnKeyDown := ComboKeyDown;
UnFlicker := False;
CtrlLoad := False;

end;

destructor TComboBox.Destroy;
begin
StoredItems.Free;
inherited;
end;

procedure TComboBox.LoadHistory(FileHistory : String = ''; CountHistory : Byte = 10);
begin

CtrlLoad := True;

Self.DropDownCount := CountHistory;
CountItems := CountHistory;

if FileHistory = EmptyStr then
HistoryName := 'History_' + Self.Name + '.txt'
else
HistoryName := FileHistory;

if FileExists(HistoryName) then
Self.StoredItems.LoadFromFile(HistoryName);

end;

procedure TComboBox.CNCommand(var Message: TWMCommand);
begin

inherited;

if Message.NotifyCode = CBN_EDITUPDATE then
FilterItems;

end;

procedure TComboBox.FilterItems;
var
i : Integer;
StartPos, EndPos : Integer;

begin

SendMessage(Handle, CB_GETEDITSEL, WPARAM(@StartPos), LPARAM(@EndPos));

AuxText := Text;

if Text <> EmptyStr then
begin

Items.Clear;

for i := 0 to StoredItems.Count - 1 do
begin
if PosEx(LowerCase(Text), LowerCase(StoredItems)) > 0 then
Items.Add(StoredItems[i]);
end;

end
else
Items.Assign(StoredItems);

if UnFlicker then
SendMessage(Handle, CB_SHOWDROPDOWN, Integer(True), 0);

Text := AuxText;

SendMessage(Handle, CB_SETEDITSEL, 0, MakeLParam(StartPos, EndPos));

UnFlicker := True;

end;

procedure TComboBox.StoredItemsChange(Sender: TObject);
begin

if Assigned(StoredItems) then
FilterItems;

end;

procedure TComboBox.ComboExit(Sender: TObject);
var
i : Integer;

begin

if (Items.IndexOf(Text) = -1) and (Text <> EmptyStr) and CtrlLoad then
begin
StoredItems.Insert(0,Text);
for i := StoredItems.Count - 1 downto CountItems do
StoredItems.Delete(i);
StoredItems.SaveToFile(HistoryName);
Self.ItemIndex := 0;
end;

end;

procedure TComboBox.ComboCloseUp(Sender: TObject);
begin

If (AuxText <> EmptyStr) and (UpDown = False) then
begin
Text := AuxText;
end;

UpDown := False;

end;

procedure TComboBox.ComboKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
if (Key = VK_UP) or (Key = VK_DOWN) then
UpDown := True;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
i : Integer;

begin

for i := 0 to ComponentCount - 1 do
begin
if Components[i] is TComboBox then
begin
if TComboBox(Components[i]).Text <> EmptyStr then
ShowMessage(TComboBox(Components[i]).Text);
end;
end;

end;

procedure TForm1.FormCreate(Sender: TObject);
begin

// Archivo Histórico definido por el usuario y por defecto 10 items por archivo
ComboBox1.LoadHistory('History_1.txt');

// Archivo Histórico definido por el usuario y 12 items por archivo
ComboBox2.LoadHistory('History_2.txt',12);

// Archivo Histórico definido por defecto ('History_' + Self.Name + '.txt') y 10 items por archivo
ComboBox3.LoadHistory;

// Archivo Histórico definido por defecto ('History_' + Self.Name + '.txt') y 5 items por archivo
ComboBox4.LoadHistory('',5);

// ComboBox5 no usa el método LoadHistory y por lo tanto se comporta como un Combobox Estándar

end;

end.

El código anterior es la Versión 4 del código sugerido en el Msg #16 que permite: [I]Simular de forma básica la función SHAutoComplete en un componente TComboBox modificado.

Notas:

1- El método LoadHistory permite definir el Archivo Histórico y su Cantidad Máxima de Items, asociado al ComboBox Modificado.

2- Si se llama el método LoadHistory sin parámetros, se creara por defecto el archivo 'History_' + Self.Name + '.txt' con un límite máximo de 10 items.

3- Los Items ahora son Insertados al principio de la lista en lugar de Adicionados al final de esta, por lo tanto esta será de reiniciación cíclica en función del número máximo de items por archivo definidos explicita o implícitamente en el método LoadHistory.

4- El número máximo de Items por archivo histórico es 255.

5- Si se omite el método LoadHistory, el ComboBox Modificado funcionara como un ComboBox estándar.

El ejemplo esta disponible en el link: http://terawiki.clubdelphi.com/Delphi/Componentes-Funciones/?download=AutoComplete_ComboBox+V4.rar

Espero sea útil :)

Nelson.

Vuelvo al ruedo con este tema después de tanto tiempo.
Resulta que está muy bueno y ahora quiero ponerlo en una Unit como una clase para poder usarlo siempre y en diversas aplicaciones.
Lo puse en una Unit particular (oAutocompletar.pas) y al momento de compilar me dice: [error] File not found oAutocompletar.dfm

¿Qué está faltando?

Gracias.

santiago14
17-03-2014, 15:46:38
Muestro lo que hice yo, de seguro este es el problema....:)


unit oAutocompletarCombo;

interface

uses
StrUtils, Classes, Messages, Controls, SysUtils, Windows, StdCtrls;

type
TComboBoxAutocomplete = class(TComboBox)
private
CtrlLoad : Boolean;
UnFlicker : Boolean;
UpDown : Boolean;
AuxText : String;
HistoryName : String;
StoredItems : TStringList;
CountItems : Byte;
procedure FilterItems;
procedure CNCommand(var Message: TWMCommand); Message CN_COMMAND;
public
procedure StoredItemsChange(Sender: TObject);
procedure LoadHistory(FileHistory : String = ''; CountHistory : Byte = 10);
procedure ComboExit(Sender: TObject);
procedure ComboCloseUp(Sender: TObject);
procedure ComboKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
constructor Create(Owner: TComponent); override;
destructor Destroy; override;
end;

implementation

{$R *.dfm}

constructor TComboBoxAutocomplete.Create(Owner: TComponent);
begin
inherited;
AutoComplete := False;
StoredItems := TStringList.Create;
StoredItems.OnChange := StoredItemsChange;
Self.OnExit := ComboExit;
Self.OnCloseUp := ComboCloseUp;
Self.OnKeyDown := ComboKeyDown;
UnFlicker := False;
CtrlLoad := False;
end;

destructor TComboBoxAutocomplete.Destroy;
begin
StoredItems.Free;
inherited;
end;

procedure TComboBoxAutocomplete.LoadHistory(FileHistory : String = ''; CountHistory : Byte = 10);
begin
CtrlLoad := True;
Self.DropDownCount := CountHistory;
CountItems := CountHistory;
if FileHistory = EmptyStr then
HistoryName := 'History_' + Self.Name + '.txt'
else
HistoryName := FileHistory;
if FileExists(HistoryName) then
Self.StoredItems.LoadFromFile(HistoryName);
end;

procedure TComboBoxAutocomplete.CNCommand(var Message: TWMCommand);
begin
inherited;
if Message.NotifyCode = CBN_EDITUPDATE then
FilterItems;
end;

procedure TComboBoxAutocomplete.FilterItems;
var
i : Integer;
StartPos, EndPos : Integer;
begin
SendMessage(Handle, CB_GETEDITSEL, WPARAM(@StartPos), LPARAM(@EndPos));
AuxText := Text;
if Text <> EmptyStr then
begin
Items.Clear;
for i := 0 to StoredItems.Count - 1 do
begin
if PosEx(LowerCase(Text), LowerCase(StoredItems[i])) > 0 then
Items.Add(StoredItems[i]);
end;
end
else
Items.Assign(StoredItems);
if UnFlicker then
SendMessage(Handle, CB_SHOWDROPDOWN, Integer(True), 0);
Text := AuxText;
SendMessage(Handle, CB_SETEDITSEL, 0, MakeLParam(StartPos, EndPos));
UnFlicker := True;
end;

procedure TComboBoxAutocomplete.StoredItemsChange(Sender: TObject);
begin
if Assigned(StoredItems) then
FilterItems;
end;

procedure TComboBoxAutocomplete.ComboExit(Sender: TObject);
var
i : Integer;
begin
if (Items.IndexOf(Text) = -1) and (Text <> EmptyStr) and CtrlLoad then
begin
StoredItems.Insert(0,Text);
for i := StoredItems.Count - 1 downto CountItems do
StoredItems.Delete(i);
StoredItems.SaveToFile(HistoryName);
Self.ItemIndex := 0;
end;
end;

procedure TComboBoxAutocomplete.ComboCloseUp(Sender: TObject);
begin
If (AuxText <> EmptyStr) and (UpDown = False) then
begin
Text := AuxText;
end;
UpDown := False;
end;

procedure TComboBoxAutocomplete.ComboKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
if (Key = VK_UP) or (Key = VK_DOWN) then
UpDown := True;
end;

end.

ecfisa
18-03-2014, 00:55:48
...
Lo puse en una Unit particular (oAutocompletar.pas) y al momento de compilar me dice: [error] File not found oAutocompletar.dfm

¿Qué está faltando?

Hola santiago14.

En realidad la pregunta es ¿ Qué está sobrando ? :)

Quitá la línea:

{$R *.dfm}


Saludos :)

santiago14
18-03-2014, 12:31:42
Gracias ecfisa, me sobraba la directiva de compilación....

Santiago.

santiago14
19-03-2014, 16:39:10
Funciona muy bien el comboBox con el autocompletado.
Resulta que ahora, mientras estoy escribiendo dentro del Combo "desaparece" la flecha del mouse. La única forma de recuperarlo es haciendo click en algún lugar del formulario que contiene el combo.
Si bien no es un gran defecto, para el usuario final que usa esto, le es molesto.

Espero poder encontrarle la vuelta.

Santiago.

nlsgarcia
19-03-2014, 19:36:14
santiago14,


...mientras estoy escribiendo dentro del Combo "desaparece" la flecha del mouse. La única forma de recuperarlo es haciendo click en algún lugar del formulario...
Puedes recuperar durante la edición del ComboBox, la flecha del Mouse actualmente activa presionado la tecla Esc.

Espero sea útil :)

Nelson.