I’ve created a class that can iterate through all running Excel instances, and lookup by Hwnd, ProcessID, or a Process object. It also has a property to return the «Active» instance (according to the Marshal class) which is the instance that double-clicked Excel file icons will open in, as well as the top-most instance, which is the instance with the top-most window.
The code is below, but check this link for further description.
http://www.codeproject.com/Tips/1080611/Get-a-Collection-of-All-Running-Excel-Instances
This has not been tested on all versions of Excel or Windows.
Code:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
//Don't add the entire interop namespace, it will introduce some naming conflicts.
using xlApp = Microsoft.Office.Interop.Excel.Application;
using xlWin = Microsoft.Office.Interop.Excel.Window;
namespace ExcelExtensions {
/// <summary>
/// Collection of currently running Excel instances.
/// </summary>
public class ExcelAppCollection : IEnumerable<xlApp> {
#region Constructors
/// <summary>Initializes a new instance of the
/// <see cref="ExcelAppCollection"/> class.</summary>
/// <param name="sessionID">Windows sessionID to filter instances by.
/// If not assigned, uses current session.</param>
public ExcelAppCollection (Int32? sessionID = null) {
if (sessionID.HasValue && sessionID.Value < -1)
throw new ArgumentOutOfRangeException("sessionID");
this.SessionID = sessionID
?? Process.GetCurrentProcess().SessionId;
}
#endregion
#region Properties
/// <summary>Gets the Windows sessionID used to filter instances.
/// If -1, uses instances from all sessions.</summary>
/// <value>The sessionID.</value>
public Int32 SessionID { get; private set; }
#endregion
#region Accessors
/// <summary>Gets the Application associated with a given process.</summary>
/// <param name="process">The process.</param>
/// <returns>Application associated with process.</returns>
/// <exception cref="System.ArgumentNullException">process</exception>
public xlApp FromProcess(Process process) {
if (process == null)
throw new ArgumentNullException("process");
return InnerFromProcess(process);
}
/// <summary>Gets the Application associated with a given processID.</summary>
/// <param name="processID">The process identifier.</param>
/// <returns>Application associated with processID.</returns>
public xlApp FromProcessID(Int32 processID) {
try {
return FromProcess(Process.GetProcessById(processID));
}
catch (ArgumentException) {
return null;
}
}
/// <summary>Get the Application associated with a given window handle.</summary>
/// <param name="mainHandle">The window handle.</param>
/// <returns>Application associated with window handle.</returns>
public xlApp FromMainWindowHandle(Int32 mainHandle) {
return InnerFromHandle(ChildHandleFromMainHandle(mainHandle));
}
/// <summary>Gets the main instance. </summary>
/// <remarks>This is the oldest running instance.
/// It will be used if an Excel file is double-clicked in Explorer, etc.</remarks>
public xlApp PrimaryInstance {
get {
try {
return Marshal.GetActiveObject(MarshalName) as xlApp;
}
catch (COMException) {
return null;
}
}
}
/// <summary>Gets the top most instance.</summary>
/// <value>The top most instance.</value>
public xlApp TopMostInstance {
get {
var topMost = GetProcesses() //All Excel processes
.Select(p => p.MainWindowHandle) //All Excel main window handles
.Select(h => new { h = h, z = GetWindowZ(h) }) //Get (handle, z) pair per instance
.Where(x => x.z > 0) //Filter hidden instances
.OrderBy(x => x.z) //Sort by z value
.First(); //Lowest z value
return FromMainWindowHandle(topMost.h.ToInt32());
}
}
#endregion
#region Methods
/// <summary>Returns an enumerator that iterates through the collection.</summary>
/// <returns>
/// A <see cref="T:System.Collections.Generic.IEnumerator`1" />
/// that can be used to iterate through the collection.
/// </returns>
public IEnumerator<xlApp> GetEnumerator() {
foreach (var p in GetProcesses())
yield return FromProcess(p);
}
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
/// <summary>Gets all Excel processes in the current session.</summary>
/// <returns>Collection of all Excel processing in the current session.</returns>
public IEnumerable<Process> GetProcesses() {
IEnumerable<Process> result = Process.GetProcessesByName(ProcessName);
if (this.SessionID >= 0)
result = result.Where(p => p.SessionId == SessionID);
return result;
}
#endregion
//--------Implementation
#region Methods
private static xlApp InnerFromProcess(Process p) {
return InnerFromHandle(ChildHandleFromMainHandle(p.MainWindowHandle.ToInt32()));
}
private static Int32 ChildHandleFromMainHandle(Int32 mainHandle) {
Int32 handle = 0;
EnumChildWindows(mainHandle, EnumChildFunc, ref handle);
return handle;
}
private static xlApp InnerFromHandle(Int32 handle) {
xlWin win = null;
Int32 hr = AccessibleObjectFromWindow(handle, DW_OBJECTID, rrid.ToByteArray(), ref win);
return win.Application;
}
private static Int32 GetWindowZ(IntPtr handle) {
var z = 0;
for (IntPtr h = handle; h != IntPtr.Zero; h = GetWindow(h, GW_HWNDPREV))
z++;
return z;
}
private static Boolean EnumChildFunc(Int32 hwndChild, ref Int32 lParam) {
var buf = new StringBuilder(128);
GetClassName(hwndChild, buf, 128);
if (buf.ToString() == ComClassName) {
lParam = hwndChild;
return false;
}
return true;
}
#endregion
#region Extern Methods
[DllImport("Oleacc.dll")]
private static extern Int32 AccessibleObjectFromWindow(
Int32 hwnd, UInt32 dwObjectID, Byte[] riid, ref xlWin ptr);
[DllImport("User32.dll")]
private static extern Boolean EnumChildWindows(
Int32 hWndParent, EnumChildCallback lpEnumFunc, ref Int32 lParam);
[DllImport("User32.dll")]
private static extern Int32 GetClassName(
Int32 hWnd, StringBuilder lpClassName, Int32 nMaxCount);
[DllImport("User32.dll")]
private static extern IntPtr GetWindow(IntPtr hWnd, UInt32 uCmd);
#endregion
#region Constants & delegates
private const String MarshalName = "Excel.Application";
private const String ProcessName = "EXCEL";
private const String ComClassName = "EXCEL7";
private const UInt32 DW_OBJECTID = 0xFFFFFFF0;
private const UInt32 GW_HWNDPREV = 3;
//3 = GW_HWNDPREV
//The retrieved handle identifies the window above the specified window in the Z order.
//If the specified window is a topmost window, the handle identifies a topmost window.
//If the specified window is a top-level window, the handle identifies a top-level window.
//If the specified window is a child window, the handle identifies a sibling window.
private static Guid rrid = new Guid("{00020400-0000-0000-C000-000000000046}");
private delegate Boolean EnumChildCallback(Int32 hwnd, ref Int32 lParam);
#endregion
}
}
- Remove From My Forums
-
Question
-
I develop an UDF in C#:
public string GetActiveSheetName() { string Caller = null; Excel.Application oExcel = Marshal.GetActiveObject("Excel.Application") as Excel.Application; Caller = oExcel.ActiveSheet.Name; return Caller; }
This works fine if there is only one Excel process running. But if I have two or more instances of Excel are running, it still return first excel application’s
active sheet name.
Answers
-
I’ve been working this problem since your previous posting. After a few days of trying I finally got all the code to work. I had to use a number of Winn32 API to get it to work. Here are the steps I used. I not sure if there is an
easier way to do this.1) You need to find all the Excel.exe processes. This was pretty easy to do. I used the Net Library function Process.GetProcessesByName(@»excel») which returns an array of Processes
2) I then checked that the Excel.exe window was the active window using Win32 API user32.dll function IsWindowVisible.
3) I then had to write another function EnumChildWindows(ExcelWin, cb, ref hwndChild) which uses the Win32 API User32.dll function GetClassName(). I needed the child window to be able to extract the excel application from the window.
4) Next I used the Win32 API Oleacc.dll function AccessibleObjectFromWindow to get the excel object from the window.
5) Finally I overload the window object to an excel object and get the sheet name.
//using System.Runtime.InteropServices; //using System.Diagnostics; public delegate bool EnumChildCallback(int hwnd, ref int lParam); [System.Runtime.InteropServices.DllImport("User32.dll")] public static extern bool EnumChildWindows(IntPtr hWndParent, EnumChildCallback lpEnumFunc, ref IntPtr lParam); private static EnumChildCallback cb; [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] static extern bool IsWindowVisible(IntPtr hWnd); [DllImport("Oleacc.dll")] private static extern int AccessibleObjectFromWindow( IntPtr hwnd, uint dwObjectID, byte[] riid, [MarshalAs(UnmanagedType.IUnknown)]ref object winobj); [DllImport("User32.dll")] public static extern int GetClassName( int hWnd, StringBuilder lpClassName, int nMaxCount); public static bool EnumChildProc(int hwndChild, ref int lParam) { StringBuilder buf = new StringBuilder(128); GetClassName(hwndChild, buf, 128); if (buf.ToString() == "EXCEL7") { lParam = hwndChild; return false; } return true; } public static string GetActiveSheetName() { string activesheetname = null; IntPtr activeAppWindow = IntPtr.Zero; IntPtr ExcelWin; Process[] ExcelProcesses = Process.GetProcessesByName(@"excel"); foreach(Process MyProcess in ExcelProcesses) { ExcelWin = MyProcess.MainWindowHandle; IntPtr hwndChild = (IntPtr)0; cb = new EnumChildCallback(EnumChildProc); EnumChildWindows(ExcelWin, cb, ref hwndChild); if (IsWindowVisible(ExcelWin)) { const uint OBJID_NATIVEOM = 0xFFFFFFF0; Guid IID_IDispatch = new Guid("{00020400-0000-0000-C000-000000000046}"); object winptr = null; int As = AccessibleObjectFromWindow( hwndChild, OBJID_NATIVEOM, IID_IDispatch.ToByteArray(), ref winptr); Excel.Window excelwin = (Excel.Window)winptr; Excel.Application XlApp = excelwin.Application; Excel.Worksheet sht = (Excel.Worksheet)XlApp.ActiveSheet; activesheetname = sht.Name; break; } } return activesheetname; }
-
Edited by
Friday, December 30, 2011 4:37 AM
-
Marked as answer by
Calvin_Gao
Monday, January 9, 2012 5:38 AM
-
Edited by
We have created add in for office application by using VSTO in c# language.In this add in providing save as ribbon where user can save document on server location.
When user click on save as ribbon -> showing custom save as dialog box. here i want to show file name in text box as window shows.
It works by using
Microsoft.Office.Interop.Excel.Application loExcelApp;
loExcelApp = (Excel.Application)Marshal.GetActiveObject(«Excel.Application»);
txtFileName.Text = loExcelApp.ActiveWorkbook.Name;
but it fails when i start multiple excel application,it shows first application workbook name in second document while click on save as ribbon.
1.how to get name of current file name
2.Does it possible to get handle of parent window when i show save as dialog box.
kindly refer below screens.
Screen one showing proper working but in screen 2 document name is book 2 and showing name as book1 in text box.
Both are separate excel application running simultaneously.
kindly help me anyone
-
#2
Check first that Excel is running with Process.GetProcessesByName(«excel»).
Get the Application object with Marshal.GetActiveObject(«Excel.Application») and then get ActiveWorkbook.
-
#3
I can’t seem to get the activeworkbook or the sheet or much of anything else.
Can someone please point me to some books or tutorials that will help me understand this better?
-
#4
Are you looking to understand? Or looking to copy-and-paste some code? Post #2 explains the steps needed to accomplish the task that you asked about, assuming that you have the rudimentary understanding of C# and Office InterOp.
-
#5
Both actually, I’d like to understand how to use the object browser to find the objects that I need. I would also like some form of documentation (Ideally with examples) or using the excel interop. I understand the basics of c#.
I need to get whatever workbooks happen to be open into a combobox and then each workbooks worksheets into another combobox. Finally I want the program to detect which column of which worsheet the user is in and to transfer information from my c# portion into that active column at specific rows.
This is all I have so far:
public Microsoft.Office.Interop.Excel.Workbook ActiveWorkbook { get; }
private void button1_Click(object sender, EventArgs e)
{
Process.GetProcessesByName("excel");
Marshal.GetActiveObject("Excel.Application");
comboBox1.Items.Add(ActiveWorkbook);
}
The code gets to the last line then throws an exception.
-
#6
The Process.GetProcessesByName()
lets you check to see if Excel is already running. You need to check the returned value to see if Excel is found. If it is running, then you need to call Marshal.GetActiveObject()
to get the main Excel COM server instance’s interface as an Application. From that Application object, you can get the ActiveWorkbook object/interface.
As a quick aside, it’s not really a good idea to add an object directly into a WinForms UI element. You’ll likely want to get the string representation of the active workbook. Perhaps it’s name?
-
#7
Thank you for your input. I’ve been on another project for a bit and am now back to this issue. I will try to implement what you have said. I realize my wording must have been misleading, but yes, I realize that I’m trying to add a string expression of the object.
-
#8
Process.GetProcessesByName(«excel»); Marshal.GetActiveObject(«Excel.Application»);
These are methods that return a value, and it’s that value you have to work with.
using System.Diagnostics;
using System.Runtime.InteropServices;
using Excel = Microsoft.Office.Interop.Excel;
if (Process.GetProcessesByName("excel").Length > 0)
{
var app = (Excel.Application)Marshal.GetActiveObject("Excel.Application");
var book = app.ActiveWorkbook;
}
-
#9
@Frans : How is what you posted different from what has previously been described and shown on this thread?
-
#10
needs to write data to an already open excel workbook.
You’re going to struggle; excel locks its files upon opening to the extent that other apps can’t even read them so you won’t be able to do it by editing the file itself
Probably easiest to just start a timer and then tell the user then have 10 seconds to switch to make Excel the active app, then SendKeys the data you want. That can include keys like Ctrl+Home and Right Right Right to move to cell D1 etc
-
#11
You’re going to struggle; excel locks its files upon opening to the extent that other apps can’t even read them so you won’t be able to do it by editing the file itself
Probably easiest to just start a timer and then tell the user then have 10 seconds to switch to make Excel the active app, then SendKeys the data you want. That can include keys like Ctrl+Home and Right Right Right to move to cell D1 etc
Office automation is no problem, granted that it is ok with the interactive user. You can do whatever you want with the ‘book’ reference shown in post 8.
SendKeys is last resort for automation.
-
#12
The code in post 8 works just fine. No need for dynamic
late-binding.
-
#13
Posts #2 says «and then get the ActiveWorkbook». It answers the question.
My response in post #4 asking about understanding vs just copying and pasting is relevant because this site meant to be a learning site — not a question and answer site. And further in post #4, I redirected the user back to post #2 which is all the user actually needs to know if they are just seeking to understand (vs. just copy and paste code).
And then in post #6, I elaborated on what post #2 was describing in broad term. It also answers the question, since post #2 answered the question. Post #6 just goes into more details without giving the OP code that they can copy and paste. My second paragraph in post #6 was not about not using WinForms. It was about not putting the active workbook object directly into the WinForms combobox.
The code in post #8 works just fine without using dynamic
Your post #9 is still there. I don’t know why you think it’s been deleted.
Get Active Workbook or Worksheet Name Path FullName in Excel VBA
Description:
When we are working with workbooks and worksheets, some times we may required to Get Active Workbook or Worksheet Name, Path of the workbook to know the directory, FullName(Complete path) of the workbook to know the location of the workbook, selected Range address in active sheet or selected Cell address in active sheet using Excel VBA.
Solution(s):
You can get Active Workbook Or Worksheet Name by using Name property of the workbook or worksheet.
Get Active Workbook or Worksheet Name – Example Cases:
- Get an Active Workbook Name
- Get an Active Workbook Path
- Get an Active Workbook FullName
- Get an Active Worksheet Name
- Get an Active Range Address
- Get an Active Cell Address
Get an Active Workbook Name
You can use ActiveWorkbook property to return the active workbook name. You can use the following code to get the name of the Active Workbook
Code:
Sub DisplayWorkbookName() MsgBox ActiveWorkbook.Name, vbInformation, "Workbook Name" End Sub
Output:
Instructions:
- Open an excel workbook
- Press Alt+F11 to open VBA Editor
- Insert a new module from Insert menu
- Copy the above code and Paste in the code window
- Press F5 to see the output
- You should see output as shown above
- Save the file as macro enabled workbook
Get an Active Workbook Path
You can use ActiveWorkbook property to return the active workbook Path.You can use the following code to Get Active Workbook Path to know the workbook directory.
Code:
Sub DisplayWorkbookPath() MsgBox ActiveWorkbook.Path, vbInformation, "Workbook Path" End Sub
Output:
Instructions:
- Open an excel workbook
- Press Alt+F11 to open VBA Editor
- Insert a new module from Insert menu
- Copy the above code and Paste in the code window
- Press F5 to see the output
- You should see output as shown above
- Save the file as macro enabled workbook
Get an Active Workbook FullName
You can use ActiveWorkbook property to return the active workbook FullName. You can use the following code to get Active Workbook FullName to know the location of workbook.
Code:
Sub DisplayWorkbookFullName() MsgBox ActiveWorkbook.FullName, vbInformation, "Workbook Complete Path" End Sub
Output:
Instructions:
- Open an excel workbook
- Press Alt+F11 to open VBA Editor
- Insert a new module from Insert menu
- Copy the above code and Paste in the code window
- Press F5 to see the output
- You should see output as shown above
- Save the file as macro enabled workbook
Get an Active Worksheet Name
You can use ActiveSheet property to return the ActiveSheet Name. You can use the following code to get Active Worksheet Name.
Code:
Sub DisplayWorkSheetName() MsgBox ActiveSheet.Name, vbInformation, "Active Sheet Name" End Sub
Output:
Instructions:
- Open an excel workbook
- Press Alt+F11 to open VBA Editor
- Insert a new module from Insert menu
- Copy the above code and Paste in the code window
- Press F5 to see the output
- You should see output as shown above
- Save the file as macro enabled workbook
Get an Active(Selected) Range Address
You can use Address property of the selected range(Selection Method). You can use the following code to get the selected range address in active sheet.
Code:
Sub SelectedRangeAddress() 'Variable Declaration Dim MyRange As String 'Assign Selected Range Address to variable MyRange = Selection.Address 'Display Output MsgBox MyRange, vbInformation, "Range Address" '-----------------------(OR)------------------ MsgBox Selection.Address, vbInformation, "Range Address" End Sub
Output:
Instructions:
- Open an excel workbook
- Press Alt+F11 to open VBA Editor
- Insert a new module from Insert menu
- Copy the above code and Paste in the code window
- Select a range from B2 to E11 in active sheet
- Goto code window and Press F5 to see the output
- You should see output as shown above
- Save the file as macro enabled workbook
Get an Active(Selected) Cell Address
You can use Address property of the selected cell(Selection Method). By using the following code you can get the selected cell address in active sheet.
Code:
Sub SelectedCellAddress() 'Variable Declaration Dim MyCell As String 'Assign Selected Range Address to variable MyCell = Selection.Address 'Display Output MsgBox MyCell, vbInformation, "Cell Address" '-----------------------(OR)------------------ MsgBox Selection.Address, vbInformation, "Cell Address" End Sub
Output:
Instructions:
- Open an excel workbook
- Press Alt+F11 to open VBA Editor
- Insert a new module from Insert menu
- Copy the above code and Paste in the code window
- Select a cell F5 in active sheet
- Goto code window and Press F5 to see the output
- You should see output as shown above
- Save the file as macro enabled workbook
A Powerful & Multi-purpose Templates for project management. Now seamlessly manage your projects, tasks, meetings, presentations, teams, customers, stakeholders and time. This page describes all the amazing new features and options that come with our premium templates.
Save Up to 85% LIMITED TIME OFFER
All-in-One Pack
120+ Project Management Templates
Essential Pack
50+ Project Management Templates
Excel Pack
50+ Excel PM Templates
PowerPoint Pack
50+ Excel PM Templates
MS Word Pack
25+ Word PM Templates
Ultimate Project Management Template
Ultimate Resource Management Template
Project Portfolio Management Templates
Related Posts
VBA Reference
Effortlessly
Manage Your Projects
120+ Project Management Templates
Seamlessly manage your projects with our powerful & multi-purpose templates for project management.
120+ PM Templates Includes:
One Comment
-
Prabhaker
July 5, 2017 at 4:10 PM — Replythank you this is helpful
Effectively Manage Your
Projects and Resources
ANALYSISTABS.COM provides free and premium project management tools, templates and dashboards for effectively managing the projects and analyzing the data.
We’re a crew of professionals expertise in Excel VBA, Business Analysis, Project Management. We’re Sharing our map to Project success with innovative tools, templates, tutorials and tips.
Project Management
Excel VBA
Download Free Excel 2007, 2010, 2013 Add-in for Creating Innovative Dashboards, Tools for Data Mining, Analysis, Visualization. Learn VBA for MS Excel, Word, PowerPoint, Access, Outlook to develop applications for retail, insurance, banking, finance, telecom, healthcare domains.
Page load link
Go to Top
Return to VBA Code Examples
In this Article
- ActiveSheet
- Activate Worksheet (Setting the ActiveSheet)
- ActiveSheet Name
- Selected Sheets vs ActiveSheet
- Select Worksheet
- Select Worksheet by Tab Name
- Select Worksheet by Index Number
- Select Worksheet With VBA Code Name
- Select Current Worksheet
- More Activate / Select Sheet Examples
- Set ActiveSheet to Variable
- Change ActiveSheet Name
- With ActiveSheet
- Loop Through Selected Sheets
- GoTo Next Sheet
- VBA Coding Made Easy
This article will discuss the ActiveSheet object in VBA. It will also discuss how to activate, select, and go to Worksheets (& much more). Read our full VBA Worksheets Guide for more information about working with worksheets in VBA.
ActiveSheet
In VBA, ActiveSheet refers to the currently active Worksheet. Only one Sheet may be active at a time.
Activate Worksheet (Setting the ActiveSheet)
To set the ActiveSheet use Worksheet.Activate:
Worksheets("Input").Activate
The Activate Sheet command will actually “go to” the sheet, changing the visible Sheet.
The above example uses the Sheet (Tab) name. Instead you can use the VBA code name for the worksheet:
Sheet1.Activate
ActiveSheet Name
To get the ActiveSheet Name:
msgbox ActiveSheet.name
Selected Sheets vs ActiveSheet
At any point in time, only one Sheet can be the ActiveSheet. However, multiple Worksheets can be selected at once.
When multiple Worksheets are selected only the “top-most” Worksheet is considered active (the ActiveSheet).
Select Worksheet
If you would like to select a worksheet instead of activating it. Use .Select instead.
Select Worksheet by Tab Name
This selects a Worksheet based on it’s Sheet Tab Name
Sheets("Input").Select
Select Worksheet by Index Number
This selects a Worksheet based on it’s position relative to other tabs
Worksheets(1).Select
Select Worksheet With VBA Code Name
Sheet1.Select
Selecting worksheets by code name can prevent errors caused by worksheet name changes.
Select Current Worksheet
To select the current Worksheet, use the ActiveSheet object:
ActiveSheet.Select
More Activate / Select Sheet Examples
VBA Programming | Code Generator does work for you!
Set ActiveSheet to Variable
This will assign the ActiveSheet to a Worksheet Object Variable.
Dim ws As Worksheet
Set ws = ActiveSheet
Change ActiveSheet Name
This will change the ActiveSheet Name.
ActiveSheet.Name = "NewName"
With ActiveSheet
Using the With Statement allows you to streamline your code when working with objects (such as Sheets or ActiveSheet).
With ActiveSheet
.Name = "StartFresh"
.Cells.Clear
.Range("A1").Value = .Name
End With
Notice how you don’t need to repeat “ActiveSheet” before each line of code. This can be a huge time saver when working with a long list of commands.
Loop Through Selected Sheets
The following macro will Loop through all selected sheets, displaying their names.
Sub GetSelectedSheetsName()
Dim ws As Worksheet
For Each ws In ActiveWindow.SelectedSheets
MsgBox ws.Name
Next ws
End Sub
GoTo Next Sheet
This code will go to the next Sheet. If the ActiveSheet is the last Sheet, then it will go to the first Sheet in the Workbook.
If ActiveSheet.Index = Worksheets.Count Then
Worksheets(1).Activate
Else
ActiveSheet.Next.Activate
End If
VBA Coding Made Easy
Stop searching for VBA code online. Learn more about AutoMacro – A VBA Code Builder that allows beginners to code procedures from scratch with minimal coding knowledge and with many time-saving features for all users!
Learn More!
<<Return to VBA Examples
Update
I’ve created a new article that describes how to do the same things as this article, but with a reusable assembly instead of everything in one class, and there is a demo proejct and walkthrough as well. Check it out here.
Introduction
If you write a lot of .NET Excel automation, particularly for use on servers where there may be multiple instances of Excel running at a time, you may find yourself needing to access a specific instance of Excel, not just the «active» one.
I have spent a lot of time on forums and Q&A sites trying to find a way to iterate over all running instances, and select specific instances by Hwnd
or ProcessID
, but have not yet found a satisfactory article. After a while of piecing different answers together, I believe I have a class that can provide this functionality for anyone in a similar situation.
A lot of credit goes to the anonymous article at the link below, as well as some hints from various users of StackOverflow.
Background
You will need an intermediate grasp of C# for this project, and some familiarity with Windows processes and window handles. There is also some usage of LINQ and lambda expressions, but only in a few places. You actually don’t need to know much about Excel automation, other than knowing what the Microsoft.Office.Interop.Excel.Application
class is.
Several parts of the private implementation of the class involve extern
calls to the Win32 API, which you don’t necessarily need to understand to use this class. I am not very familiar with the Win32 API myself, but learned a good bit about it in putting this class together.
Please let me know if the code violates any best practices for dealing with Win32.
IMPORTANT: This code has not been tested on all versions of Excel or Windows. (Please help me test them all out.) I believe this code may be particularly prone to issues based on different Excel and Windows versions.
Tested environments:
- Windows 7 64-bit with Excel 2016 32-bit
- Windows 7 64-bit with parallel instances of Excel 2010 32-bit and Excel 2013 32-bit.
Using the Code
The class below can be used alongside the Microsoft primary interop assembly for Excel, to get a Microsoft.Office.Interop.Excel.Application
object for any running instance of Excel.
I’ve split the class into two partial class files, to breakup what would otherwise be a 200-line file. The first part is the public
interface, and the second is the private
implementation.
Public Interface
The publicly visible interface is pretty simple, and has the following members:
- Constructor — This takes a nullable
Int32
as a parameter, which defaults tonull
. The value is used to filter Excel instances by WindowssessionID
. Ifnull
, the class’sSessionID
property will be set to the currentsessionID
. SessionID
— This property is used to filter Excel instances by WindowssessionID
. This is very important when working with servers where multiple users may be using Excel at once.- If
-1
, the collection will give access to instances from all sessions. - If a valid
sessionID
, the collection will give access to all Excel instances running in that session. - If not a valid
sessionID
, the collection will always be empty. No exception is thrown.
- If
- Accessors
FromProcess
— This method takes a reference to aProcess
and returns the Excel instance of thatProcess
, ornull
if theProcess
is not an Excel instance.FromProcessID
— This method takes aprocessID
and returns the Excel instance of the correspondingProcess
, ornull
if the ID is invalid or does not correspond to an Excel instance.FromMainWindowHandle
— This method takes theHwnd
value of the main window of an Excel instance, and returns the corresponding Excel instance, ornull
if theHwnd
is invalid or does not correspond to an Excel instance.PrimaryInstance
— This property returns the first-created Excel instance, ornull
if there are none. If a user double-clicks an Excel file icon, this will be the instance the file opens in.TopMostInstance
— This property returns the Excel instance with the top-most visible window, ornull
if there are none. This will normally be the last instance selected by a user.
- Methods
GetEnumerator
— This method returns a collection of all Excel instances, filtering bySessionID
(ifSessionID
is not-1
).GetProcesses
— This method returns a collection of allProcess
objects of Excel instances, filtering bySessionID
(ifSessionID
is not-1
).
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; using xlApp = Microsoft.Office.Interop.Excel.Application; namespace ExcelExtensions { public partial class ExcelAppCollection : IEnumerable<xlApp> { #region Constructors public ExcelAppCollection (Int32? sessionID = null) { if (sessionID.HasValue && sessionID.Value < -1) throw new ArgumentOutOfRangeException("sessionID"); this.SessionID = sessionID ?? Process.GetCurrentProcess().SessionId; } #endregion #region Properties public Int32 SessionID { get; private set; } #endregion #region Accessors public xlApp FromProcess(Process process) { if (process == null) throw new ArgumentNullException("process"); return InnerFromProcess(process); } public xlApp FromProcessID(Int32 processID) { try { return FromProcess(Process.GetProcessById(processID)); } catch (ArgumentException) { return null; } } public xlApp FromMainWindowHandle(Int32 mainHandle) { return InnerFromHandle(ChildHandleFromMainHandle(mainHandle)); } public xlApp PrimaryInstance { get { try { return Marshal.GetActiveObject(MarshalName) as xlApp; } catch (COMException) { return null; } } } public xlApp TopMostInstance { get { var topMost = GetProcesses() .Select(p => p.MainWindowHandle) .Select(h => new { h = h, z = GetWindowZ(h) }) .Where(x => x.z > 0) .OrderBy(x => x.z) .First(); return FromMainWindowHandle(topMost.h.ToInt32()); } } #endregion #region Methods public IEnumerator<xlApp> GetEnumerator() { foreach (var p in GetProcesses()) yield return FromProcess(p); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } public IEnumerable<Process> GetProcesses() { IEnumerable<Process> result = Process.GetProcessesByName(ProcessName); if (this.SessionID >= 0) result = result.Where(p => p.SessionId == SessionID); return result; } #endregion } }
Private Implementation
As mentioned in the introduction, I am not an expert on the Win32 API. Parts of the private
implementation are still a bit mysterious to me and may violate best practices for using it. That being said, it has been reliable as far as I’ve used it.
- Methods
InnerFromProcess
— This method takes a reference to aProcess
and returns the correspondingMicrosoft.Office.Interop.Excel.Application
object.ChildHandleFromMainHandle
— This method takes theHwnd
of aProcess
orApplication
object and returns a child window’sHwnd
.InnerFromHandle
— This method takes theHwnd
of a child window of anApplication
object and returns theApplication
.GetWindowZ
— This method takes theHwnd
of a window and returns itsz
value.EnumChildFunc
— This method is used by theEnumChildWindows
method to get child windowHwnd
s.
- External Methods
AccessibleObjectFromWindow
— This method takes theHwnd
of an Excel window, as well as some of the constants below, and returns (through itsref
parameter) a reference to aWindow
object, which can then be used to get its parentApplication
object.- It does not work if you pass it the value of a
Application
‘sHwnd
property; it must be a specific workbook’s window’sHwnd
. This may only be the case on Excel 2013 or newer, where there is no main Excel window.
- It does not work if you pass it the value of a
EnumChildWindows
— This method takes theHwnd
of the main window of an Excel instance and anEnumChildCallback
delegate as parameters, and returns (through itsref
parameter) theHwnd
of a child window, which can be used byAccessibleObjectFromWindow
.GetClassName
— This method is used by theEnumChildCallback
delegate that is passed toEnumChildWindows
. I believe it gets the details of theWindow
class internally so that anHwnd
can be returned.GetWindow
— This method takes anHwnd
and a constant as parameters. The constant used determines how to get otherHwnd
s based on the providedHwnd
. UsingGW_HWNDPREV
returns theHwnd
of the window directly above (z position) the givenHwnd
. This is used to get theTopMostInstance
.
- Constants and Delegates
MarshalName
— This constant is required to get the «active» instance (PrimaryInstance
) from theSystem.Runtime.InteropServices.Marshal
class.ProcessName
— This constant is required to get Excel processes by name fromSystem.Diagnostics.Process
.ComClassName
— This constant is required forEnumChildFunc
method, which is used by theEnumChildWindow
method from the Win32 API.DW_OBJECTID
— This constant is required for theAccessibleObjectFromWindow
method from the Win32 API.GW_HWNDPREV
— This constant is required for getting windowz
(depth) values from theGetWindow
method from the Win32 API. I copied a bit of the Microsoft documentation into the code comments.rrid
— This pseudo-constant is required for theAccesibleObjectFromWindow
from the Win32 API.EnumChildCallback
— This delegate is implemented by theEnumChildFunc
method and is requied for theEnumChildWindow
method from the Win32 API.
using System; using System.Diagnostics; using System.Runtime.InteropServices; using System.Text; using xlApp = Microsoft.Office.Interop.Excel.Application; using xlWin = Microsoft.Office.Interop.Excel.Window; namespace ExcelExtensions { public partial class ExcelAppCollection { #region Methods private static xlApp InnerFromProcess(Process p) { return InnerFromHandle(ChildHandleFromMainHandle(p.MainWindowHandle.ToInt32())); } private static Int32 ChildHandleFromMainHandle(Int32 mainHandle) { Int32 handle = 0; EnumChildWindows(mainHandle, EnumChildFunc, ref handle); return handle; } private static xlApp InnerFromHandle(Int32 handle) { xlWin win = null; Int32 hr = AccessibleObjectFromWindow(handle, DW_OBJECTID, rrid.ToByteArray(), ref win); return win.Application; } private static Int32 GetWindowZ(IntPtr handle) { var z = 0; for (IntPtr h = handle; h != IntPtr.Zero; h = GetWindow(h, GW_HWNDPREV)) z++; return z; } private static Boolean EnumChildFunc(Int32 hwndChild, ref Int32 lParam) { var buf = new StringBuilder(128); GetClassName(hwndChild, buf, 128); if (buf.ToString() == ComClassName) { lParam = hwndChild; return false; } return true; } #endregion #region Extern Methods [DllImport("Oleacc.dll")] private static extern Int32 AccessibleObjectFromWindow( Int32 hwnd, UInt32 dwObjectID, Byte[] riid, ref xlWin ptr); [DllImport("User32.dll")] private static extern Boolean EnumChildWindows( Int32 hWndParent, EnumChildCallback lpEnumFunc, ref Int32 lParam); [DllImport("User32.dll")] private static extern Int32 GetClassName( Int32 hWnd, StringBuilder lpClassName, Int32 nMaxCount); [DllImport("User32.dll")] private static extern IntPtr GetWindow(IntPtr hWnd, UInt32 uCmd); #endregion #region Constants & delegates private const String MarshalName = "Excel.Application"; private const String ProcessName = "EXCEL"; private const String ComClassName = "EXCEL7"; private const UInt32 DW_OBJECTID = 0xFFFFFFF0; private const UInt32 GW_HWNDPREV = 3; private static Guid rrid = new Guid("{00020400-0000-0000-C000-000000000046}"); private delegate Boolean EnumChildCallback(Int32 hwnd, ref Int32 lParam); #endregion } }
Points of Interest
Please let me know if you find this class helpful (or terrible). I’m especially interested in issues with older version of Excel (pre-2013), multiple versions of Excel on one machine, or multiple users on one server. If you have any further insight into how the Win32 API is working behind the scenes, I would also like to know more about that. Any feedback is highly appreciated.
Further Developments
I have recently started working on a WPF application called ExcelBrowser that allows users to easily browse through multiple Excel instances, their workbooks, and sheets. Part of the implementation of this application is directly decended from the class described in this article. Check it out at github.com/JamesFaix/ExcelBrowser. Also, note that the solution uses C#6/.NET 4.6.1. As of writing this, I also need to catch up on some code comments, so bear with me.
The parts related to this article are in the ExcelBrowser.Interop project of the solution. All extern
methods are encapsulated in the NativeMethods
class, the Session
class represents a collection of all running Applications
and all running Processes
with the name «Excel». AppFactory
provides methods for getting specific Application
instances. Some other parts of this class are also in the ApplicationExtensionMethods
and ProcessExtensionMethods
classes.
History
- Added «Further Developments» section 11/20/16
- Posted 2/23/2016
I am a professional developer, but I really create software because it’s fun. I’ve always been interested in deconstructing complex systems, and software engineering has proven to be an inexhaustable supersystem of such systems. In the past I’ve also spent time focusing on music composition, audio engineering, electronics, game design, history, and philosophy.
My strongest languages are English and C#, in fact I’m a Microsoft Certified Professional for «Programming in C#». I do not have any certifications for English, so please trust me there. I’ve spent a lot of time working on Windows desktop applications, particularly for interacting with SQL Server or automating Microsoft Office programs, using technologies such as C#, VB.NET, VBA, T-SQL, WinForms, WPF, ADO.NET, the MS Office PIA’s, ExcelDNA, EPPlus, and Crystal Reports. I’ve also done some web development using JavaScript, HTML, CSS, TypeScript, ASP.NET, WCF, jQuery, and requirejs. I am very interested in functional programming (F#, Haskell, Clojure), and try to use C# and JavaScript in a «functional» way at times, but I haven’t had the opportunity to use a functional language for a serious project yet.
4 ответа
Примерно через пол дня игры я наконец понял, как сделать эту работу, чтобы вы могли защелкнуться на открытой копии Excel. Мои пользователи очень жаловались на то, что слишком много экземпляров Excel открыты.
Вот фрагмент того, что я сделал, чтобы заставить его работать:
_Application excelApp;
try
{
excelApp = (_Application)Marshal.GetActiveObject("Excel.Application");
}
catch(Exception)
{
// this is important. If Excel is not running, GetActiveObject will throw
// an exception
excelApp = null;
}
if( excelApp == null )
{
excelApp = new ApplicationClass();
}
Я некоторое время гонялся за этим, и, наконец, у меня было время уйти и понять это.
Jager
20 янв. 2010, в 00:29
Поделиться
Чтобы иметь возможность ссылаться на объектную модель Excel как «Excel», вы можете создать псевдоним с помощью оператора using
в верхней части пространства имен (или документа) следующим образом:
using Excel = Microsoft.Office.Interop.Excel;
После этого вы можете ссылаться на Excel.Application
вместо длинного Microsoft.Office.Interop.Excel.Application
.
Что касается того, почему ваш вызов в Marshal.GetActiveObject не работает, я не могу сказать точно. Несколько мыслей:
(1) Вы уверены, что есть уже запущенный экземпляр Excel? Если нет, то создайте новое приложение Excel:
Excel.Application xlApp = new Excel.Application();
(2) Если определенная версия приложения Excel уже запущена, возможно, экземпляр Excel еще не добавлен в таблицу рабочих столов (ROT), если приложение Excel никогда не теряло фокус. См. Visual С#.NET Ошибка присоединения к запуску экземпляра приложения Office для получения дополнительной информации об этом. Я считаю, что метод Marshal.GetActiveObject должен генерировать исключение в этом случае — не тихо возвращать null — но это все еще кажется потенциально релевантным.
Надеюсь, это поможет…
Mike
Mike Rosenblum
27 авг. 2009, в 15:59
Поделиться
Я также работал над надстройками Excel, в пространствах имен использовать
using Excel=Microsoft.Interop.Office.Excel;
вместо Microsoft.Interop.Office.Excel используйте приведенную выше строку, для большего понимания посмотрите мой код:
using System.IO;
using System.Windows.Forms;
using System.Xml;
using System.Xml.Serialization;
using Excel = Microsoft.Office.Interop.Excel;
using Microsoft.Office.Interop.Excel;
using static MMCAPP2010.Profile;
using System.Runtime.InteropServices;
namespace MMCAPP2010
{
public class ExcelTemplateLoad
{
public void DeserializeObject(string filename)
{
Excel.Application instance;
Workbook wb = null;
try
{
//getting the current running instance of an excel application
instance = (Excel.Application)Marshal.GetActiveObject("Excel.Application");
}
catch
{
instance = new Excel.Application();
}
//opening the template
wb = instance.Workbooks.Open(@"C:UsersU1152927Downloadssample.xltx");
Hemanth Mannam
07 авг. 2018, в 11:08
Поделиться
Ещё вопросы
- 1Закрывает ли файл открытие и выполнение операции над этим файлом в одной строке?
- 0MySQL поиск значения в двух столбцах и сумма третьего столбца
- 0Как настроить положение определенных элементов / элементов / контента на странице с помощью CSS
- 1Какое официальное название для этого алгоритма?
- 0Rails API использует Devise для аутентификации
- 0Как изменить цвет индикатора прогресса
- 0получение пространства следующего размера в памяти в C #
- 0«Docker-compose up» в Windows завершился неудачно с ошибкой на шаге контейнера Mysql
- 0Индексы для большой таблицы MYSQL
- 1Выравнивание текста в java JPanel
- 0css позиционируется как сетка
- 0Я хочу строку, где максимальный идентификатор
- 0полоса прокрутки slimscroll не видна
- 0перенос слов в машинах MAC
- 1Flash-сообщения Node.js не отображаются на странице
- 1Публикация на Android Маркете: атрибут android: icon: атрибут не является строковым значением
- 0JQuery Colorbox с JQuery Validation не работает
- 0часовой пояс для конкретной страны php [дубликаты]
- 0Вернуть несколько строк из подзапроса
- 1Поворот серии Multiindex в DataFrame
- 0JQuery Slideshow error. Картинки перечисляют вниз по странице, а не перетасовывают. Не могу найти ошибку
- 1AmChart умножает значения даты
- 1Заставьте dijit.form.currencytextbox принимать отрицательное значение с одной цифрой после десятичной
- 1Должен ли я создать экземпляр класса внутри класса?
- 1Загрузка текста richTextBox в массив
- 1ASP.NET MVC Выберите значение списка с помощью ключа
- 1Ошибка при загрузке log4net
- 0Выйти из Facebook Чат через JavaScript
- 0Jquery событие не работает во второй раз, когда я называю это
- 0О сегментации в OPENCV
- 0Отображать поля, когда пользователь выбирает конкретное значение из поля со списком — Проблема логики проверки
- 1Как выполнить итерацию по карте со значением Map <String, Integer>, объявленной как Map <String, Map <String, Integer >>, с использованием языка выражений?
- 1Android Google Plus SDK: индикатор прогресса (ProgressBar) никогда не останавливается после нажатия кнопки +1
- 0JQuery, чтобы скрыть и показать div с помощью выпадающего
- 1Как сделать прозрачную область неприкосновенной для ImageView в Android?
- 1Сохранить результаты в файл CSV без открытого CSV с Python
- 1Как разобрать XML дочерний узел
- 1Как открыть проект из GitHub в Android-студии? Проблемы с Maven и Android поддержки библиотек.
- 1ArrayList не добавляет объекты?
- 1Pandas Series.rename не отражено в столбцах DataFrame
- 0Как избежать конфликтов в рамках веб-приложения
- 1Можно ли восстановить расширения-атрибуты из скрытых элементов управления asp.net?
- 1Angular использует неправильный роутер-розетку
- 0Перезагрузить iframe после закрытия галереи изображений
- 1Цикл Python для создания словаря
- 1Как получить суммирование в группе по запросу linq
- 0мобильная версия сайта номер мобильного телефона всегда белого цвета
- 1Имя токена «приложение» не определено. колба
- 0Не могу понять, почему функция не будет работать
- 1Как сканировать все диски, чтобы найти конкретное расширение файла в Java?