6

如果 DataSet 包含作为时间戳或其他二进制值的列,则在显示该列中的任何数据时,其关联的 DataGridView 将引发 ArgumentException。也就是说,假设您有一些包含二进制列的表,例如:

CREATE TABLE [dbo].[DataTest](
    [IdStuff] INT IDENTITY(1,1) NOT NULL,
    [ProblemColumn] TIMESTAMP NOT NULL )

在 Visual Studio 2008 中,添加一个指向可疑表的新数据源。将表格从数据源资源管理器拖到新 WinForm 的可视化设计器表面上,以自动创建 DataGridView、BindingSource 等。执行应用程序,您将获得运行时异常。听起来像个缺陷,对吧?

如果您检查 DataGridView 的 Columns 集合,您会发现它将列类型设置为 DataGridViewImageColumn。为什么?因为,根据微软的说法,.NET 假定二进制列是图像。事实上,微软确认这种行为是设计使然!请参阅 Microsoft Connect 上的此缺陷报告:http ://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=93639

正如对话框礼貌地指出的那样,可以通过处理 DataGridView 的 DataError 事件来抑制错误对话框,但这引出了问题。我想找到一种方法来避免首先出现错误情况。也就是说,我想要一个 DataGridViewTextColumn 显示二进制数据的文本表示,例如“0x1234a8e9433bb2”。我正在寻找一个通用的解决方案,因为我的实际代码没有使用上面示例中的特定表。相反,我将一些任意查询放入 dataAdapter.SelectCommand,然后调用

dataAdapter.Fill(dataTable)

自动生成我的数据表。由于 DataGridView 有(恕我直言)错误,我想我需要检查数据表的列(即 dataTable.Columns[n].DataType.Name.Equals("Byte[]") ?)并在我将 dataTable 连接到 DataGridView 之前手动将任何字节数组转换为它们的文本形式

bindingSource.DataSource = dataTable;

那我的问题:

有没有更简单或更优雅的方式在 DataGridView 中显示二进制列?

(请注意,VS 2005 和 VS 2008、.NET 2.0 和 .NET 3.5 都存在此问题。)

4

4 回答 4

7

对上述方法进行一些改进。#1 处理空二进制列,#2 在转换大量列时提高性能(一遍又一遍地使用相同的字符串生成器),#3 最大显示长度为 8000,以避免将非常大的二进制列转换为字符串... #4 创建临时列使用 guid 命名以避免名称冲突,以防有一个名为“temp”的列...

/// <summary>
/// Maximum length of binary data to display (display is truncated after this length)
/// </summary>
const int maxBinaryDisplayString = 8000;

/// <summary>
/// Accepts datatable and converts all binary columns into textual representation of a binary column
/// For use when display binary columns in a DataGridView
/// </summary>
/// <param name="t">Input data table</param>
/// <returns>Updated data table, with binary columns replaced</returns>
private DataTable FixBinaryColumnsForDisplay(DataTable t)
{
    List<string> binaryColumnNames = t.Columns.Cast<DataColumn>().Where(col => col.DataType.Equals(typeof(byte[]))).Select(col => col.ColumnName).ToList();
    foreach (string binaryColumnName in binaryColumnNames)
    {
        // Create temporary column to copy over data
        string tempColumnName = "C" + Guid.NewGuid().ToString();
        t.Columns.Add(new DataColumn(tempColumnName, typeof(string)));
        t.Columns[tempColumnName].SetOrdinal(t.Columns[binaryColumnName].Ordinal);

        // Replace values in every row
        StringBuilder hexBuilder = new StringBuilder(maxBinaryDisplayString * 2 + 2);
        foreach (DataRow r in t.Rows)
        {
            r[tempColumnName] = BinaryDataColumnToString(hexBuilder, r[binaryColumnName]);
        }

        t.Columns.Remove(binaryColumnName);
        t.Columns[tempColumnName].ColumnName = binaryColumnName;
    }
    return t;
}
/// <summary>
/// Converts binary data column to a string equivalent, including handling of null columns
/// </summary>
/// <param name="hexBuilder">String builder pre-allocated for maximum space needed</param>
/// <param name="columnValue">Column value, expected to be of type byte []</param>
/// <returns>String representation of column value</returns>
private string BinaryDataColumnToString(StringBuilder hexBuilder, object columnValue)
{
    const string hexChars = "0123456789ABCDEF";
    if (columnValue == DBNull.Value)
    {
        // Return special "(null)" value here for null column values
        return "(null)";
    }
    else
    {
        // Otherwise return hex representation
        byte[] byteArray = (byte[])columnValue;
        int displayLength = (byteArray.Length > maxBinaryDisplayString) ? maxBinaryDisplayString : byteArray.Length;
        hexBuilder.Length = 0;
        hexBuilder.Append("0x");
        for(int i = 0; i<displayLength; i++)
        {
            hexBuilder.Append(hexChars[(int)byteArray[i] >> 4]);
            hexBuilder.Append(hexChars[(int)byteArray[i] % 0x10]);
        }
        return hexBuilder.ToString();
    }
}
于 2012-12-17T17:30:58.283 回答
4

在 Quandary 的回答的推动下,加上在发布我的问题后留出了足够的时间来获得新的视角:-),我以下面的方法为幌子想出了一个相当干净的解决方案MorphBinaryColumns,嵌入到一个完整的示例测试程序中(除了VS 的设计器从我的 WinForm 生成了包含单个 DataGridView 的代码)。

MorphBinaryColumns 检查列集合,并为每个二进制列生成一个新列,并将值转换为十六进制字符串,然后将原始列替换为新列,保留原始列顺序。

public partial class Form1 : Form
{
  public Form1()
  {
    InitializeComponent();
  }

  private void Form1_Load(object sender, EventArgs e)
  {
    var sqlCnn = new SqlConnection("..."); // fill in your connection string
    string strsql = "select ... from ..."; // fill in your query

    var dataAdapter = new SqlDataAdapter();
    var dataTable = new DataTable();
    dataAdapter.SelectCommand = new SqlCommand(strsql, sqlCnn);
    dataAdapter.Fill(dataTable);
    MorphBinaryColumns(dataTable);
    dataGridView1.DataSource = dataTable;
  }

  private void MorphBinaryColumns(DataTable table)
  {
    var targetNames =  table.Columns.Cast<DataColumn>()
      .Where(col => col.DataType.Equals(typeof(byte[])))
      .Select(col => col.ColumnName).ToList();
    foreach (string colName in targetNames)
    {
      // add new column and put it where the old column was
      var tmpName = "new";
      table.Columns.Add(new DataColumn(tmpName, typeof (string)));
      table.Columns[tmpName].SetOrdinal(table.Columns[colName].Ordinal);

      // fill in values in new column for every row
      foreach (DataRow row in table.Rows)
      {
        row[tmpName] = "0x" + string.Join("",
          ((byte[]) row[colName]).Select(b => b.ToString("X2")).ToArray());
      }

      // cleanup
      table.Columns.Remove(colName);
      table.Columns[tmpName].ColumnName = colName;
    }
  }
}
于 2011-07-20T18:34:40.497 回答
1

您可能会发现这很有用: http ://social.msdn.microsoft.com/Forums/en/winformsdatacontrols/thread/593606df-0bcb-49e9-8e55-497024699743

基本上:

  • 从数据库中获取数据到数据表
  • 然后添加一个新列(typeof(string))
  • 然后将二进制内容(使用 bytearray 到十六进制字符串)写入该新列

  • 然后数据绑定。

它简单而烦人,但它有效地解决了问题。

于 2011-07-20T11:32:47.443 回答
0

将您的查询基于为该列执行 CAST 的视图怎么样?

于 2009-12-08T21:12:08.873 回答