I have to display some modified 'masked' value in a VCL TDBGrid (Delphi XE2), ie : change 'password' to 'xxxxxxxx' or uppercase 'pass' to 'PASS' or others. As my Fields are dynamically created (but the Name is coded so I know how and when mask them ie : xxxx_PASSW for password Fields) I can't use (I Think) OnGetText event.
So what is the most efficient way to do this (as I yet use OnDrawColumnCell for some presentation modification I would prefere to use it) ?
There are at least 3 ways to do this, I'll illustrate by masking a password field from a database. I'm using sql server for the sql dialect.
1. Define a calculated field on the sql string.
select field1, field2, '********' as maskedPwd from table1;
Then, right-click on the dbgrid, choose the columns editor. Inside the columns editor of the dbgrid, simply select maskedPwd column instead of the real password column. Now the dbgrid will display the masked value instead of the password.
or
2. Define a calculated field on the dataset used by the dbgrid.
Simply right-click on the dataset, and use the fields-editor to create a new calculated field (e.g. maskedPwd2). Then onCalcField event of the dataset, write code to set the value of maskedPwd2, i.e.
procedure TForm1.ADOQuery1CalcFields(DataSet: TDataSet);
begin
DataSet.FieldByName('maskedPwd2').AsString := '********';
end;
Make sure to include maskedPwd2 in the column editor of the dbgrid.
or
3. Write custom text on the onDrawColumnCell event of the dbgrid.
procedure TForm1.DBGrid1DrawColumnCell(Sender: TObject; const Rect: TRect;
DataCol: Integer; Column: TColumn; State: TGridDrawState);
var
grid : TDBGrid;
maskValue : String;
aRect : TRect;
begin
maskValue := '********';
aRect := Rect;
grid := sender as TDBGrid;
if column.FieldName = 'password' then
begin
grid.Canvas.FillRect(Rect);
DrawText(grid.Canvas.Handle, PChar(maskValue), Length(maskValue), aRect,
DT_SINGLELINE or DT_LEFT or DT_VCENTER);
end;
end;
Note that the code above only displaying the masked value, but if the grid is editable, the real password value will be visible when the cell is focused/edited.
To deal with this, drop a TEdit on the form, clear the text property, set PpasswordChar property to '*', and visible to false. Now it is ready to be used as a replacement for the inbuilt editor for the cell. Now, we need some glueing logic, i.e.
procedure TForm1.DBGrid1DrawColumnCell(Sender: TObject; const Rect: TRect;
DataCol: Integer; Column: TColumn; State: TGridDrawState);
var
grid : TDBGrid;
maskValue : String;
aRect : TRect;
begin
maskValue := '********';
aRect := Rect;
grid := sender as TDBGrid;
if column.FieldName = 'password' then
if gdfocused in State then
begin
Edit1.Left := Rect.Left + grid.Left + 1;
Edit1.Top := rect.Top + grid.Top + 1;
Edit1.Width := Rect.Right - Rect.Left + 2;
Edit1.Height := Rect.Bottom - Rect.Top + 2;
Edit1.Clear;
Edit1.Visible := True;
end
else
begin
grid.Canvas.FillRect(Rect);
DrawText(grid.Canvas.Handle, PChar(maskValue), Length(maskValue), aRect,
DT_SINGLELINE or DT_LEFT or DT_VCENTER);
end
else
grid.DefaultDrawColumnCell(Rect, DataCol, Column, state);
end;
procedure TForm1.DBGrid1ColExit(Sender: TObject);
begin
Edit1.Visible := False;
end;
procedure TForm1.DBGrid1KeyPress(Sender: TObject; var Key: Char);
begin
if Key = Chr(9) then Exit;
if (Sender as TDBGrid).SelectedField.FieldName = 'password' then
begin
Edit1.SetFocus;
SendMessage(Edit1.Handle, WM_CHAR, word(Key), 0);
end;
end;
procedure TForm1.Edit1Change(Sender: TObject);
begin
if DBGrid1.DataSource.State in [dsEdit, dsInsert] then
DBGrid1.DataSource.DataSet.FieldByName('password').AsString := Edit1.Text;
end;
procedure TForm1.Edit1Enter(Sender: TObject);
begin
DBGrid1.DataSource.Edit;
end;
Note that the code above is not perfect, yet, but the essence is there. I'll leave it to you for exercise.