Laden...

C# - ExcelAutomation; Downcast eines workbook object

Erstellt von JessicaLampe vor 16 Jahren Letzter Beitrag vor 16 Jahren 7.169 Views
J
JessicaLampe Themenstarter:in
6 Beiträge seit 2008
vor 16 Jahren
C# - ExcelAutomation; Downcast eines workbook object

Hallo @all,

ich habe das folgende Problem. Ich möchte verschiedene Excel-Versionen automatisieren. Dafür will ich "late binding" einsetzen um dadurch ein Makro innerhalb der geöffneten Excel-Dateien auszuführen.

Dafür ermittle ich mit Hilfe der ROT table (Running Object Table) die geöffneten Workbooks.

Nun habe ich das folgende Problem, das Makro lässt sich nur aufrufen, wenn ich die dazugehörige Excel-Instanz (application instance) übergebe. Ich hab aber nur Zugriff auf die untergeordneten Workbooks.

Wie kann ich auf die übergeordnete Instanz vom Workbook referenzieren bzw. das ganze mit einem Cast lösen? ?(

.GetActiveObject hilft mir ja nicht weiter, da ich dadurch nur eine geöffnete Instanz von Excel ermitteln kann:

object XL = System.Runtime.InteropServices.Marshal.GetActiveObject("Excel.Application");

Code:
http://www.themssforum.com/Winforms/Retrieve-Multiple/

Danke
Jessi

183 Beiträge seit 2004
vor 16 Jahren

Hallo JessicaLampe,

willkommen hier im Forum 👍 (Obligatorischer Hinweis: [Hinweis] Wie poste ich richtig?)

Mit dem Code aus

Hallo @all,
Code:

>

bekommst du doch schon die passende Excel-Instanz für ein Workbook ...

Jetzt noch 'Run' aufrufen und es sollte funktionieren 😁

Nun habe ich das folgende Problem, das Makro lässt sich nur aufrufen, wenn ich die dazugehörige Excel-Instanz (application instance) übergebe. Ich hab aber nur Zugriff auf die untergeordneten Workbooks. Versteh ich nicht 🤔

Ein bisschen Beispielcode aus deiner Anwendung wäre übrigens nicht schlecht.

Grüße

So einfach wie möglich, aber nicht einfacher. [Albert Einstein]

take a look at
* baer-torsten.de
* codinghints

J
JessicaLampe Themenstarter:in
6 Beiträge seit 2008
vor 16 Jahren

Hallo él toro,

danke für die Antwort.
Ich mache bisher nicht viel mehr als den verlinkten Code auszuführen und die Workbook-Objekte abhängig vom Dateinamen zu filtern. Damit erhalte ich die verschiedenen Workbook-Instanzen, völlig richtig.

Ich kann ein Excel-Makro aber immer nur über die Application-Instanz ausführen:

oApp.GetType().InvokeMember("Run", System.Reflection.BindingFlags.Default | System.Reflection.BindingFlags.InvokeMethod, null, oApp, oRunArgs);

Das heißt, oApp ist vom Typ "Excel.Application". Wenn ich für oApp das Objekt eines Workbooks übergebe wird das Makro nicht ausgeführt.

Daher möchte ich vom entsprechenden Workbook sozusagen den Parent, also die Applikationsinstanz ermitteln. Wenn ich diese zur Verfügung hätte könnte ich auch mein Makro starten.

Oder muss ich einfach den "Run"-Befehl so abändern, dass dieser auch mit Workbook-Instanzen funktioniert? Wenn ja, wie funktioniert das? ?(

183 Beiträge seit 2004
vor 16 Jahren

Nur mal so zum Verständnis:

object XL = System.Runtime.InteropServices.Marshal.GetActiveObject("Excel.Application");

Liefert dir ein Object (genauer einen RCW) für die Excel-Anwendung.

Für das Makro musst du natürlich auch den Namen übergeben ...

Bei mir läuft folgender Code ohne Probleme:

object oApp = Marshal.GetActiveObject("Excel.Application");

oApp.GetType().InvokeMember("Run", BindingFlags.Instance | BindingFlags.InvokeMethod | BindingFlags.Public, null,
oApp, new object[] { "Makro1" });

So einfach wie möglich, aber nicht einfacher. [Albert Einstein]

take a look at
* baer-torsten.de
* codinghints

J
JessicaLampe Themenstarter:in
6 Beiträge seit 2008
vor 16 Jahren

Hey él toro,

das ist mir alles bekannt, hilft mir leider nicht weiter. 🙁

Du verwendest .GetActiveObject um EINE aktive Excel-Instanz zu ermitteln. Das Problem ist hierbei, das NIE vorhersehbar ist welche der geöffneten Excel-Instanzen geschnappt wird.

Wenn du mehrfach Excel öffnest existieren nämlich auch mehrere Prozesse (siehe Taskmanager). Laut MSDN wird angeblich immer die zuerst geöffnete Excel-Instanz mit .GetActiveObject ermittelt, dem ist aber nicht immer so.

Aus diesem Grund bin ich nach einer langen Suche darauf gekommen die ROT table durchzugehen. Dort findet man nämlich alle aktiven Workbooks aufgelistet.

Das heißt ich habe die verschiedenen Workbooks zur Auswahl.

Nun möchte ich aber in einem der Workbooks (z. B. C:\Datei1.xls) das Makro ausführen.

Dafür braucht man anscheinend unbedingt die oberste Excel-Instanz, die habe ich aber nicht, da die Workbooks innerhalb der verschiedenen Excel-Instanzen liegen.
Ich bin quasi schon einen Schritt zu weit.

Also benötige ich eine Möglichkeit vom Workbook-object zurück zur Excel-Instanz zu kommen, nämlich genau zu dem Object das du mit XL und GetActiveObject ermittelst.

Du kannst das ja mal spaßenshalber probieren und mehrmals verschiedene Exceldateien in unterschiedlicher Reihenfolge öffnen und diese auch offen zu lassen, dann deinen Code auszuführen. Mal wird das Makro ausgeführt, mal nicht.

Das Problem bleibt also leider weiterhin bestehen. Es muss doch möglich sein von einem Workbook auf das Elternelement, die Parentobjektklasse zuzugreifen? ?(

1.378 Beiträge seit 2006
vor 16 Jahren

Nichts leichter als das. In den ganzen Office Object Modellen kann man oft von jeder Ebene zu jeder wechseln.

Du willst aus einem Workbook die Excel Applikation erreichen?

dann verwende untenstehendes:


Microsoft.Office.Interop.Excel.Application oXl = (Microsoft.Office.Interop.Excel.Application)wb.Application;

Lg XXX

183 Beiträge seit 2004
vor 16 Jahren

Du verwendest .GetActiveObject um EINE aktive Excel-Instanz zu ermitteln. Das Problem ist hierbei, das NIE vorhersehbar ist welche der geöffneten Excel-Instanzen geschnappt wird. Ich weiß, war nur ein Beispiel 😉

Aus diesem Grund bin ich nach einer langen Suche darauf gekommen die ROT table durchzugehen. Dort findet man nämlich alle aktiven Workbooks aufgelistet. Hast ja Recht, ich bin dem aufgesessen, dass man damit die Applikation selbst bekommt. Mein Fehler 8)

xxxprod hat zum Teil Recht. Da du Late Binding verwenden möchtest hier mal als Wiedergutmachung nen bisschen Code 😁

object[] monikers = Win32.Win32.GetActiveObjectsFromROT("Mappe1.xls");

object oWorkbook = null, oApp = null;
try
{
   oWorkbook = monikers[0];

   oApp = oWorkbook.GetType().InvokeMember("Application",
      BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.Public,
      null, oWorkbook, null);

   oApp.GetType().InvokeMember("Run",
      BindingFlags.Public | BindingFlags.InvokeMethod | BindingFlags.Instance,
      null, oApp, new object[] { "Makro1" });
}
finally
{
   if (oApp != null) Marshal.ReleaseComObject(oApp);
   if (oWorkbook != null) Marshal.ReleaseComObject(oWorkbook);

   foreach (object o in monikers)
      Marshal.ReleaseComObject(o);
}

Und noch mein Wrapper:

public class Win32
{
   [DllImport("ole32.dll")]
   public static extern int GetRunningObjectTable(int reserved, out IRunningObjectTable prot);

   [DllImport("ole32.dll")]
   public static extern int CreateBindCtx(int reserved, out IBindCtx ppbc);

   public static object[] GetActiveObjectsFromROT(string moniker)
   {
      ArrayList objs = new ArrayList();
      IRunningObjectTable prot;
      IEnumMoniker pMonkEnum;

      GetRunningObjectTable(0, out prot);
      prot.EnumRunning(out pMonkEnum);
      pMonkEnum.Reset();
      IntPtr fetched = IntPtr.Zero;

      IMoniker[] pmon = new IMoniker[1];
      while (pMonkEnum.Next(1, pmon, fetched) == 0)
      {
         IBindCtx pCtx;
         CreateBindCtx(0, out pCtx);
         string str;

         pmon[0].GetDisplayName(pCtx, null, out str);
         if (str.Contains(moniker))
         {
            object objReturnObject;
            prot.GetObject(pmon[0], out objReturnObject);
            objs.Add(objReturnObject);
         }
      }

      return objs.ToArray();
   } 
}

Hier nun aber auch gleich wieder der Hinweis: :rtfm:

So einfach wie möglich, aber nicht einfacher. [Albert Einstein]

take a look at
* baer-torsten.de
* codinghints

J
JessicaLampe Themenstarter:in
6 Beiträge seit 2008
vor 16 Jahren

Hi él toro,

großes Danke für den Hinweis wie man den Vorschlag von xxxprod mit late binding umsetzt!

Ich hab den Code jetzt soweit implementiert und versucht die Möglichkeit einzubauen gegebenenfalls mehrere offene Workbooks durchzugehen. Zusätzlich merke ich mir die geöffneten Workbooks in einem String-Array (by reference). Deinen Code zum Ausführen des Makros hab ich nur in eine Funktion namens RunMakro ausgelagert.

string[] OpenWorkbooks = new string[6];
object[] ExcelWorkbooks = Win32.GetActiveObjectsFromROT(".xls", ref OpenWorkbooks);

int i = 0;
foreach (object ExcelWorkbook in ExcelWorkbooks)
{
	object oApp = null;
	try
	{
		oApp = ExcelWorkbook.GetType().InvokeMember("Application", BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.Public, null, ExcelWorkbook, null);
		RunMacro(oApp, new Object[] { "Makro1" });
	}
	finally
	{
		if (oApp != null)
			Marshal.ReleaseComObject(oApp);
	}
	i++;
}

foreach (object o in ExcelWorkbooks)
	Marshal.ReleaseComObject(o);

Leider hab ich teilweise immer noch dasselbe verhalten. In einem Workbook (z. B. C:\ExcelDatei1.xls) ist der Makroname "Makro1" definiert, in einem anderen Workbook (z. B. C:\ExcelDatei2.xls) nicht. Nun wird trotz ermittelten der Applikationsinstanz nicht immer das Makro in dem Workbook ausgeführt, in dem es definiert wurde.

Womit hängt das zusammen, dass es wie bei .GetActiveObject manchmal funktioniert, manchmal nicht??? ?(
Eigentlich ist der Aufruf

oApp = ExcelWorkbook.GetType().InvokeMember("Application", BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.Public, null, ExcelWorkbook, null);

doch eindeutig und müsste in jedem Fall auf die richtige dazugehörige Applikationsinstanz zeigen?

183 Beiträge seit 2004
vor 16 Jahren

Leider hab ich teilweise immer noch dasselbe verhalten. In einem Workbook (z. B. C:\ExcelDatei1.xls) ist der Makroname "Makro1" definiert, in einem anderen Workbook (z. B. C:\ExcelDatei2.xls) nicht. Nun wird trotz ermittelten der Applikationsinstanz nicht immer das Makro in dem Workbook ausgeführt, in dem es definiert wurde. Kann ich leider nicht nachvollziehen 🤔

Wie ruffst du deinen Code auf?

So einfach wie möglich, aber nicht einfacher. [Albert Einstein]

take a look at
* baer-torsten.de
* codinghints

J
JessicaLampe Themenstarter:in
6 Beiträge seit 2008
vor 16 Jahren

So, ich hab das Problem jetzt herausgefunden.

Bei Excel werden teilweise verschiedene Mappen/Workbooks innerhalb derselben Excel-Anwendung geöffnet bzw. in irgendeiner Form gruppiert. Das erkennt man daran, dass die Exceldokumente im Task so gruppiert werden --> (2) Microsoft Office Excel
Man kann also nur über den entsprechenden Taskeintrag die Mappen wechseln.

Wenn nun aber die "falsche" Mappe den Fokus hat (als letztens angeklickt wurde), dann schlägt die Ausführung des Makros fehl. Sobald man aber der anderen Mappe mit dem dazugehörigen Makro den Focus gibt (so dass die Z-Order über der der anderen Mappe liegt), dann funktioniert das ganze wieder. Er findet aber wie gewünscht stets alle geöffneten Mappen auf die der Suchstring zutrifft.

Daher meine Frage, wie setze ich das jeweilige Workbook in den Vordergrund bzw. aktiviere es, damit das Makro wirklich immer ausgeführt wird? ?(
Sprich ich würde bei der Suche immer sporadisch das Workbook aktivieren und dort versuchen das Makro zu starten.

183 Beiträge seit 2004
vor 16 Jahren

"Activate" ist das Zauberwort ...

Ab und an auch bitte mal selbst :rtfm: benützen --> _Worksheet Interface

So einfach wie möglich, aber nicht einfacher. [Albert Einstein]

take a look at
* baer-torsten.de
* codinghints

J
JessicaLampe Themenstarter:in
6 Beiträge seit 2008
vor 16 Jahren

Danke für die schnelle und kompetente Hilfe! 👍