web-dev-qa-db-ja.com

ワークシートをデータソースとして使用したVSTOExcelの簡単な例

「最も簡単な答えは見つけるのが最も難しい」というケースに遭遇していると思いますが、これを簡単な方法で提供する検索はありません。これは、既存のVSTO(C#)プロジェクト内のExcel 201およびVS 201用です。

DataGridViewのソースとして使用したい4列のデータを含むExcelワークシートがあります。誰かが(1)特定のワークシートからデータを取得してカスタムオブジェクトにデータを入力するためのC#コードスニペットを提供できますか? (2)オブジェクト(IEnumerableリストなど)をDatagridviewにバインドし、(3)グリッドに固有でソースワークシートにフィードバックする更新および削除機能のスニペットをいくつか示します。

私はここでたくさんのことを求めていることを知っていますが、VSTO情報の多くはばらばらであり、必ずしも簡単に見つけることができないようです。ありがとう!

11
Unknown Coder

編集:すばらしいです。質問の大部分を見逃していて、更新と削除をワークシートに戻していることに気づきました。それが可能かどうかはまったくわかりませんが、それが私の解決策を無価値にしていると思います。とにかくここに残しておきます、多分それは何らかの形で役立つかもしれません。


なぜVSTOが必要なのですか?私の知る限り、VSTOはOfficeアドインに使用されています。ただし、DataGridViewにデータを表示する必要があるため、ブックにアクセスするだけのWinFormsアプリケーションがあると想定します。この場合、Office相互運用機能を使用してブックを開くことができます。 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;

その2次元オブジェクト配列は、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を正しく閉じることは、COMオブジェクトへのすべての参照が解放されていることを確認する必要があるため、タスク自体です。これを行うために私が見つけた最も簡単な方法は、Excelとブック(つまり、最初と最後のコードブロック)を別々の方法で開いたり閉じたりすることを除いて、すべての作業を行うことです。これにより、Quitが呼び出されたときに、そのメソッドで使用されるすべてのCOMオブジェクトがスコープ外になります。

16
cremor

更新:

以前のメソッドを新しいコードに置き換えて、アプローチを高速化しました。 System.Arrayは、データを読み取ってExcelにバインドするための非常に効率的で高速な方法です。デモは このリンク からダウンロードできます。


Excel 2003WorkbookでVSTOアプリケーションを開発しました。構文に大きな違いはないので、2007/2010でも手間をかけずに使用できます。

enter image description here

データを表示するウィンドウを開くためにどのイベントを使用するかわからなかったので、使用することを想定しています。

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()
        }

現在のシートにリンクを追加すると、データグリッドビューのウィンドウがポップアップ表示されます。

         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;

//メソッドstringstrRange = "A1";によって返される4と5を想定します。 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;
    }

これは、ユーザーが値を表示および編集できるようにするフォームです。 extension methodsと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";
        }

    }
4

これは私が書いた中で最も醜いコードの1つですが、概念実証として機能します:)私はそのようなサンプルワークブックを作成しました

 Column1 Column2 Column3 Column4 
 ------------------------------------ ------------------ 
データ-1-1データ-2-1データ-3-1データ-4-1 
データ- 1-2データ-2-2データ-3-2データ-4-2 
 .... 

Excelファイルには正確に50行が含まれています。これは、ハードコードされた範囲セレクターを説明しています。コードの残りの部分を記述した後は、フォームを作成し、dataviewgridを追加し、MyExcelDataのデータソースを作成し、var data = new MyExcelData(pathToExcelFile);のようなMyExcelDataのインスタンスを作成するだけです。グリッドにバインドします。

コードは醜く、多くの仮定がありますが、それはあなたの要件を実装します。 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:軽量のオブジェクトを使用したいのですが、不必要な複雑さが加わります。

3
Erdogan Kurtur

したがって、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());
1
juanvan