【问题标题】:Overcoming .NET problem of displaying binary columns in a DataGridView克服在 DataGridView 中显示二进制列的 .NET 问题
【发布时间】:2009-12-08 21:08:54
【问题描述】:

如果 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 都存在此问题。)

【问题讨论】:

    标签: sql-server visual-studio-2008 datagridview binary-data


    【解决方案1】:

    对上述方法进行了一些改进。 #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】:

      在 Quandary 的回答的推动下,加上在发布我的问题后留出了足够的时间来获得全新的视角 :-),我以下面的 MorphBinaryColumns 方法为幌子想出了一个相当干净的解决方案,嵌入在一个完整的示例测试程序(except 用于 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;
          }
        }
      }
      

      【讨论】:

        【解决方案3】:

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

        基本上:

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

        • 然后数据绑定。

        简单又烦人,却能有效解决问题。

        【讨论】:

        • 基本上,您的总结简明扼要地重申了我在问题中所说的内容。尽管如此,您的意见促使我重新审视这个问题并提出一个通用的解决方案,所以我给你一个赞成票。谢谢!
        • 基本上,您是对的,因为我只阅读了上半部分。对此感到抱歉。但是,您想在 GridView 本身中执行此操作,这有点过于复杂。但无论如何,为那段非常好的代码 +1。
        【解决方案4】:

        如何将您的查询基于为该列执行 CAST 的视图?

        【讨论】:

        • 两个原因:(1)我认为没有办法在查询中使用 CAST 来做到这一点。微软展示了可能是最简单的方法,需要一个存储过程;请参阅support.microsoft.com/kb/104829 (2) 我希望它对输入任意查询字符串的用户是透明的。
        猜你喜欢
        • 1970-01-01
        • 2014-03-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-03-12
        • 2011-04-17
        • 2021-09-28
        相关资源
        最近更新 更多