如果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)
自动生成我的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都存在此问题。)
答案 0 :(得分:6)
为上述方法添加一些改进。 #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();
}
}
答案 1 :(得分:3)
在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;
}
}
}
答案 2 :(得分:1)
您可能会觉得这很有用: http://social.msdn.microsoft.com/Forums/en/winformsdatacontrols/thread/593606df-0bcb-49e9-8e55-497024699743
基本上:
然后写二进制内容(使用 bytearray到hex string)到新列
然后是数据绑定。
这很简单,很烦人,但它有效地解决了这个问题。
答案 3 :(得分:0)
如何将查询基于对该列执行CAST的视图?