【问题标题】:Can I use a hidden DataGridView column to manage a SQL Server VarBinary field?我可以使用隐藏的 DataGridView 列来管理 SQL Server VarBinary 字段吗?
【发布时间】:2016-01-14 17:25:49
【问题描述】:

我有一个表,我想在字段中存储一个字节数组。字节数组大约是 20 字节(160 位)的关键数据。

我正在使用多个DataGridView 来管理此表和此应用程序的其他表。我目前有几个例程允许我提供 SQL 选择字符串,DataGridView 允许用户编辑数据。

private void InitializeUsersDataGrid()
{
  string sql = "SELECT UserId, Enabled, AccessLevel, Name, KeyValue FROM Users";

  DataGridViewIntialize(dgvUsers, sql);
  dgvUsers.Columns[fdKeyValue].Visible = false;
}

private void DataGridViewIntialize(DataGridView dataGridView, string sql)
{
  dataGridViewInUse = dataGridView; // This is the current DataGridView
  OleDbConnection oleDbConnection = new OleDbConnection(txtConnectionString.Text);
  dataAdapter = new OleDbDataAdapter(sql, oleDbConnection);
  OleDbCommandBuilder commandBuilder = new OleDbCommandBuilder(dataAdapter);  //Creates SQL commands for IUD

  dataTable = new DataTable();
  dataTable.Locale = System.Globalization.CultureInfo.InvariantCulture;

  dataAdapter.Fill(dataTable);
  bindingSource1.DataSource = dataTable;

  dataGridView.DataSource = bindingSource1;

  // Resize the DataGridView columns to fit the newly loaded content.
  dataGridView.AutoResizeColumns(DataGridViewAutoSizeColumnsMode.AllCells);
  AddDeleteRefreshContextMenu(dataGridView);
}

我有一个保存按钮,它提供验证然后调用TableSave

private void TableSave()
{
  // Update the database with the user's changes.
  try {
    dataAdapter.Update((DataTable)bindingSource1.DataSource);
  }
  catch (Exception ex) {
    MessageBox.Show(ex.Message);
  }
}

当我将varbinary 字段添加到表中时,它在DataGridView 中显示为损坏的位图图像。这一点我并不关心,因为无论如何我都不打算展示它。

当我尝试为调用分配一个新的字节数组时,它会生成一个 DataGridView 默认错误对话框。

/// <summary>
/// Change the password hash for the selected login
/// </summary>
/// <param name="e"></param>
void ChangePassword( DataGridViewCellEventArgs e)
{
  // Request a new password with confirmation
  fPassword form = new fPassword();
  form.Confirm = true;
  DialogResult dr = form.ShowDialog();
  if (dr == DialogResult.OK) {
    // Combine the UserID with the password to generate a new key
    byte[] salt;
    byte[] key;
    string p = dgvUsers.Rows[e.RowIndex].Cells["UserId"].Value + form.Value;
    Data.PBK2DF2Hash.GenerateSaltAndKey(p, out salt, out key);
    if (dgvUsers.Rows[e.RowIndex].Cells[fdKeyValue].ValueType == key.GetType()) {
      // We do get this far Error occurs on the assignment in the next line
      dgvUsers.Rows[e.RowIndex].Cells[fdKeyValue].Value = key;
    } 
  }
}

DataGridView 默认错误对话框

DataGridView 出现以下异常:System.ArgumentException: Parameter is not valid.
在 System.Drawing.Image.FromStream(流流,布尔 使用EmbeddedColorManagement,布尔验证图像数据) 在 System.Drawing.ImageConverter.ConvertFrom(ITypeDescriptorContext 上下文、CultureInfo 文化、对象值) 在 System.Windows.Forms.Formatter.FormatObjectInternal(对象值,类型 targetType, TypeConverter sourceConverter, TypeConverter targetConverter, String formatString, IFormatProvider formatInfo, 对象格式化 NullValue) 在 System.Windows.Forms.Formatter.FormatObject(对象值,类型 targetType, TypeConverter sourceConverter, TypeConverter targetConverter, String formatString, IFormatProvider formatInfo, 对象格式化NullValue,对象dataSourceNullValue) 在 System.Windows.Forms.DataGridViewCell.GetFormattedValue(对象值, Int32 rowIndex, DataGridViewCellStyle& cellStyle, TypeConverter valueTypeConverter, TypeConverter 格式化ValueTypeConverter, DataGridViewDataErrorContexts 上下文)

要替换此默认对话框,请处理 DataError 事件。

似乎与CellFormatting 有关,但我不知道如何将其关闭。

添加这段代码提供了一个带有此错误消息的更简单的对话框:

System.FormatException:单元格的格式化值类型错误。

private void dgvUsers_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
  DataGridViewColumn c = dgvUsers.Columns[fnKeyValue];
  if (c != null) {
    if (e.ColumnIndex == c.Index) {
      if (e.Value != null) {
        e.FormattingApplied = true;
      }
      else
        e.FormattingApplied = false;
    }
  }
}

更新 #1 - 使用 Ivan Stoev 建议方法的附加代码,并显示了我用来请求更改密码的按钮单元。它还增加了保存盐值。

此时的问题是,当我点击更改密码按钮时,我可以打开我的密码对话框,获取密码。生成盐和键,将其保存到该行盐和键值的数据网格单元格中。

如果我执行TableSave()(参见前面的代码),除非我在网格中的不同行上单击鼠标,否则这些字段不在 SQL Server 表中。该操作似乎表明该行已脏,然后保存将起作用。

我还可以在更改密码之前或之后更改用户可见的字段之一,该行将保存。

private void InitializeUsersDataGrid()
{
  string sql = "SELECT UserId, Enabled, AccessLevel, Name, Salt, KeyValue FROM Users";

  AddAccessLevelColumn(dgvUsers);  //Add column if needed
  AddPasswordChangeColumn(dgvUsers);
  DataGridViewIntialize1(dgvUsers, sql);
  dgvUsers.Columns[UsersFieldName.fdAccessLevel].Visible = false;  //Hide the raw column from the database

  MoveAccessLevelColumn(dgvUsers, AccessLevelComboBoxColumn, AccessLevelColumnPosition);
  MoveAccessLevelColumn(dgvUsers, PasswordButtonColumn, PasswordColumnPosition);
}

private void DataGridViewIntialize1(DataGridView dataGridView, string sql)
{
  dataGridViewInUse = dataGridView; // This is the current DataGridView
  OleDbConnection oleDbConnection = new OleDbConnection(txtConnectionString.Text);
  dataAdapter = new OleDbDataAdapter(sql, oleDbConnection);
  OleDbCommandBuilder commandBuilder = new OleDbCommandBuilder(dataAdapter);  //Creates SQL commands for IUD

  dataTable = new DataTable();
  dataTable.Locale = System.Globalization.CultureInfo.InvariantCulture;

  dataAdapter.Fill(dataTable);
  dataTable.Columns[Data.UsersFieldName.fdSalt].ColumnMapping = MappingType.Hidden;
  dataTable.Columns[Data.UsersFieldName.fdKey].ColumnMapping = MappingType.Hidden;
  bindingSource1.DataSource = dataTable;

  dataGridView.DataSource = bindingSource1;

  // Resize the DataGridView columns to fit the newly loaded content.
  dataGridView.AutoResizeColumns(DataGridViewAutoSizeColumnsMode.AllCells);
  AddDeleteRefreshContextMenu(dataGridView);
}

private void AddPasswordChangeColumn(DataGridView dataGridView)
{
  // This column will remain unless manually removed, so it only needs to be added
  // the first time the DVG is initialized.

  if (dataGridView.Columns[PasswordButtonColumn] == null) {
    // creating new ComboBoxCell Column
    DataGridViewButtonColumn btnColumn = new DataGridViewButtonColumn();
    btnColumn.Text = "Change Password";
    btnColumn.HeaderText = "Change Password";
    btnColumn.UseColumnTextForButtonValue = true;
    btnColumn.Name = PasswordButtonColumn;
    btnColumn.FlatStyle = FlatStyle.Popup;
    dataGridView.Columns.Insert(0, btnColumn);
    // Add a CellClick handler to handle clicks in the button column.
    dataGridView.CellClick += dataGridView_CellClick;
  }
}

private void dataGridView_CellClick(object sender, DataGridViewCellEventArgs e)
{
  // Ignore clicks that are not on button cells.
  if (e.RowIndex >= 0 && e.RowIndex < dgvUsers.RowCount - 1 && e.ColumnIndex == dgvUsers.Columns[PasswordButtonColumn].Index) {
    ChangePassword(e);
  }
}

/// <summary>
/// Change the password hash for the selected login
/// </summary>
/// <param name="e"></param>
private void ChangePassword(DataGridViewCellEventArgs e)
{
  // Request a new password with confirmation
  fPassword form = new fPassword();
  form.Confirm = true;
  DialogResult dr = form.ShowDialog();
  if (dr == DialogResult.OK) {
    // Combine the UserID with the password to generate a new key
    byte[] salt;
    byte[] key;
    string p = dgvUsers.Rows[e.RowIndex].Cells[Data.UsersFieldName.fdUserId].Value + form.Value;
    Data.PBK2DF2Hash.GenerateSaltAndKey(p, out salt, out key);
    var gridRow = dgvUsers.Rows[e.RowIndex];
    var dataRow = (DataRowView)gridRow.DataBoundItem;
    var value = dataRow[Data.UsersFieldName.fdKey];
    dataRow[Data.UsersFieldName.fdSalt] = salt;  //This assignment now works
    dataRow[Data.UsersFieldName.fdKey] = key;
  }
}

更新 #2 将 'NotifyCurrectCell' Dirty 添加到 'ChangePassword' 方法会通知网格需要保存单元格而不更改 UI 中的行。

private void ChangePassword(DataGridViewCellEventArgs e)
{
  // Request a new password with confirmation
  fPassword form = new fPassword();
  form.Confirm = true;
  DialogResult dr = form.ShowDialog();
  if (dr == DialogResult.OK) {
    // Combine the UserID with the password to generate a new key
    byte[] salt;
    byte[] key;
    string p = dgvUsers.Rows[e.RowIndex].Cells[Data.UsersFieldName.fdUserId].Value + form.Value;
    Data.PBK2DF2Hash.GenerateSaltAndKey(p, out salt, out key);
    var gridRow = dgvUsers.Rows[e.RowIndex];
    var dataRow = (DataRowView)gridRow.DataBoundItem;
    var value = dataRow[Data.UsersFieldName.fdKey];
    dataRow[Data.UsersFieldName.fdSalt] = salt;  //This assignment now works
    dataRow[Data.UsersFieldName.fdKey] = key;
    dgvUsers.NotifyCurrentCellDirty(true); //Tells the UI that this row has changed and needs updating
  }
}

【问题讨论】:

  • 您的图像表单无效。 ImageFromStream() 必须是有效的图像格式,否则会出现异常。
  • 如何告诉列它不是图像?
  • 数据类型在 System.Data.SqlDbType 中找到。二进制应该可以工作。要指定类型,您必须向 SQL 命令添加一个参数。参数包含数据类型。
  • @jdweng 我尝试添加 CAST,但这通常不起作用。你的意思是别的吗? string sql = "SELECT UserId, Enabled, AccessLevel, Name, CAST(KeyValue AS BINARY) FROM Users
  • 您必须将数据库列设置为二进制。该类型将自动以二进制形式传输到 VS。你不需要演员表。

标签: c# sql-server winforms datagridview


【解决方案1】:

您遇到的问题是因为DataGridView 默认为byte[] 数据类型创建DataGridViewImageColumn

这里有一些选项。

  • A.将DataGridView.AutoGenerateColumns 属性设置为false 并手动创建网格列。

  • B.如果您真的不需要网格中的该列,请不要尝试隐藏网格列,而不要创建网格列。但是如何实现呢?对于类属性,可以使用Browsable(false),但这是DataTable。好吧,虽然没有记录,但可以将DataColumn.ColumnMapping 属性与MappingType.Hidden 一起用于相同的目的。

在你的情况下,删除这一行

dgvUsers.Columns[fdKeyValue].Visible = false;

DataGridViewIntialize方法里面使用

// ...
dataAdapter.Fill(dataTable);
dataTable.Columns[fdKeyValue].ColumnMapping = MappingType.Hidden;
bindingSource1.DataSource = dataTable;
// ...

您仍然可以使用这样的基础数据源访问您的列数据

var gridRow = dgvUsers.Rows[...];
var dataRow = (DataRowView)gridRow.DataBoundItem;
var value = dataRow[fdKeyValue];

【讨论】:

  • 我必须将 Columns 属性添加到语句中才能访问 ColumnMapping 属性。但这完全将其从网格中删除,因此我无法更改返回的数据。将 Visible 设置为 false 允许数据在内部存在并至少尝试进行更改。
  • 为什么认为如果网格中没有列,数据就不存在了?由于您使用的是数据绑定模式,因此数据包含在数据表中,而不是网格中。使用DataGridViewRow.DataBoundItem 属性,转换为DataRowView,这是您所有列的数据。
  • 当我使用您的语法时,该列存在,但当我使用原始语法时,则不存在。使用您的语法,我可以分配我的数组,它会显示在表格中。 dataRow[fdKeyValue] = key;我的最后一个问题是,只有在写入数据后将光标移动到另一个单元格时,才会将 KeyValue 字段写入表中。否则它不知道该字段是脏的。我可以在行上设置一个标志以便保存吗?
  • 如果您发布一些代码(事件处理程序等),显示您正在尝试使用“您的语法”做什么,我想我们将能够翻译它:)
  • 我添加了一些更新代码(Update #1),进一步解释了当前问题。
猜你喜欢
  • 2016-08-11
  • 2012-02-13
  • 2023-03-08
  • 2011-01-19
  • 2017-02-02
  • 1970-01-01
  • 2020-02-22
  • 2016-12-19
  • 1970-01-01
相关资源
最近更新 更多