Uruchomienie makra Excel z poziomu aplikacji

0

Cześć wszystkim.
Mam pewien pomysł do zrealizowania, ale opierał by się on o aplikację która bazowała by na bazie danych opartej na plikach Excela. Chcąc sobie ułatwić zacząłem się zastanawiać (jako że nie jestem profesjonalistą) czy nie dało by się zastąpić funkcji programu głównego operujących na bazie danych - funkcjami wynikającymi z makr zarejestrowanych bezpośrednio w pliku Excel z poziomu buttonu aplikacji.
Czy da się tak zrobić?

2

Excel ma kontrolkę COM+ instalowaną razem z programem, widniejącą w rejestrze Windows jako "Application.Excel" lub "Excel.Application".

Za pomocą tej kontrolki można wykonywać wszystkie czynności, jakie można wykonać w Excelu. Funkcje w nazwach i argumentach są bardzo zbliżone do funkcji w VBA dostępnych w makrach.

W ten sposób można na przykład utworzyć nowy arkusz, wpisać wartości do komórek, sformatować obramowanie, kolory i zapisać plik.

Nie jestem pewien, ale bardzo możliwe, że tą drogą można uruchomić makro. Jednakże, bezpośrednio po otwarciu pliku makra są wyłączone i należy potwierdzić możliwość uruchamiania ich. Tego to nie wiem, czy da się zrobić poprzez kontrolkę COM+. Jednakże Twój program może mieć powiedzmy dwa przyciski. Pierwszy otwiera plik XLS, potem Ty potwierdzasz, że chcesz uruchamiać makra, a drugi przycisk wykonuje właściwą operację.

0
Neosphoros napisał(a):

...a co powiesz o tym: https://social.msdn.microsoft.com/Forums/lync/en-US/2e33b8e5-c9fd-42a1-8d67-3d61d2cedc1c/how-to-call-excel-macros-programmatically-in-c?forum=exceldev

Ciężko jest mi zinterpretować kod. Czy to jest to co co mi chodzi?

To jest mniej więcej to, co ja proponuję, tyle, że jest przypisana kontrolka COM jako using, nie wiedziałem da się tak zrobić.

Wklej kod, którego nie rozumiesz i opisz konkretnie, czego nie rozumiesz.

Ja nie mam możliwości przetestowania.

0

@andrzejlisek: Autor z podanego linku pisze, że:

Let's say you have an Excel Macro which looks like this

Sub ShowMsg(msg As String, title As String)
MsgBox msg, vbInformation, title
End Sub

Here is the C# code to pass arguments to that macro and then call it.

...póżniej jednak:

xlApp.Run("ShowMsg", "Hello from C# Client", "Demo to run Excel macros from C#");

Czy nie oznacza to więc, że nie ważne jaki komunikat umieściliśmy w macro Excela bo i tak wyświetli się komunikat jaki "zadeklarujemy" dopiero pozniej w linijce "xlApp.Run("ShowMsg", "Hello from C# Client", "Demo to run Excel macros from C#");" czyli tak czy inaczej, czy przypadkiem wiadomość zapisana w macro nie staje się tu nieistotna?

poza tym czy dla vb net jest odpowiednik tego kodu?

0

Jakiś czas temu robiłem sterowanie Excelem z C# i wyszło mniej więcej coś takiego:

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Windows.Forms;

namespace GamOffice
{
    class CoreExcelMS : ICoreExcel
    {
        object xlApp;
        object xlBooks;
        object xlBook;
        object xlSheets;
        object xlSheet;

        bool ProgCreated = false;


        private object OpenApp()
        {
            return Activator.CreateInstance(Type.GetTypeFromProgID("Excel.Application")); ;
        }


        private object GetRangeXY(int X1, int Y1, int X2, int Y2)
        {
            return xlSheet.GetType().InvokeMember("Range", BindingFlags.GetProperty, null, xlSheet, C.Params(C.CellCoordsToAddress(X1, Y1), C.CellCoordsToAddress(X2, Y2)));
        }
        private object GetRangeXY(int X, int Y)
        {
            return GetRangeXY(X, Y, X, Y);
        }


        public void Open(string FileName)
        {
            if (!ProgCreated)
            {
                xlApp = OpenApp();
                ProgCreated = true;
            }
            xlBooks = xlApp.GetType().InvokeMember("Workbooks", BindingFlags.GetProperty, null, xlApp, null);
            if (FileName != "")
            {
                xlBook = xlBooks.GetType().InvokeMember("Open", BindingFlags.InvokeMethod, null, xlBooks, C.Params(FileName));
            }
            else
            {
                xlBook = xlBooks.GetType().InvokeMember("Add", BindingFlags.InvokeMethod, null, xlBooks, null);
            }
            xlApp.GetType().InvokeMember("Visible", BindingFlags.SetProperty, null, xlApp, C.Params(true));
            xlSheets = xlBook.GetType().InvokeMember("Sheets", BindingFlags.GetProperty, null, xlBook, null);
            SetSheet(0);
        }

        public void SetSheet(int Nr)
        {
            int SheetCount = (int)xlSheets.GetType().InvokeMember("Count", BindingFlags.GetProperty, null, xlSheets, null);
            while (SheetCount <= Nr)
            {
                xlSheets.GetType().InvokeMember("Add", BindingFlags.GetProperty, null, xlSheets, C.Params(Missing.Value, xlSheets.GetType().InvokeMember("Item", BindingFlags.GetProperty, null, xlSheets, C.Params(SheetCount)), Missing.Value, Missing.Value));
                SheetCount = (int)xlSheets.GetType().InvokeMember("Count", BindingFlags.GetProperty, null, xlSheets, null);
            }
            xlSheet = xlSheets.GetType().InvokeMember("Item", BindingFlags.GetProperty, null, xlSheets, C.Params(Nr + 1));
        }

        public void CellSet(int X, int Y, string Val)
        {
            object xlRange = GetRangeXY(X, Y);
            xlRange.GetType().InvokeMember("Value2", BindingFlags.SetProperty, null, xlRange, C.Params(Val));
        }

        public string CellGet(int X, int Y)
        {
            object xlRange = GetRangeXY(X, Y);
            return xlRange.GetType().InvokeMember("Value2", BindingFlags.GetProperty, null, xlRange, C.Params()).ToString();
        }

        public void Save(string FileName)
        {
            // http://msdn.microsoft.com/en-us/library/office/ff198017%28v=office.14%29.aspx
            object[] Par = new object[2];
            Par[0] = FileName;
            Par[1] = -4143;
            xlBook.GetType().InvokeMember("SaveAs", BindingFlags.InvokeMethod, null, xlBook, Par);
        }

        public void Close()
        {
            xlSheets = null;
            try
            {
                xlBook.GetType().InvokeMember("Close", BindingFlags.InvokeMethod, null, xlBook, C.Params(false, C.X, C.X));
            }
            catch
            {
            }
            try
            {
                xlBooks.GetType().InvokeMember("Close", BindingFlags.InvokeMethod, null, xlBooks, null);
            }
            catch
            {
            }
            try
            {
                xlApp.GetType().InvokeMember("Quit", BindingFlags.InvokeMethod, null, xlApp, null);
            }
            catch
            {
            }
            xlBook = null;
            xlBooks = null;
            xlApp = null;
            ProgCreated = false;
        }
    }
}

Statyczny typ C ma takie funkcje:

        public static object[] Params()
        {
            return null;
        }

        public static object[] Params(object P0)
        {
            object[] P = new object[1];
            P[0] = P0;
            return P;
        }

        public static object[] Params(object P0, object P1)
        {
            object[] P = new object[2];
            P[0] = P0;
            P[1] = P1;
            return P;
        }

        public static object[] Params(object P0, object P1, object P2)
        {
            object[] P = new object[3];
            P[0] = P0;
            P[1] = P1;
            P[2] = P2;
            return P;
        }

        public static object[] Params(object P0, object P1, object P2, object P3)
        {
            object[] P = new object[4];
            P[0] = P0;
            P[1] = P1;
            P[2] = P2;
            P[3] = P3;
            return P;
        }

        public static object X = Missing.Value;

        public static void CellAddressToCoords(string S, out int X, out int Y)
        {
            X = 0;
            Y = 0;
            string XX = "";
            string YY = "";
            for (int i = 0; i < S.Length; i++)
            {
                if (((byte)S[i]) < 60)
                {
                    YY = YY + S[i];
                }
                else
                {
                    XX = XX + S[i];
                }
            }
            for (int i = 0; i < XX.Length; i++)
            {
                X = X * 26;
                X = X + (((byte)XX[i]) - 64);
            }
            X--;
            Y = int.Parse(YY) - 1;
        }

        public static string CellCoordsToAddress(int X, int Y)
        {
            string SX = "";
            if (X > 701)
            {
                SX += ((char)((X / 676) + 64)).ToString();
                X = X % 676;
            }
            if (X > 25)
            {
                SX += ((char)((X / 26) + 64)).ToString();
            }
            SX += ((char)((X % 26) + 65)).ToString();
            string SY = (Y + 1).ToString();
            return (X >= 0 ? SX : "") + (Y >= 0 ? SY : "");
        }

Za pomocą tego kodu możesz otworzyć arkusz (lub utworzyć nowy), coś wpisać do komórki, coś odczytać z komórki i zapisać plik. Myślę, że to może być dobry punkt wyjścia, żeby próbować coś więcej zrobić, np. uruchomić makro, wstawić wykres itp.

0

@andrzejlisek: Sugestia sama-w-sobie wydaje się super. Dzięki.

...ale czy samo połączenie składni C# i wywołanie makr VBA jest możliwe w jednej aplikacji?

0
Neosphoros napisał(a):

@andrzejlisek: Sugestia sama-w-sobie wydaje się super. Dzięki.

...ale czy samo połączenie składni C# i wywołanie makr VBA jest możliwe w jednej aplikacji?

Jak wcześniej wspomniałem, nie mam możliwości sprawdzenia, ale jeżeli w ramach pracy makra można uruchomić inne makro, to raczej tak. Polecenia kontrolki COM są takie same, jak polecenia w VBA. W C# w ten sposób można zrealizować dowolne działanie w Excelu. Aby mieć ogląd, jaki będzie kod, to trzeba zarejestrować makro, wykonać to działanie, a potem otworzyć kod tego makra. W C# będzie trzeba napisać bardzo podobny kod do tego w VBA, są tylko niewielkie różnice w składni poleceń i argumentów wynikające z różnic między tymi językami. Jeżeli w VBA będzie polecenie typu RunMacro(MacroName$), gdzie MacroName$ to nazwa makra, to w C# też można to zrobić, jak biblioteka COM ma funkcję RunMacro przyjmującą tekst jako argument. Proponuję włączyć rejestrowanie makra i w czasie trwania rejestrowania uruchomić inne makro i zobaczyć, co zostanie zarejestrowane, to już podpowie nazwę potrzebnej funkcji.

1 użytkowników online, w tym zalogowanych: 0, gości: 1