【发布时间】:2010-07-01 09:52:00
【问题描述】:
有没有办法查看 Excel 工作簿(例如 DataSheet.xls)是否打开(正在使用)?如果该工作簿已打开,我想关闭它。
【问题讨论】:
标签: c# visual-studio-2005 excel-2007
有没有办法查看 Excel 工作簿(例如 DataSheet.xls)是否打开(正在使用)?如果该工作簿已打开,我想关闭它。
【问题讨论】:
标签: c# visual-studio-2005 excel-2007
正确的方法是检查 Application.Workbooks 对象。在 VBA 中你会写:
Dim wb as Workbook
On Error Resume Next '//this is VBA way of saying "try"'
Set wb = Application.Workbooks(wbookName)
If err.Number = 9 then '//this is VBA way of saying "catch"'
'the file is not opened...'
End If
换句话说,Workbooks 是所有打开的工作簿的数组(或 VBA 术语中的集合)。
在 C# 中,以下代码有效:
static bool IsOpened(string wbook)
{
bool isOpened = true;
Excel.Application exApp;
exApp = (Excel.Application)System.Runtime.InteropServices.Marshal.GetActiveObject("Excel.Application");
try
{
exApp.Workbooks.get_Item(wbook);
}
catch (Exception)
{
isOpened = false;
}
return isOpened;
}
您可能希望自己传递对 Excel.Application 的引用。
【讨论】:
试试这个:
try
{
Stream s = File.Open(FileName, FileMode.Open, FileAccess.Read, FileShare.None);
s.Close();
return true;
}
catch (Exception)
{
return false;
}
这将尝试以独占方式打开文件。如果文件已经打开,它将引发异常,然后您可以(尝试)关闭它并继续。
【讨论】:
Marshal.GetActiveObject 只获取对象,如果 excel 在另一个对象中打开怎么办?很好的解决方案。 +1
对于任何对避免使用 try-catch 的单一衬里感兴趣的人...
bool wbOpened = ((Application)Marshal.GetActiveObject("Excel.Application")).Workbooks.Cast<Workbook>().FirstOrDefault(x => x.Name == "Some Workbook.xlsx") != null;
或者使用完全限定的名称...
bool wbOpened = ((Microsoft.Office.Interop.Excel.Application)System.Runtime.InteropServices.Marshal.GetActiveObject("Excel.Application")).Workbooks.Cast<Microsoft.Office.Interop.Excel.Workbook>().FirstOrDefault(x => x.Name == "Some Workbook.xlsx") != null;
当然,您可能希望将其拆分一下。重点是使用 LINQ 而不是 try-catch 来检查工作簿是否存在。
注意 1: 如果没有打开 Excel 实例,Marshal.GetActiveObject("Excel.Application") 将抛出错误。因此,除非另有保证或处理,否则应始终在 try-catch 内。
bool wbOpened = false;
try
{
wbOpened = ((Application)Marshal.GetActiveObject("Excel.Application")).Workbooks.Cast<Workbook>().FirstOrDefault(x => x.Name == "Some Workbook.xlsx") != null;
}
catch
{
...
}
注意 2:Marshal.GetActiveObject("Excel.Application") 只会返回一个 Excel 实例。如果您需要搜索任何可能的 Excel 实例,那么下面的代码可能是更好的选择。
更好的选择
如果您不介意添加帮助程序类,下面的代码可能是更好的选择。除了能够搜索任何打开的 Excel 实例外,它还允许您检查完整路径并在找到时返回实际的工作簿对象。如果没有打开 Excel 实例,它还可以避免引发错误。
用法应该是这样的......
If (IsOpenedWB_ByName("MyWB.xlsx"))
{
....
}
或
Workbook wb = GetOpenedWB_ByPath("C:\MyWB.xlsx")
if (wb.obj == null) //If null then Workbook is not already opened
{
...
}
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Office.Interop.Excel;
using System.Runtime.InteropServices.ComTypes;
public class WBHelper
{
public static bool IsOpenedWB_ByName(string wbName)
{
return (GetOpenedWB_ByName(wbName) != null);
}
public static bool IsOpenedWB_ByPath(string wbPath)
{
return (GetOpenedWB_ByPath(wbPath) != null);
}
public static Workbook GetOpenedWB_ByName(string wbName)
{
return (Workbook)GetRunningObjects().FirstOrDefault(x => (System.IO.Path.GetFileName(x.Path) == wbName) && (x.Obj is Workbook)).Obj;
}
public static Workbook GetOpenedWB_ByPath(string wbPath)
{
return (Workbook)GetRunningObjects().FirstOrDefault(x => (x.Path == wbPath) && (x.Obj is Workbook)).Obj;
}
public static List<RunningObject> GetRunningObjects()
{
// Get the table.
List<RunningObject> roList = new List<RunningObject>();
IBindCtx bc;
CreateBindCtx(0, out bc);
IRunningObjectTable runningObjectTable;
bc.GetRunningObjectTable(out runningObjectTable);
IEnumMoniker monikerEnumerator;
runningObjectTable.EnumRunning(out monikerEnumerator);
monikerEnumerator.Reset();
// Enumerate and fill list
IMoniker[] monikers = new IMoniker[1];
IntPtr numFetched = IntPtr.Zero;
List<object> names = new List<object>();
List<object> books = new List<object>();
while (monikerEnumerator.Next(1, monikers, numFetched) == 0)
{
RunningObject running;
monikers[0].GetDisplayName(bc, null, out running.Path);
runningObjectTable.GetObject(monikers[0], out running.Obj);
roList.Add(running);
}
return roList;
}
public struct RunningObject
{
public string Path;
public object Obj;
}
[System.Runtime.InteropServices.DllImport("ole32.dll")]
static extern void CreateBindCtx(int a, out IBindCtx b);
}
我从here改编了上面代码中的GetRunningObjects()方法。
【讨论】:
如果 Excel 应用程序当前未运行,马丁的回答将不起作用。 您可能需要修改代码如下:
static bool IsOpened(string wbook)
{
bool isOpened = true;
Excel.Application exApp;
try
{
// place the following line here :
exApp = (Excel.Application)System.Runtime.InteropServices.Marshal.GetActiveObject("Excel.Application");
// because it throws an exception if Excel is not running.
exApp.Workbooks.get_Item(wbook);
}
catch (Exception)
{
isOpened = false;
}
return isOpened;
}
感谢您的关注。问候
【讨论】:
这不是特别好 - 我们将尝试打开文件并检查异常是否失败。我不确定您在 C# 中还有其他选择。
但是,重要的是只处理正确的异常:基本上我们尝试打开不允许共享的文件。如果它失败了,并且我们得到了正确的异常类型并且我们在异常中得到了正确的消息,那么我们就知道它是打开的。
// open the file with no sharing semantics (FileShare.None)
using (FileStream stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.None))
{
try
{
stream.ReadByte();
return false;
}
catch (IOException ex)
{
// catch ONLY the exception we are interested in, and check the message too
if (ex.Message != null
&& ex.Message.Contains("The process cannot access the file"));
{
return true;
}
// if the message was incorrect, this was not the IOException we were looking for. Rethrow it.
throw;
}
}
显然,对于在未来版本的 .Net 中更改的异常消息,这种方法很脆弱。您可能希望通过有意锁定文件的测试来补充此类功能,然后调用它来检查它是否正确检测到消息。
【讨论】:
如果您有工作表和工作簿对象,您可以进行父级检查
if (sheet.Parent == 工作簿)
【讨论】:
下面的这个函数将返回您是否打开了 excel 文件或 不是。
第二个功能将为您提供 Excel 应用程序、工作簿和工作表 用于您的代码。
using System.Collections.Generic;
using System.IO;
using System.Linq;
using wf = System.Windows.Forms;
using xl = Microsoft.Office.Interop.Excel;
public static class ExcelTest
{
public xl.Application xlApp = null;
public xl.Workbook xlWb = null;
public xl.Worksheet xlWs = null;
public static bool IsXlFileOpen(string xlFileName)
{
try
{
if (!File.Exists(xlFileName))
{
wf.MessageBox.Show("Excel File does not exists!");
return false;
}
try
{
xlApp = (xl.Application)Marshal.GetActiveObject("Excel.Application");
}
catch (Exception ex)
{
return false;
}
foreach (xl.Workbook wb in xlApp.Workbooks)
{
if (wb.FullName == xlFileName)
{
xlWb = wb;
return true;
}
}
return false;
}
catch (Exception ex)
{
return false;
}
}
public static void GetXlSheet(string xlFileName,
string xlSheetName)
{
try
{
if (!File.Exists(xlFileName))
{
wf.MessageBox.Show("Excel File does not exists!");
return false;
}
xlApp = (xl.Application)Marshal.GetActiveObject("Excel.Application");
foreach (xl.Workbook wb in xlApp.Workbooks)
{
if (wb.FullName == xlFileName)
{
if (!xlWb
.Sheets
.Cast<xl.Worksheet>()
.Select(s => s.Name)
.Contains(xlSheetName))
{
wf.MessageBox.Show("Sheet name does not exist in the Excel workbook!");
return;
}
xlWs = xlWb.Sheets[xlSheetName];
}
}
}
catch (Exception ex)
{
// catch errors
}
}
}
【讨论】:
对以前的 C# anwsers 的一个小补充,如果有人可能偶然发现:
你检查的方法:
xlAppRef.Workbooks.Item
实际上有效,只要变量 wbook 是“myworkbook.xlsx”,不是整个路径到工作簿(如“C/Users/Me/Desktop/myworkbook.xlsx”)
bool IsOpened(string wbook, Excel.Application xlAppRef)
{
bool isOpened = true;
try
{
// wbook should be: "name-of-the-workbook.xlsx". Otherwise it will always raise the
// exception and never return true
var vb = xlAppRef.Workbooks.Item[wbook];
}
catch (Exception e)
{
isOpened = false;
}
return isOpened;
}
【讨论】: