2014-11-05 72 views
0

我有一個打開Excel .xlsm文件的應用程序。 Excel文件在Auto_Open()中有一堆長時間運行的代碼,可能需要幾分鐘才能完成。在Diagnostics.Process.Start()完成之前,應用程序不會退出

目前,我打開excel文件,而宏仍在運行時退出我的應用程序。主窗口關閉,但我可以看到MyApp.exe在任務管理器中運行,直到宏完成,此時MyApp.exe進程結束。

private void btnOpenExcel_Click(object sender, RoutedEventArgs e) 
    { 
     System.Diagnostics.Process.Start(excelFilePath); 
    } 

    private void btnClose_Click(object sender, RoutedEventArgs e) 
    { 
     Application.Current.Shutdown(); 
     //Also tried this.Close(); 
    } 

我希望能夠打開Excel文件,然後退出我的應用程序,而無需等待Excel宏結束。這可能嗎?

+1

請顯示您的代碼。見http://stackoverflow.com/help/mcve – 2014-11-05 16:56:54

+0

@PeterDuniho完成。希望這可以幫助你。 – 2014-11-05 17:06:18

+0

嘗試System.Windows.Forms.Application.Exit() – 2014-11-05 17:23:14

回答

2

經過大量的實驗和研究,我知道發生了什麼。這是Excel實施的一個不幸的副作用,也是本機Windows功能ShellExecuteEx(其中System.Diagnostics.Process使用)的工作方式。特別是,直到auto_run宏已完成,並在Process類使用它的方式調用時ShellExecuteEx不會返回,直到出現這種情況或(非常重要)到2分鐘以上的通過Excel將不承認DDE命令完成ShellExecuteEx

(文檔說有一分鐘的暫停,但在我的Windows 8.1的機器上是兩分鐘)。

我發現一些變通,但沒有完全優雅所有這一切,但應該工作正常。

注:下面所有的代碼示例將被放置在點擊事件處理程序中,除非另有說明,否則(即互操作聲明)。

我的首選解決方法是簡單地使用單獨的線程來啓動過程。這並不能解決流程本身的問題。但這一過程的其餘部分可以關閉,留下孤獨的線程等待超時(或auto_open完成,以先到者爲準):

Thread thread = new Thread(() => Process.Start(target)); 

thread.IsBackground = false; 
thread.SetApartmentState(ApartmentState.STA); 
thread.Start(); 

一個在出現的問題時,ShellExecuteEx沒有按」 t等待的是,Windows實際上需要您的STA線程掛起足夠長的時間,以便它將DDE命令發送到Excel以打開給定文件。這意味着任何試圖繞過ShellExecuteEx延遲的嘗試都會導致Excel無法啓動,或者不打開請求的文件。也就是說,如果你願意接受這種風險,或者用更長的超時時間來緩解風險(但不一定只要Windows強加兩分鐘超時),那麼你可以採取其他一些方法。

第二種方法是排隊一個Close()呼叫以供以後執行。這利用了ShellExecuteEx仍然在運行消息泵的事實,因此即使Process.Start()方法尚未返回,仍然可以在您的Form子類中獲取代碼。這方面的一個例子是:

BeginInvoke((Action)(async() => 
    { 
     await Task.Delay(1000); 
     Close(); 
    })); 
Process.Start(target); 

這1秒,這在我的電腦上是足夠長的時間讓ProcessShellExecuteEx做必要得到運行Excel工作延遲Close()命令。

注:我確實嘗試了更短的超時時間,並發現它不可靠。也就是說,在100ms而不是1000ms時,Excel根本沒有啓動。在500毫秒時,它開始但通常不會實際加載工作簿。整整一秒,我的SSD配備的筆記本電腦就可靠了。我實際上並不知道延遲是在哪裏,但可能是在驅動器較慢的計算機上,需要更長的超時時間。

我不喜歡上述的一件事是它在Process.Start()方法實際返回之前將應用程序關閉。雖然它起作用,但這似乎有點過於「猶太教/非猶太教」的路線。 :)

因此,第三個選項是完全繞過Process類,直接調用ShellExecuteEx。這樣做,你仍然需要等待,否則Excel將無法可靠地啓動。但是你可以在之後等待完成ShellExecuteEx的呼叫,因此應用程序清理對我來說似乎更清潔。也就是說,這是一個完全正常的程序退出,允許您可能想要做的所有常規內務。

它的互操作聲明的時間長一點到期,但它工作得很好:

SHELLEXECUTEINFO sei = new SHELLEXECUTEINFO(); 

sei.fMask = ShellExecuteMaskFlags.SEE_MASK_FLAG_NO_UI; 
sei.nShow = ShowCommands.SW_NORMAL; 
sei.lpFile = target; 

if (!Interop.ShellExecuteEx(sei)) 
{ 
    int hr = Marshal.GetLastWin32Error(); 
    Exception e = Marshal.GetExceptionForHR(hr); 
    // Throw, display message box, whatever you like here 
} 

await Task.Delay(100); 
Close(); 

在互操作的聲明是這樣的(未使用的枚舉值省略):

class Interop 
{ 
    [DllImport("shell32.dll", CharSet = CharSet.Auto, SetLastError = true)] 
    public static extern bool ShellExecuteEx(SHELLEXECUTEINFO lpExecInfo); 
} 

[StructLayout(LayoutKind.Sequential)] 
public class SHELLEXECUTEINFO 
{ 
    public int cbSize; 
    public ShellExecuteMaskFlags fMask; 
    public IntPtr hwnd; 
    [MarshalAs(UnmanagedType.LPTStr)] 
    public string lpVerb; 
    [MarshalAs(UnmanagedType.LPTStr)] 
    public string lpFile; 
    [MarshalAs(UnmanagedType.LPTStr)] 
    public string lpParameters; 
    [MarshalAs(UnmanagedType.LPTStr)] 
    public string lpDirectory; 
    public ShowCommands nShow; 
    public IntPtr hInstApp; 
    public IntPtr lpIDList; 
    [MarshalAs(UnmanagedType.LPTStr)] 
    public string lpClass; 
    public IntPtr hkeyClass; 
    public uint dwHotKey; 
    public IntPtr hIcon; 
    public IntPtr hProcess; 

    public SHELLEXECUTEINFO() 
    { 
     this.cbSize = Marshal.SizeOf(this); 
    } 
} 

public enum ShowCommands : int 
{ 
    SW_NORMAL = 1, 
} 

[Flags] 
public enum ShellExecuteMaskFlags : uint 
{ 
    SEE_MASK_FLAG_NO_UI = 0x00000400, 
} 

使用這項技術,我能夠使用更短的超時時間。 100毫秒似乎可靠地工作,而10毫秒沒有。

最後請注意,如果您可以更改Excel工作簿,則應該可以在auto_open例程中設置一個計時器,該例程稍後將運行實際初始化代碼,讓auto_open立即返回。這樣做會否定任何需要在C#程序中調用啓動代碼的需要。 :)

+0

優秀的答案。太棒了。 – 2014-11-06 15:22:59

1

最簡單的方法可以是調用Environment.Exit(-1),結束處理,並給出了底層操作系統指定的退出代碼。

相關問題