Преодоление проблемы .NET с отображением двоичных столбцов в DataGridView
-
18-09-2019 - |
Вопрос
Если набор данных содержит столбец, представляющий собой временную метку или другое двоичное значение, связанный с ним DataGridView выдает исключение ArgumentException при отображении любых данных в этом столбце.То есть предположим, что у вас есть некоторая таблица, содержащая двоичный столбец, такой как:
CREATE TABLE [dbo].[DataTest](
[IdStuff] INT IDENTITY(1,1) NOT NULL,
[ProblemColumn] TIMESTAMP NOT NULL )
В Visual Studio 2008 добавьте новый источник данных, указывающий на подозрительную таблицу.Перетащите таблицу из обозревателя источников данных на поверхность визуального конструктора новой WinForm, чтобы автоматически создать DataGridView, BindingSource и т.д.Запустите приложение, и вы получите исключение во время выполнения.Звучит как дефект, не так ли?
Если вы изучите коллекцию Columns DataGridView, вы обнаружите, что она устанавливает тип столбца в DataGridViewImageColumn.Почему?Потому что, согласно Microsoft, .NET предполагает, что двоичные столбцы являются изображениями.Действительно, Microsoft утверждает, что такое поведение является преднамеренным!Смотрите этот отчет о дефекте в Microsoft Connect: http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx ?Идентификатор обратной связи=93639
Можно было бы подавить диалоговое окно с ошибкой, обработав событие DataError для DataGridView, как вежливо указано в диалоговом окне, но это вызывает вопрос.Я хочу найти способ избежать возникновения ошибки в первую очередь.То есть я хочу иметь DataGridViewTextColumn, показывающий текстовое представление двоичных данных, например"0x1234a8e9433bb2".И я ищу общее решение, поскольку мой фактический код не использует конкретную таблицу, как в моем примере выше.Скорее я помещаю несколько произвольный запрос в DataAdapter.Выберите команду, затем вызовите
dataAdapter.Fill(dataTable)
чтобы автоматически сгенерировать мою таблицу данных.Поскольку именно DataGridView содержит ошибку (IMHO), я думаю, что мне нужно проверить столбцы таблицы данных (т.Е.DataTable.Столбцы[n].Тип данных.Имя.Равно("Байт[]")?) и преобразуйте любые массивы байтов в их текстовые формы вручную, прежде чем я подключу DataTable к DataGridView с помощью
bindingSource.DataSource = dataTable;
Тогда мой вопрос:
Есть ли более простой или элегантный способ отображения двоичных столбцов в DataGridView?
(Обратите внимание, что эта проблема существует как с VS 2005, так и с VS 2008, .NET 2.0 и .NET 3.5.)
Решение 2
Подстегнутый ответом 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;
}
}
}
Другие советы
Добавление нескольких улучшений к вышеуказанному подходу.#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();
}
}
Возможно, вы найдете это полезным:http://social.msdn.microsoft.com/Forums/en/winformsdatacontrols/thread/593606df-0bcb-49e9-8e55-497024699743
В основном:
- Получение данных из базы данных в datatable
- затем добавляем новый столбец (typeof(string))
затем запишите двоичное содержимое (используя bytearray в шестнадцатеричную строку) в этот новый столбец
затем привязка к данным.
Это просто и раздражает, но эффективно решает проблему.
Как насчет того, чтобы основывать свой запрос на представлении, которое выполняет CAST для этого столбца?