使用工作表作为数据源的VSTO Excel的简单示例

我认为我遇到了“最简单的答案是最难find的答案”,而且我没有遇到任何直接向我提供的search。 这是针对现有VSTO(C#)项目中的Excel 2010VS 2010

我有一个Excel工作表,其中包含我想用作DataGridView的源的4列数据。 有人可以提供C#代码片断(1)从特定的工作表中获取数据并用它填充自定义对象吗? (2)将对象(如IEnumerable列表)绑定到Datagridview;(3)更新和删除function的一些片段,这些function将是网格固有的并反馈到源工作表。

我知道我在这里要求很多,但是这么多的VSTO信息似乎是分离的,并不总是很容易find。 谢谢!

编辑:很好,我只是注意到,我错过了你的问题的一大部分,获取更新和删除回工作表。 我完全不知道这是否可能,但我认为这使我的解决scheme毫无价值。 无论如何,我会把它留在这里,也许它可以帮助你。


你为什么需要VSTO? 据我所知VSTO是用于Office加载项。 但是既然你想在DataGridView中显示数据,我假设你有一个只能访问工作簿的WinForms应用程序。 在这种情况下,您可以简单地使用Office Interop打开工作簿。 只需将Microsoft.Office.Interop.Excel的引用添加到您的项目中,并添加一个using Microsoft.Office.Interop.Excel; 声明。

Excel Interop的MSDN参考文档可以在这里find: http : //msdn.microsoft.com/en-us/library/ms262200%28v=office.14%29.aspx

我会给你的Excel部分,也许别人可以做其余的。

首先,打开Excel和工作簿:

 Application app = new Application(); // Optional, but recommended if the user shouldn't see Excel. app.Visible = false; app.ScreenUpdating = false; // AddToMru parameter is optional, but recommended in automation scenarios. Workbook workbook = app.Workbooks.Open(filepath, AddToMru: false); 

然后以某种方式得到正确的工作表。 你有几个可能性:

 // Active sheet (should be the one which was active the last time the workbook was saved). Worksheet sheet = workbook.ActiveSheet; // First sheet (notice that the first is actually 1 and not 0). Worksheet sheet = workbook.Worksheets[1]; // Specific sheet. // Caution: Default sheet names differ for different localized versions of Excel. Worksheet sheet = workbook.Worksheets["Sheet1"]; 

然后得到正确的范围。 你没有指定你如何知道需要的数据在哪里,所以我会假设它是在固定的列。

 // If you also know the row count. Range range = sheet.Range["A1", "D20"]; // If you want to get all rows until the last one that has some data. Range lastUsedCell = sheet.Cells.SpecialCells(XlCellType.xlCellTypeLastCell); string columnName = "D" + lastUsedCell.Row; Range range = sheet.Range["A1", columnName]; 

获取值:

 // Possible types of the return value: // If a single cell is in the range: Different types depending on the cell content // (string, DateTime, double, ...) // If multiple cells are in the range: Two dimensional array that exactly represents // the range from Excel and also has different types in its elements depending on the // value of the Excel cell (should always be that one in your case) object[,] values = range.Value; 

这个二维对象数组可以被用作DataGridView的数据源。 我没有使用WinForms多年,所以我不知道你是否可以直接绑定它,或者首先需要将数据转换为某种特定的格式。

最后再次closuresExcel:

 workbook.Close(SaveChanges: false); workbook = null; app.Quit(); app = null; // Yes, we really want to call those two methods twice to make sure all // COM objects AND all RCWs are collected. GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); GC.WaitForPendingFinalizers(); 

使用Interop之后正确closuresExcel本身就是一项任务,因为您必须确保所有对COM对象的引用都已释放。 我发现这样做的最简单的方法是完成所有的工作,除了打开和closuresExcel和工作簿(所以我的第一个和最后一个代码块)在一个单独的方法。 这可确保在调用Quit时,该方法中使用的所有COM对象超出范围。

更新:

我用更新的代码replace了我之前的方法,以便更快速地实现。 System.Array是相当有效和快速的方式来读取和绑定数据到Excel。 你可以从这个链接下载演示。


我在Excel 2003工作簿中开发了VSTO应用程序。 在语法方面没有太大的差别,所以你可以在2007/2010中毫不费力的使用它。

在这里输入图像描述

我不知道你将使用哪个事件打开显示数据的窗口,所以我假设你将使用。

 SheetFollowHyperlink 

我将使用Showdata.cs中声明的静态工作簿对象。 这是你的Thisworkbook.cs的代码

  private void ThisWorkbook_Startup(object sender, System.EventArgs e) { ShowData._WORKBOOK = this; } private void ThisWorkbook_SheetFollowHyperlink(object Sh, Microsoft.Office.Interop.Excel.Hyperlink Target) { System.Data.DataTable dTable = GenerateDatatable(); showData sh = new showData(dTable); sh.Show(); // You can also use ShowDialog() } 

我在当前工作表上添加了一个链接,它将popup一个带有datagridview的窗口。

  private System.Data.DataTable GenerateDatatable() { Range oRng = null; // It takes the current activesheet from the workbook. You can always pass any sheet as an argument Worksheet ws = this.ActiveSheet as Worksheet; // set this value using your own function to read last used column, There are simple function to find last used column int col = 4; // set this value using your own function to read last used row, There are simple function to find last used rows int row = 5; 

//让我们假设它的4和5由方法stringstrRange =“A1”返回; string andRange =“D5”;

  System.Array arr = (System.Array)ws.get_Range(strRange, andRange).get_Value(Type.Missing); System.Data.DataTable dt = new System.Data.DataTable(); for (int cnt = 1; cnt <= col; cnt++) dt.Columns.Add(cnt.Chr(), typeof(string)); for (int i = 1; i <= row; i++) { DataRow dr = dt.NewRow(); for (int j = 1; j <= col; j++) { dr[j - 1] = arr.GetValue(i, j).ToString(); } dt.Rows.Add(dr); } return dt; } 

这里是允许用户显示和编辑数值的表单。 我已经添加了扩展方法 s和Chr()将数字转换成各自的字母,这将来得心应手。

 public partial class ShowData : Form { //use static workbook object to access Worksheets public static ThisWorkbook _WORKBOOK; public ShowData(System.Data.DataTable dt) { InitializeComponent(); // binding value to datagrid this.dataGridView1.DataSource = dt; } private void RefreshExcel_Click(object sender, EventArgs e) { Worksheet ws = ShowData._WORKBOOK.ActiveSheet as Worksheet; System.Data.DataTable dTable = dataGridView1.DataSource as System.Data.DataTable; // Write values back to Excel sheet // you can pass any worksheet of your choice in ws WriteToExcel(dTable,ws); } private void WriteToExcel(System.Data.DataTable dTable,Worksheet ws) { int col = dTable.Columns.Count; ; int row = dTable.Rows.Count; string strRange = "A1"; string andRange = "D5"; System.Array arr = Array.CreateInstance(typeof(object),5,4); for (int i = 0; i < row; i++) { for (int j = 0; j < col; j++) { try { arr.SetValue(dTable.Rows[i][j].ToString(), i, j); } catch { } } } ws.get_Range(strRange, andRange).Value2 = arr; this.Close(); } public static class ExtensionMethods { static string alphabets = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; public static string Chr(this int p_intByte) { if (p_intByte > 0 && p_intByte <= 26) { return alphabets[p_intByte - 1].ToString(); } else if (p_intByte > 26 && p_intByte <= 700) { int firstChrIndx = Convert.ToInt32(Math.Floor((p_intByte - 1) / 26.0)); int scndIndx = p_intByte % 26; if (scndIndx == 0) scndIndx = 26; return alphabets[firstChrIndx - 1].ToString() + alphabets[scndIndx - 1].ToString(); } return "NA"; } } 

这是我写的最丑陋的代码之一,但它将作为一个概念certificate:)我已经创build了一个示例工作簿

 Column1 Column2 Column3 Column4
 -------------------------------------------------- ----
数据1-1数据2-1数据3-1数据-4-1
数据1-2数据-2-2数据-3-2数据-4-2
 ....

Excel文件恰好包含50行,这解释了硬编码范围select器。 编写代码后部分代码很简单,只需要创build一个表单,添加一个MyExcelData ,为MyExcelData创build一个数据源,创build一个MyExcelData的实例,像var data = new MyExcelData(pathToExcelFile); 并将其绑定到网格。

代码是丑陋的,并有许多假设,但它实现您的要求。 如果你打开Excel和程序,你可以看到网格上的更新反映在Excel单元格编辑后。 删除行也从Excel中删除。 因为我不知道你是否有你的Excel的主键,我使用行索引作为ID。

顺便说一句,当谈到VSTO,我真的很糟糕。 所以如果你知道更好的方式打开/编辑/保存请通知我。

 public class MyExcelDataObject { private readonly MyExcelData owner; private readonly object[,] realData; private int RealId; public MyExcelDataObject(MyExcelData owner, int index, object[,] realData) { this.owner = owner; this.realData = realData; ID = index; RealId = index; } public int ID { get; set; } public void DecrementRealId() { RealId--; } public string Column1 { get { return (string)realData[RealId, 1]; } set { realData[ID, 1] = value; owner.Update(ID); } } public string Column2 { get { return (string)realData[RealId, 2]; } set { realData[ID, 2] = value; owner.Update(ID); } } public string Column3 { get { return (string)realData[RealId, 3]; } set { realData[ID, 3] = value; owner.Update(ID); } } public string Column4 { get { return (string)realData[RealId, 4]; } set { realData[ID, 4] = value; owner.Update(ID); } } } public class MyExcelData : BindingList<MyExcelDataObject> { private Application excel; private Workbook wb; private Worksheet ws; private object[,] values; public MyExcelData(string excelFile) { excel = new ApplicationClass(); excel.Visible = true; wb = excel.Workbooks.Open(excelFile); ws = (Worksheet)wb.Sheets[1]; var range = ws.Range["A2", "D51"]; values = (object[,])range.Value; AllowEdit = true; AllowRemove = true; AllowEdit = true; for (var index = 0; index < 50; index++) { Add(new MyExcelDataObject(this, index + 1, values)); } } public void Update(int index) { var item = this[index - 1]; var range = ws.Range["A" + (2 + index - 1), "D" + (2 + index - 1)]; range.Value = new object[,] { {item.Column1, item.Column2, item.Column3, item.Column4} }; } protected override void RemoveItem(int index) { var range = ws.Range[string.Format("A{0}:D{0}", (2 + index)), Type.Missing]; range.Select(); range.Delete(); base.RemoveItem(index); for (int n = index; n < Count; n++) { this[n].DecrementRealId(); } } } 

PS:我想使用轻量级的对象,但它增加了不必要的复杂性。

所以在Sheet1_Startup事件中

 Excel.Range range1 = this.Range["A1", missing]; var obj = range1.Value2.ToString(); 

那么你需要移动到下一个单元格

  range1 = this.Range["A2", missing]; obj = New list(range1.Value2.ToString());