8

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) ?

philnext
  • 3,242
  • 5
  • 39
  • 62
  • There are 3 ways to do this. 1. Create a calculated field for the password on the dataset used by the dbgrid. 2. Create a calculated field for the password on the sql select statement. or 3. use onDrawColumCell/Data event of the dbgrid, like you stated above. But I personally like NOT to store the password in the database, but the hash-coded version of the password. Because hash code is a one-way function (i.e. cant get the original password from the hash-code), and only the correct password can produce the same hash-code, it is safer to use.] – Hendra Sep 20 '12 at 12:24
  • @Hendra Sure, Password was an example, my needs are too complicated to clearly explain. But I know how to change presentation of a cell, with onDrawColumCell, but not the text content, any example or tuto ? – philnext Sep 20 '12 at 16:02

4 Answers4

12

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.

Hendra
  • 720
  • 4
  • 8
5

I would write an OnGetText for the password field in my dataset, as the field's value should not be displayed in any control at all

iMan Biglari
  • 4,674
  • 1
  • 38
  • 83
  • Sure but my question was not complete (sorry), my DBGrids are dynamically created and I may have different TFields so as I can't assign OnGetText. I considere OnDrawColumnCell instead. – philnext Sep 19 '12 at 16:18
  • I have never tried that. I guess you could use this: `with (Sender as TDBGrid).Canvas do begin Font.Color := clWhite; Brush.Color := clWhite; Brush.Style := bsSolid; end;` to hide the value you want, but I'm not sure how to change a field's actual text there. Wouldn't it be easier to assign one `OnGetText` to several `TField`s? – iMan Biglari Sep 19 '12 at 16:23
  • Now I see why you can't use `OnGetText`, but you can easily assign the same color to `font` and `brush` in `OnDrawColumnCell` for your _password_ column. – iMan Biglari Sep 19 '12 at 16:25
  • Yes it's true but password is just an example, I want to apply a function to the shown value, it may be a mask for password, but it may be Uppercase or anythink else. – philnext Sep 19 '12 at 16:32
  • Then you are better to look for a way to modify a field's `OnGetText`. Haven't done it, but maybe you can refresh `FieldsDef` after opening your dataset, and look for your fields there and assign `OnGetText` at run time – iMan Biglari Sep 19 '12 at 16:36
  • 2
    @phil - You can assign an event handler to dynamically created objects. `MyField.OnGetText:=PassFieldGetText` where PassFieldGetText is a procedure of some class with the signature `(Sender: TField; var Text: string; DisplayText: Boolean);` – Sertac Akyuz Sep 19 '12 at 17:12
2

Do you have to mask all the values in an entire column? In that case, if you know what TField (or fieldname) to do this for: try dynamically creating a calculated field with the modified values and display that in the column.

Jan Doggen
  • 8,799
  • 13
  • 70
  • 144
  • I don't have the time to work out an example. At the moment you create the TFields that needs masking, also do TFields.Add to add a calculated field. Go through the TDBGrid.Columns, replace the field used by that column. You also have to hook an OnCalcFields event handler to your dataset of course (maybe you already have one). Hope this gets you started. – Jan Doggen Sep 20 '12 at 09:19
1

I modify the above code to show and hide the password. If the user clicks on the Password cell it will show it, when they click off the cell it will hide it again.

// Add a cell click event from the TDBGrid
procedure TForm1.DBGrid1CellClick(Column: TColumn);
begin
if DBGrid1.SelectedField.FieldName = 'password' then
Edit1.Text := Your_Table_Name.FieldByName('password').AsString;
Edit1.PasswordChar:=#0;
end;

// Change the edit1change event to this
procedure TForm1.Edit1Change(Sender: TObject);
begin
if DBGrid1.DataSource.State in [dsEdit, dsInsert] then
Your_Table_Name.FieldByName('password').AsString := Edit1.Text;
Edit1.PasswordChar:=#0;
end;

// You should change colexit event to read like this
procedure TForm1.DBGrid1ColExit(Sender: TObject);
begin
if DBGrid1.SelectedField.FieldName = 'password' then
Edit1.Visible := False;
end; 

Did not take much work to make it into a cool password field.

Forgot one thing on the DBGrid Draw Column Cell event, you should change Edit1.Clear; to Edit1.Text := Your_Table_Name.FieldByName('Password').AsString;