![](/img/trans.png)
[英]Example of VSTO for Excel in C# for setting up and using the Worksheet Change Event?
[英]Simple Example of VSTO Excel using a worksheet as a datasource
我想我遇到了“最简单的答案是最难找到的答案”的案例,而且我没有遇到任何以直截了当的方式给我的搜索。 这适用于现有VSTO(C#)项目中的Excel 2010和VS 2010 。
我有一个Excel工作表,其中包含4列数据,我想将其用作DataGridView的源。 有人可以提供C#代码片段,以便(1)从特定工作表中获取数据并用它填充自定义对象吗? (2)将对象(如IEnumerable列表)绑定到Datagridview,以及(3)用于更新和删除功能的一些片段,这些片段将是网格固有的并反馈到源工作表。
我知道我在这里要求很多,但是很多VSTO信息似乎都被拆开了,并不总是很容易找到。 谢谢!
编辑:很好,我只是注意到我错过了你的大部分问题,获取更新并删除回工作表。 我完全不知道这是否可能,但我认为这使我的解决方案毫无价值。 无论如何我会留在这里,也许它可以以任何方式帮助。
你为什么需要VSTO? 据我所知,VSTO用于Office Add-Ins。 但是因为你想在DataGridView中显示数据,我假设你有一个WinForms应用程序应该只访问工作簿。 在这种情况下,您只需使用Office Interop打开工作簿即可。 只需在项目中添加对Microsoft.Office.Interop.Excel的引用,并using Microsoft.Office.Interop.Excel;
添加using Microsoft.Office.Interop.Excel;
声明。
可以在此处找到Excel Interop的MSDN参考文档: 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,所以我不知道你是否可以直接绑定它,或者首先需要将数据转换成某种特定的格式。
最后再次关闭Excel:
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后正确关闭Excel本身就是一项任务,因为您必须确保已释放对COM对象的所有引用。 我发现这样做的最简单的方法是完成所有的工作,除了在单独的方法中打开和关闭Excel和工作簿(所以我的第一个和最后一个代码块)。 这可确保在调用Quit
时,该方法中使用的所有COM对象都超出范围。
更新:
为了更快的方法,我用更新的代码替换了我以前的方法。 System.Array
是一种非常有效且快速的方法来读取和绑定数据到Excel。 您可以从此链接下载演示。
我在Excel 2003 Workbook中开发了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()
}
我在当前工作表上添加了一个链接,它将弹出一个带有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由方法字符串strRange =“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";
}
}
这是我编写的最丑陋的代码之一,但它可以作为概念证明:)我已经创建了一个像这样的示例工作簿
Column1 Column2 Column3 Column4 ------------------------------------------------------ Data-1-1 Data-2-1 Data-3-1 Data-4-1 Data-1-2 Data-2-2 Data-3-2 Data-4-2 ....
Excel文件恰好包含50行,这解释了硬编码范围选择器。 编写MyExcelData
部分代码后,只需创建表单,添加数据视图网格,为MyExcelData
创建数据源,创建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());
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.