La VCL mette a disposizione tanti controlli pronti, ma capita spesso di aver bisogno di un editor numerico data-aware con vincoli propri: cifre decimali configurabili, valori minimo e massimo, e magari un piccolo bottone di supporto. Costruire un componente su misura, derivato da TPanel, è più semplice di quanto sembri e permette di riusarlo identico in tutti i form del progetto.
La struttura: un TPanel come contenitore
L'idea è creare una classe che eredita da TPanel e che, al proprio interno, ospita un TDBEdit (per restare collegato a un TDataSource) e un piccolo TSpeedButton di servizio:
unit EGNumericEdit;
interface
uses
Windows, Messages, SysUtils, Classes, Controls, Forms,
StdCtrls, ExtCtrls, Buttons, DB, DBCtrls;
type
TDBNumericEdit = class(TPanel)
private
FEdit: TDBEdit;
FButton: TSpeedButton;
FDataSource: TDataSource;
FDataField: string;
FDecimalPlaces: Integer;
FAllowDecimals: Boolean;
FMinValue, FMaxValue: Double;
procedure SetDataSource(const Value: TDataSource);
procedure SetDataField(const Value: string);
procedure EditKeyPress(Sender: TObject; var Key: Char);
procedure EditExit(Sender: TObject);
function GetValue: Double;
procedure SetValue(const Value: Double);
protected
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
procedure Resize; override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
property Value: Double read GetValue write SetValue;
published
property DataSource: TDataSource read FDataSource write SetDataSource;
property DataField: string read FDataField write SetDataField;
property DecimalPlaces: Integer read FDecimalPlaces write FDecimalPlaces default 2;
property AllowDecimals: Boolean read FAllowDecimals write FAllowDecimals default True;
property MinValue: Double read FMinValue write FMinValue;
property MaxValue: Double read FMaxValue write FMaxValue;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('EG Controls', [TDBNumericEdit]);
end;
I sotto-componenti (FEdit e FButton) vengono creati nel costruttore e agganciati con Parent := Self, mentre il posizionamento è gestito sovrascrivendo Resize, così il componente si ridimensiona correttamente quando viene ancorato o ridimensionato nell'IDE.
Validare l'input carattere per carattere
Il controllo più utile di questo tipo di editor è quello sull'evento OnKeyPress: si filtrano i tasti non numerici, si permette il separatore decimale una sola volta e si consente il segno meno solo in prima posizione:
procedure TDBNumericEdit.EditKeyPress(Sender: TObject; var Key: Char);
var
SeparatoreDecimale: Char;
begin
SeparatoreDecimale := FormatSettings.DecimalSeparator;
if not CharInSet(Key, ['0'..'9', #8]) then
begin
if (Key = '.') or (Key = ',') then
begin
if not FAllowDecimals or (Pos(SeparatoreDecimale, FEdit.Text) > 0) then
Key := #0
else
Key := SeparatoreDecimale;
end
else if not ((Key = '-') and (FEdit.SelStart = 0) and (Pos('-', FEdit.Text) = 0)) then
Key := #0;
end;
end;
Uscendo dal campo (OnExit) si applica anche il controllo sul range, riportando il valore entro MinValue e MaxValue se necessario: una validazione "morbida" che evita all'utente di dover correggere manualmente un valore fuori scala.
Un bottone di supporto opzionale
Il codice originale da cui è tratto questo componente include anche una piccola calcolatrice popup, creata a runtime e mostrata in modale quando si preme il bottone accanto all'edit. È una soluzione pratica per un dialogo ausiliario "usa e getta", ma se nel tuo progetto preferisci restare fedele alla convenzione "tutto a design time", conviene trasformarla in una vera form disegnata nell'IDE con il proprio .dfm e invocata con ShowModal: il comportamento resta identico, ma la form diventa modificabile visivamente come qualsiasi altra.
Il vantaggio di impacchettare tutto in un componente registrato con RegisterComponents è che, una volta compilato in un package, TDBNumericEdit compare nella Tool Palette esattamente come i controlli nativi: lo trascini su un form, lo colleghi a un TDataSource e hai già editor, validazione e range check pronti.