2010-05-04 4 views
5

Altro ciao tutti,Automazione di Excel: manca l'evento

Sto eseguendo l'automazione di Excel tramite Interop in C# e desidero essere informato quando una cartella di lavoro viene chiusa. Tuttavia, non esiste un evento Close sulla cartella di lavoro né un evento Quit sull'applicazione.

Qualcuno l'ha già fatto? Come posso scrivere un pezzo di codice che reagisce alla cartella di lavoro che viene chiusa (che viene eseguita solo se la cartella di lavoro è in realtà chiusa)? Idealmente dovrebbe succedere dopo chiudere la cartella di lavoro, quindi posso fare affidamento sul file per riflettere tutte le modifiche.

dettagli su quello che ho trovato finora:

c'è una() evento BeforeClose, ma se ci sono modifiche non salvate viene generato questo evento prima che l'utente viene chiesto se salvare loro, quindi al momento non posso elaborare l'evento, non ho il file finale e non posso rilasciare gli oggetti COM, entrambe le cose che ho bisogno di avere/fare. Non so nemmeno se la cartella di lavoro sarà effettivamente chiusa, poiché l'utente potrebbe scegliere di interrompere la chiusura.

Quindi c'è un evento BeforeSave(). Pertanto, se l'utente sceglie "Sì" per salvare le modifiche non salvate, BeforeSave() viene eseguito dopo BeforeClose(). Tuttavia, se l'utente sceglie di "Annullare", quindi fa clic su "File-> Salva", viene eseguito lo stesso ordine esatto degli eventi. Inoltre, se l'utente sceglie "No", il BeforeSave() non viene eseguito affatto. Lo stesso vale finché l'utente non fa clic su nessuna di queste opzioni.

risposta

4

ho creato un hack utilizzando un approccio di polling-simile, e funziona:

Data la cartella di lavoro di osservare, a creare un thread che tenta periodicamente di trovare che cartella di lavoro nella raccolta cartelle di lavoro.

(La classe DisposableCom è il mio attuale soluzione per properly cleanup COM objects.)

Excel.Application app = wbWorkbook.Application; 
string sWorkbookName = wbWorkbook.Name; 

Thread overseeWorkbooksThread = new Thread(new ThreadStart(
    delegate() 
    { 
     bool bOpened = false; 

     Excel.Workbooks wbsWorkbooks = app.Workbooks; 
     using (new DisposableCom<Excel.Workbooks>(wbsWorkbooks)) 
     { 
      while (true) 
      { 
       Thread.Sleep(1000); 

       if (wbsWorkbooks.ContainsWorkbookProperly(sWorkbookName)) 
        bOpened = true; 
       else 
        if (bOpened) 
         // Workbook was open, so it has been closed. 
         break; 
        else 
        { 
         // Workbook simply not finished opening, do nothing 
        } 
      } 

      // Workbook closed 
      RunTheCodeToBeRunAfterWorkbookIsClosed(); 
     } 
    })); 

overseeWorkbooksThread.Start(); 

I metodi di estensione "ContainsWorkbookProperly" si presenta in questo modo:

public static bool ContainsWorkbookProperly(this Excel.Workbooks excelWbs, 
    string sWorkbookName) 
{ 
    Excel.Workbook wbTemp = null; 
    try 
     wbTemp = excelWbs.Item(sWorkbookName); 
    catch (Exception) 
    { 
     // ignore 
    } 

    if (wbTemp != null) 
    { 
     new DisposableCom<Excel.Workbook>(wbTemp).Dispose(); 
     return true; 
    } 

    return false; 
} 

Ancora sarei interessato se v'è un più semplice o migliore soluzione.

-1

Puoi utilizzare entrambi gli eventi? Su BeforeClose() imposta un flag, quindi BeforeSave() verifica se il flag è impostato. È necessario un modo per reimpostarlo, tuttavia, nel caso in cui BeforeClose() sia attivato e BeforeSave() non lo sia. Non sono sicuro se c'è qualcos'altro che potrebbe aiutare con quello.

Modifica: Sembra che lo abbiate già trattato con "lo stesso identico ordine di eventi viene eseguito". Ma se riesci a trovare un modo per resettarlo (un altro evento "Annulla"?) Potrebbe funzionare.

+0

Ciao Nelson - sì, l'ho già spiegato. Il problema è che "e BeforeSave() non è" la parte della tua proposta è semidecidibile. Se non viene chiamato, posso aspettare ma non decidere mai se questo significa che ha abortito, ha scelto di no, o semplicemente non ha ancora fatto clic su alcun pulsante. – chiccodoro

3

Questo non è il mio codice, ma questo ha funzionato a meraviglia per me:

https://gist.github.com/jmangelo/301884

copia incolla:

using System; 
using Excel = Microsoft.Office.Interop.Excel; 

namespace Helpers.Vsto 
{ 
    public sealed class WorkbookClosedMonitor 
    { 
     internal class CloseRequestInfo 
     { 
      public CloseRequestInfo(string name, int count) 
      { 
       this.WorkbookName = name; 
       this.WorkbookCount = count; 
      } 

      public string WorkbookName { get; set; } 

      public int WorkbookCount { get; set; } 
     } 

     public WorkbookClosedMonitor(Excel.Application application) 
     { 
      if (application == null) 
      { 
       throw new ArgumentNullException("application"); 
      } 

      this.Application = application; 

      this.Application.WorkbookActivate += Application_WorkbookActivate; 
      this.Application.WorkbookBeforeClose += Application_WorkbookBeforeClose; 
      this.Application.WorkbookDeactivate += Application_WorkbookDeactivate; 
     } 

     public event EventHandler<WorkbookClosedEventArgs> WorkbookClosed; 

     public Excel.Application Application { get; private set; } 

     private CloseRequestInfo PendingRequest { get; set; } 

     private void Application_WorkbookDeactivate(Excel.Workbook wb) 
     { 
      if (this.Application.Workbooks.Count == 1) 
      { 
       // With only one workbook available deactivating means it will be closed 
       this.PendingRequest = null; 

       this.OnWorkbookClosed(new WorkbookClosedEventArgs(wb.Name)); 
      } 
     } 

     private void Application_WorkbookBeforeClose(Excel.Workbook wb, ref bool cancel) 
     { 
      if (!cancel) 
      { 
       this.PendingRequest = new CloseRequestInfo(
        wb.Name, 
        this.Application.Workbooks.Count); 
      } 
     } 

     private void Application_WorkbookActivate(Excel.Workbook wb) 
     { 
      // A workbook was closed if a request is pending and the workbook count decreased 
      bool wasWorkbookClosed = true 
       && this.PendingRequest != null 
       && this.Application.Workbooks.Count < this.PendingRequest.WorkbookCount; 

      if (wasWorkbookClosed) 
      { 
       var args = new WorkbookClosedEventArgs(this.PendingRequest.WorkbookName); 

       this.PendingRequest = null; 

       this.OnWorkbookClosed(args); 
      } 
      else 
      { 
       this.PendingRequest = null; 
      } 
     } 

     private void OnWorkbookClosed(WorkbookClosedEventArgs e) 
     { 
      var handler = this.WorkbookClosed; 

      if (handler != null) 
      { 
       handler(this, e); 
      } 
     } 
    } 

    public sealed class WorkbookClosedEventArgs : EventArgs 
    { 
     internal WorkbookClosedEventArgs(string name) 
     { 
      this.Name = name; 
     } 

     public string Name { get; private set; } 
    } 
} 

Quando l'ho usato ho cambiato dal ritorno del nome del cartella di lavoro per un riferimento alla cartella di lavoro.