如何在Delphi TDBGrid中修改单元格的值

8
我需要在Delphi XE2的VCL TDBGrid中显示一些修改后的“掩码”值,即:将“password”更改为“xxxxxxxx”,或将大写字母“pass”更改为“PASS”或其他内容。由于我的字段是动态创建的(但名称是编码的,因此我知道何时以及如何将它们掩码,例如xxxx_PASSW用于密码字段),所以我不能使用OnGetText事件。那么最有效的方法是什么?(由于我已经在一些演示文稿修改中使用了OnDrawColumnCell,因此我更喜欢使用它)。

有三种方法可以实现这个功能。1. 在dbgrid使用的数据集上创建一个计算字段来存储密码。2. 在sql select语句中创建一个计算字段来存储密码。或者3.像你之前提到的那样,使用dbgrid的onDrawColumCell/Data事件。但是我个人不喜欢将密码存储在数据库中,而是喜欢将密码的哈希码版本存储在其中。因为哈希码是单向函数(即无法从哈希码获取原始密码),只有正确的密码才能产生相同的哈希码,所以使用哈希码更安全。 - Hendra
@Hendra 当然,密码只是一个例子,我的需求太复杂了,无法清楚地解释。但我知道如何使用onDrawColumCell更改单元格的呈现方式,但不知道如何更改文本内容,有任何示例或教程吗? - philnext
4个回答

12

至少有三种方法可以做到这一点,我将通过屏蔽数据库中的密码字段来说明。我使用的是sql server作为sql方言。

1. 在sql字符串上定义一个计算字段。

select field1, field2, '********' as maskedPwd from table1;

接下来,右键单击dbgrid,选择列编辑器。在dbgrid的列编辑器中,仅选择掩码密码列而非真实密码列。现在,dbgrid将显示掩码值而不是密码。

或者

2. 在dbgrid使用的数据集上定义计算字段。

只需右键单击数据集,并使用字段编辑器创建一个新的计算字段(例如,maskedPwd2)。然后,在数据集的onCalcField事件中编写代码以设置maskedPwd2的值,即

procedure TForm1.ADOQuery1CalcFields(DataSet: TDataSet);
begin
  DataSet.FieldByName('maskedPwd2').AsString := '********';
end;

请务必在dbgrid的列编辑器中包含maskedPwd2。

或者

3. 在dbgrid的onDrawColumnCell事件中编写自定义文本。

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;
请注意,上面的代码仅显示掩码值,但如果网格是可编辑的,则在单元格聚焦/编辑时实际密码值将可见。
为了解决这个问题,在表单上放置一个TEdit,清除文本属性,将PpasswordChar属性设置为“*”,并将其可见性设置为false。现在可以将其用作单元格内置编辑器的替代品。现在,我们需要一些粘合逻辑,即:
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;

注意,上面的代码还不完美,但精髓在那里。练习留给你。


5

我会为我的数据集中的密码字段编写一个 OnGetText,因为该字段的值不应在任何控件中显示。


当然,但我的问题没有完全说明(抱歉),我的DBGrid是动态创建的,我可能有不同的TFields,因此无法分配OnGetText。我考虑使用OnDrawColumnCell代替。 - philnext
我从未尝试过那个。我猜你可以使用这个:with (Sender as TDBGrid).Canvas do begin Font.Color := clWhite; Brush.Color := clWhite; Brush.Style := bsSolid; end; 来隐藏你想要的值,但我不确定如何在那里更改字段的实际文本。将一个 OnGetText 分配给多个 TField 是否更容易呢? - iMan Biglari
现在我明白为什么你不能使用 OnGetText,但是你可以在 OnDrawColumnCell 中轻松地将相同的颜色分配给 password 列的 fontbrush - iMan Biglari
是的,这是真的,但密码只是一个例子,我想对显示的值应用一个函数,它可以是密码的掩码,也可以是大写或其他任何东西。 - philnext
那么你最好寻找一种修改字段的 OnGetText 的方法。虽然我没有尝试过,但也许你可以在打开数据集后刷新 FieldsDef,查找你的字段并在运行时分配 OnGetText - iMan Biglari
2
@phil - 你可以为动态创建的对象分配事件处理程序。MyField.OnGetText:=PassFieldGetText,其中PassFieldGetText是某个类的过程,其签名为(Sender: TField; var Text: string; DisplayText: Boolean); - Sertac Akyuz

2

您是否需要掩盖整个列中的所有值?如果是这样,如果您知道要对哪个TField(或字段名)执行此操作:请尝试动态创建一个带有修改后的值的计算字段,并在该列中显示。


我没有时间编写一个示例。在创建需要掩码的TFields时,同时进行TFields.Add以添加计算字段。遍历TDBGrid.Columns,替换该列使用的字段。当然,您还必须将OnCalcFields事件处理程序挂钩到数据集上(也许您已经有了一个)。希望这能帮助您入门。 - Jan Doggen

1
我修改了上面的代码,可以显示和隐藏密码。如果用户点击密码单元格,它会显示出来,再次点击单元格,它将再次隐藏起来。
// 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; 

这个密码框的制作并不需要太多工作。

在DBGrid绘制列单元格事件中,您应该将Edit1.Clear;更改为Edit1.Text:=Your_Table_Name.FieldByName('Password').AsString;


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接