代码之家  ›  专栏  ›  技术社区  ›  Michael Sorens

克服.NET在DataGridView中显示二进制列的问题

  •  6
  • Michael Sorens  · 技术社区  · 16 年前

    如果数据集包含的列是时间戳或其他二进制值,则其关联的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集合,您会发现它将Column类型设置为DataGridViewImageColumn。为什么?因为,根据Microsoft,.NET假定二进制列是图像。事实上,微软肯定这种行为是设计出来的!在Microsoft Connect上查看此缺陷报告: http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=93639

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

    dataAdapter.Fill(dataTable)
    

    自动生成数据表。由于是DataGridView有(IMHO)错误,我认为需要检查数据表的列(即dataTable.columns[n].datatype.n am e.equals(“byte[]”)?)并在将数据表连接到DataGridView之前手动将任何字节数组转换为其文本形式

    bindingSource.DataSource = dataTable;
    

    我的问题是:

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

    (请注意,此问题同时存在于2005年和2008年,.NET 2.0和.NET 3.5中。)

    4 回复  |  直到 13 年前
        1
  •  6
  •   Community Mohan Dere    13 年前

    对上述方法进行了一些改进。#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();
        }
    }
    
        2
  •  3
  •   Michael Sorens    15 年前

    在左右为难的回答的激励下,加上在发表我的问题后有足够的时间重新思考问题,我以一种 MorphBinaryColumns 方法,嵌入完整的样本测试程序中( 除了 对于vs的设计器从包含单个DataGridView的WinForm生成的代码)。

    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;
        }
      }
    }
    
        3
  •  1
  •   Stefan Steiger Marco van de Voort    15 年前

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

    基本上:

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

    • 然后数据库。

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

        4
  •  0
  •   Dave Swersky    16 年前

    如何基于为该列执行强制转换的视图进行查询?

    推荐文章