Простой пример VSTO Excel с использованием рабочего листа в качестве источника данных
Я думаю, что я столкнулся с случаем "самые легкие ответы - самые трудные, которые можно найти", и я не сталкивался с поисками, которые дают это мне прямолинейно. Это для Excel 2010 и VS 2010 в рамках существующего проекта VSTO (С#).
У меня есть лист Excel, содержащий 4 столбца данных, которые я хотел бы использовать в качестве источника для DataGridView. Может кто-нибудь, пожалуйста, предоставит фрагменты кода С# для (1) получения данных с определенного листа и заполнения им определенного объекта? (2) привязка объекта (например, списка IEnumerable) к Datagridview и (3) некоторые фрагменты для функций обновления и удаления, которые будут присущи сетке и будут возвращены на исходный лист.
Я знаю, что я много прошу здесь, но большая часть информации VSTO, похоже, диссонирована и не всегда легко найти. Спасибо!
Ответы
Ответ 1
Изменить: Отлично, я просто заметил, что пропустил большую часть вашего вопроса, получая обновления и удаляя их обратно на рабочий лист. Я понятия не имею, возможно ли это, но я думаю, что это решение не имеет смысла. Я все равно оставлю его здесь, возможно, это может помочь в любом случае.
Зачем вам VSTO? Насколько я знаю, VSTO используется для надстроек Office. Но поскольку вы хотите показать данные в DataGridView, я предполагаю, что у вас есть приложение WinForms, которое должно просто получить доступ к рабочей книге. В этом случае вы можете просто открыть книгу, используя Office Interop. Просто добавьте ссылку на Microsoft.Office.Interop.Excel в свой проект и добавьте оператор using Microsoft.Office.Interop.Excel;
.
Справочная документация MSDN для Excel Interop приведена здесь: 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();
Правильное закрытие Excel после использования Interop - это сама задача, потому что вы должны убедиться, что все ссылки на COM-объекты были выпущены. Самый простой способ, который я нашел для этого, - сделать всю работу, кроме открытия и закрытия Excel и рабочей книги (так что мой первый и последний блок кода) в отдельном методе. Это гарантирует, что все COM-объекты, используемые в этом методе, выходят за рамки, когда вызывается Quit
.
Ответ 2
ОБНОВЛЕНИЕ:
Я заменил свой предыдущий метод новым кодом для более быстрого подхода. System.Array
- довольно эффективный и быстрый способ чтения и привязки данных к excel. Вы можете скачать демо из эту ссылку.
Я разработал приложение VSTO в книге Excel 2003. В синтаксисе нет больших различий, поэтому вы можете использовать его в 2007/2010 годах без усилий.
![enter image description here]()
Я не знал, какое событие вы будете использовать, чтобы открыть окно с данными, поэтому я предполагаю, что вы будете использовать.
SheetFollowHyperlink
Я собираюсь использовать объект Static workbook, объявленный в 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 возвращаются методом string strRange = "A1"; string и Range = "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;
}
Здесь форма, которая позволит пользователю отображать и редактировать значения. Я добавил метод расширения и 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";
}
}
Ответ 3
это один из самых уродливых кодов, которые я написал, но он будет работать как доказательство концепции:)
Я создал примерную рабочую книгу, подобную этой
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 строк, это объясняет селектор диапазона с жестким кодированием.
После написания этой части кода легко, просто создайте форму, добавьте dataviewgrid, создайте источник данных для MyExcelData
, создайте экземпляр MyExcelData
, например var data = new MyExcelData(pathToExcelFile);
, и привяжите его к сетке.
Код уродливый и имеет множество предположений, но он выполняет ваши требования. Если вы откроете excel и программу, вы увидите, что обновления в сетке отражаются на excel после редактирования ячейки. удаленная строка также удаляется из excel. так как я не знал, есть ли у вас первичные ключи вашего excel или нет, я использовал индекс строки в качестве идентификатора.
Кстати, мне очень плохо, когда речь идет о 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: Я бы хотел использовать легкие объекты, но он добавляет лишних осложнений.
Ответ 4
Итак, в событии 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());