如何使用剪贴板将数据从Excel Sheet复制到DataTable?
How to use clipboard to copy data from Excel Sheet to DataTable?
我有一个在 Microsoft Framework 3.5 上创建的 Winform 项目。
用户可能已安装 Windows 7 或 Windows XP,以及 Office 2007 或更高版本。
我正在处理获取剪贴板数据并将其放入 C# DataTable 的过程。
我已经创建了一种从剪贴板获取原始数据并将其上传到 DataTable 的方法。
但在某些情况下,Excel数据显示一个值,但内部还有另一个:
我正在研究一种从 Excel:
获取原始数据的方法
string XmlFmt = "XML Spreadsheet";
var clipboard = Clipboard.GetDataObject();
if (clipboard.GetDataPresent(XmlFmt))
{
var clipData = clipboard.GetData(XmlFmt);
StreamReader streamReader = new StreamReader((MemoryStream)clipData);
streamReader.BaseStream.SetLength(streamReader.BaseStream.Length - 1);
string xmlText = streamReader.ReadToEnd();
var stream = new StringReader(xmlText);
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.LoadXml(xmlText);
DataSet dsExcelData = new DataSet();
dsExcelData.ReadXml(new XmlNodeReader(xmlDocument));
}
但是,此方法为我检索了一个包含多个表的数据集,其中包含 Excel 数据每个部分的配置:
基本上,我想将这些结构转换为仅包含原始数据的简单 DataTable。
有人可以帮我提示如何实现这一目标吗?
...我不想在此实现中使用第三方库。
如果它们是平面数据,你可以这样做。
private class Field
{
public string Valor { get; set; }
}
private class Row
{
public List<Field> Fields { get; set; }
public Row(string value)
{
Fields = new List<Field>();
var fieldsString = value.Split(new char[] {'\t'});
foreach (string f in fieldsString)
{
Fields.Add(new Field {Valor = f});
}
}
}
public Parse()
{
var data = Clipboard.GetDataObject();
var datos = (string)data.GetData(DataFormats.Text);
var stringRows = datos.Split(new Char[] {'\r', '\n'}, StringSplitOptions.RemoveEmptyEntries);
var table = new List<Row>(stringRows.Length) ;
foreach (string stringRow in stringRows)
{
table.Add( new Row(stringRow) );
}
}
in some cases, the Excel data shows a value, but internally have another.
使用XML方法是多数据表内部结构的原因。试试这个方法:
private void PasteFromExcel()
{
DataTable tbl = new DataTable();
tbl.TableName = "ImportedTable";
List<string> data = new List<string>(ClipboardData.Split('\n'));
bool firstRow = true;
if (data.Count > 0 && string.IsNullOrWhiteSpace(data[data.Count - 1]))
{
data.RemoveAt(data.Count - 1);
}
foreach (string iterationRow in data)
{
string row = iterationRow;
if (row.EndsWith("\r"))
{
row = row.Substring(0, row.Length - "\r".Length);
}
string[] rowData = row.Split(new char[] { '\r', '\x09' });
DataRow newRow = tbl.NewRow();
if (firstRow)
{
int colNumber = 0;
foreach (string value in rowData)
{
if (string.IsNullOrWhiteSpace(value))
{
tbl.Columns.Add(string.Format("[BLANK{0}]", colNumber));
}
else if (!tbl.Columns.Contains(value))
{
tbl.Columns.Add(value);
}
else
{
tbl.Columns.Add(string.Format("Column {0}", colNumber));
}
colNumber++;
}
firstRow = false;
}
else
{
for (int i = 0; i < rowData.Length; i++)
{
if (i >= tbl.Columns.Count) break;
newRow[i] = rowData[i];
}
tbl.Rows.Add(newRow);
}
}
DataGridView1.DataSource = tbl;
}
参考:http://www.seesharpdot.net/?p=221
编辑:
我做了一些测试,甚至使用 "XML Spreadsheet" 剪贴板格式,数据可以以指数表示法存储:
您可以检测并转换这些数字:Parse a Number from Exponential Notation
我找到了一个干净且万无一失的解决方案。这里的代码:
首先,将 XmlDocument 转换为 XElement 的扩展:
/// <summary> Convert XML Document to XDocument </summary>
/// <param name="xmlDocument">Attached XML Document</param>
public static XDocument fwToXDocument(this XmlDocument xmlDocument)
{
using (XmlNodeReader xmlNodeReader = new XmlNodeReader(xmlDocument))
{
xmlNodeReader.MoveToContent();
return XDocument.Load(xmlNodeReader);
}
}
完整功能:
private DataTable clipboardExcelToDataTable(bool blnFirstRowHasHeader = false)
{
string strTime = "S " + DateTime.Now.ToString("mm:ss:fff");
var clipboard = Clipboard.GetDataObject();
if (!clipboard.GetDataPresent("XML Spreadsheet")) return null;
strTime += "\r\nRead " + DateTime.Now.ToString("mm:ss:fff");
StreamReader streamReader = new StreamReader((MemoryStream)clipboard.GetData("XML Spreadsheet"));
strTime += "\r\nFinish read " + DateTime.Now.ToString("mm:ss:fff");
streamReader.BaseStream.SetLength(streamReader.BaseStream.Length - 1);
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.LoadXml(streamReader.ReadToEnd());
strTime += "\r\nRead XML Document " + DateTime.Now.ToString("mm:ss:fff");
XNamespace ssNs = "urn:schemas-microsoft-com:office:spreadsheet";
DataTable dtData = new DataTable();
var linqRows = xmlDocument.fwToXDocument().Descendants(ssNs + "Row").ToList<XElement>();
for (int x = 0; x < linqRows.Max(a => a.Descendants(ssNs + "Cell").Count()); x++)
dtData.Columns.Add("Column " + (x + 1).ToString());
int intCol = 0;
DataRow drCurrent;
linqRows.ForEach(rowElement =>
{
intCol = 0;
drCurrent = dtData.Rows.Add();
rowElement.Descendants(ssNs + "Cell")
.ToList<XElement>()
.ForEach(cell => drCurrent[intCol++] = cell.Value);
});
if (blnFirstRowHasHeader)
{
int x = 0;
foreach (DataColumn dcCurrent in dtData.Columns)
dcCurrent.ColumnName = dtData.Rows[0][x++].ToString();
dtData.Rows.RemoveAt(0);
}
strTime += "\r\nF " + DateTime.Now.ToString("mm:ss:fff");
return dtData;
}
该过程需要约 15 秒来读取约 25,000 行。
适用于任何类型的数据。
基本上,该方法会创建一个与 Excel WorkSheet 具有相同结构的网格。
行或列的合并将填满第一个单元格。
默认情况下,所有列都是字符串数据类型。
我有一个在 Microsoft Framework 3.5 上创建的 Winform 项目。 用户可能已安装 Windows 7 或 Windows XP,以及 Office 2007 或更高版本。
我正在处理获取剪贴板数据并将其放入 C# DataTable 的过程。 我已经创建了一种从剪贴板获取原始数据并将其上传到 DataTable 的方法。
但在某些情况下,Excel数据显示一个值,但内部还有另一个:
我正在研究一种从 Excel:
获取原始数据的方法string XmlFmt = "XML Spreadsheet";
var clipboard = Clipboard.GetDataObject();
if (clipboard.GetDataPresent(XmlFmt))
{
var clipData = clipboard.GetData(XmlFmt);
StreamReader streamReader = new StreamReader((MemoryStream)clipData);
streamReader.BaseStream.SetLength(streamReader.BaseStream.Length - 1);
string xmlText = streamReader.ReadToEnd();
var stream = new StringReader(xmlText);
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.LoadXml(xmlText);
DataSet dsExcelData = new DataSet();
dsExcelData.ReadXml(new XmlNodeReader(xmlDocument));
}
但是,此方法为我检索了一个包含多个表的数据集,其中包含 Excel 数据每个部分的配置:
基本上,我想将这些结构转换为仅包含原始数据的简单 DataTable。 有人可以帮我提示如何实现这一目标吗? ...我不想在此实现中使用第三方库。
如果它们是平面数据,你可以这样做。
private class Field
{
public string Valor { get; set; }
}
private class Row
{
public List<Field> Fields { get; set; }
public Row(string value)
{
Fields = new List<Field>();
var fieldsString = value.Split(new char[] {'\t'});
foreach (string f in fieldsString)
{
Fields.Add(new Field {Valor = f});
}
}
}
public Parse()
{
var data = Clipboard.GetDataObject();
var datos = (string)data.GetData(DataFormats.Text);
var stringRows = datos.Split(new Char[] {'\r', '\n'}, StringSplitOptions.RemoveEmptyEntries);
var table = new List<Row>(stringRows.Length) ;
foreach (string stringRow in stringRows)
{
table.Add( new Row(stringRow) );
}
}
in some cases, the Excel data shows a value, but internally have another.
使用XML方法是多数据表内部结构的原因。试试这个方法:
private void PasteFromExcel()
{
DataTable tbl = new DataTable();
tbl.TableName = "ImportedTable";
List<string> data = new List<string>(ClipboardData.Split('\n'));
bool firstRow = true;
if (data.Count > 0 && string.IsNullOrWhiteSpace(data[data.Count - 1]))
{
data.RemoveAt(data.Count - 1);
}
foreach (string iterationRow in data)
{
string row = iterationRow;
if (row.EndsWith("\r"))
{
row = row.Substring(0, row.Length - "\r".Length);
}
string[] rowData = row.Split(new char[] { '\r', '\x09' });
DataRow newRow = tbl.NewRow();
if (firstRow)
{
int colNumber = 0;
foreach (string value in rowData)
{
if (string.IsNullOrWhiteSpace(value))
{
tbl.Columns.Add(string.Format("[BLANK{0}]", colNumber));
}
else if (!tbl.Columns.Contains(value))
{
tbl.Columns.Add(value);
}
else
{
tbl.Columns.Add(string.Format("Column {0}", colNumber));
}
colNumber++;
}
firstRow = false;
}
else
{
for (int i = 0; i < rowData.Length; i++)
{
if (i >= tbl.Columns.Count) break;
newRow[i] = rowData[i];
}
tbl.Rows.Add(newRow);
}
}
DataGridView1.DataSource = tbl;
}
参考:http://www.seesharpdot.net/?p=221
编辑:
我做了一些测试,甚至使用 "XML Spreadsheet" 剪贴板格式,数据可以以指数表示法存储:
您可以检测并转换这些数字:Parse a Number from Exponential Notation
我找到了一个干净且万无一失的解决方案。这里的代码:
首先,将 XmlDocument 转换为 XElement 的扩展:
/// <summary> Convert XML Document to XDocument </summary>
/// <param name="xmlDocument">Attached XML Document</param>
public static XDocument fwToXDocument(this XmlDocument xmlDocument)
{
using (XmlNodeReader xmlNodeReader = new XmlNodeReader(xmlDocument))
{
xmlNodeReader.MoveToContent();
return XDocument.Load(xmlNodeReader);
}
}
完整功能:
private DataTable clipboardExcelToDataTable(bool blnFirstRowHasHeader = false)
{
string strTime = "S " + DateTime.Now.ToString("mm:ss:fff");
var clipboard = Clipboard.GetDataObject();
if (!clipboard.GetDataPresent("XML Spreadsheet")) return null;
strTime += "\r\nRead " + DateTime.Now.ToString("mm:ss:fff");
StreamReader streamReader = new StreamReader((MemoryStream)clipboard.GetData("XML Spreadsheet"));
strTime += "\r\nFinish read " + DateTime.Now.ToString("mm:ss:fff");
streamReader.BaseStream.SetLength(streamReader.BaseStream.Length - 1);
XmlDocument xmlDocument = new XmlDocument();
xmlDocument.LoadXml(streamReader.ReadToEnd());
strTime += "\r\nRead XML Document " + DateTime.Now.ToString("mm:ss:fff");
XNamespace ssNs = "urn:schemas-microsoft-com:office:spreadsheet";
DataTable dtData = new DataTable();
var linqRows = xmlDocument.fwToXDocument().Descendants(ssNs + "Row").ToList<XElement>();
for (int x = 0; x < linqRows.Max(a => a.Descendants(ssNs + "Cell").Count()); x++)
dtData.Columns.Add("Column " + (x + 1).ToString());
int intCol = 0;
DataRow drCurrent;
linqRows.ForEach(rowElement =>
{
intCol = 0;
drCurrent = dtData.Rows.Add();
rowElement.Descendants(ssNs + "Cell")
.ToList<XElement>()
.ForEach(cell => drCurrent[intCol++] = cell.Value);
});
if (blnFirstRowHasHeader)
{
int x = 0;
foreach (DataColumn dcCurrent in dtData.Columns)
dcCurrent.ColumnName = dtData.Rows[0][x++].ToString();
dtData.Rows.RemoveAt(0);
}
strTime += "\r\nF " + DateTime.Now.ToString("mm:ss:fff");
return dtData;
}
该过程需要约 15 秒来读取约 25,000 行。
适用于任何类型的数据。 基本上,该方法会创建一个与 Excel WorkSheet 具有相同结构的网格。 行或列的合并将填满第一个单元格。 默认情况下,所有列都是字符串数据类型。