2010-11-08 8 views
1

J'ai un fichier .xlsx que je souhaite lancer dans Excel à partir de C#. Pour ce faire, j'utilise l'API Process.start() avec le verbe open.Lancer Excel à partir de C# passe à l'arrière-plan

Cela fonctionne bien, sauf que la fenêtre Excel apparaît brièvement, puis se cache derrière l'application principale.

Lancer bizarrement un PDF (Viewer Adoboe comme vue par défaut) en utilisant exactement la même API dans exactement la même section de code fonctionne bien, le PDF apparaît maximisé et reste là. Cela semblerait exclure mon application se déplaçant vers le front après le lancement d'Excel.

Est-ce que quelqu'un sait ce qui pourrait causer cela?

EDIT: code ajouté

ProcessStartInfo startInfo = new ProcessStartInfo(filename); 
    startInfo.WindowStyle = windowStyle; // maximized 

    startInfo.Verb = "open"; 
    startInfo.ErrorDialog = false; 

    Process.Start(startInfo); 
+0

est en cours d'exécution Excel déjà quand vous voyez cela? –

+0

@Hans, non, je me suis assuré qu'il est la première instance et confirmé dans l'explorateur de processus –

Répondre

1

Répondre à ma propre question.

Il s'avère que c'était un bug/une fonctionnalité DevExpress. C'est quelque chose avec leur AlertControl qui se recentre lorsque vous cliquez dessus. DevExpress dans la mode habituelle impressionnant d'habitude ont déjà résolu le problème. Voir this item

+1

Pourquoi avez-vous vérifié cela comme solution? Vous n'avez même pas mentionné DevExpress en question. Vous réalisez que c'est supposé être utilisé par d'autres personnes? Veuillez ne pas marquer votre réponse et sélectionnez l'une des autres réponses. – IvanP

3

J'utiliser SetForeground sur la fenêtre Excel.

[DllImport("user32.dll")] 
private static extern bool SetForegroundWindow(IntPtr hWnd); 

Pour obtenir la poignée vers Excel, vous devez les éléments suivants:

Process p = Process.Start(startInfo); 
System.IntPtr hWnd = p.Handle; 
+0

Ajouté mon code. Pour le moment, je n'ai pas accès à la poignée de la fenêtre. –

4

Démarrer Excel:

Process myProcess = new Process(); 
myProcess.StartInfo.FileName = "Excel"; //or similar 
myProcess.Start(); 
IntPtr hWnd = myProcess.Handle; 
SetFocus(new HandleRef(null, hWnd)); 

Importer la fonction SetFocus de user32.dll:

[DllImport("user32.dll", CharSet=CharSet.Auto,ExactSpelling=true)] 
public static extern IntPtr SetFocus(HandleRef hWnd); 

Placer l'importation à l'extérieur de votre fonction. Vous devrez peut-être désactiver le thread principal pour attendre le démarrage d'Excel.

Edit:

System.Diagnostics.Process myProcess = new 
System.Diagnostics.Process(); 
myProcess.StartInfo.FileName = "Excel"; //or similar 
myProcess.Start(); 
myProcess.WaitForInputIdle(2000); 
IntPtr hWnd = myProcess.MainWindowHandle; 
bool p = SetForegroundWindow(hWnd); 
if(!p) 
{//could not set focus} 

Importations:

[DllImport("user32.dll", CharSet=CharSet.Auto,SetLastError=true)] 
public static extern bool SetForegroundWindow(IntPtr hWnd); 
[DllImport("user32.dll", CharSet=CharSet.Auto,SetLastError=true)] 
public static extern IntPtr SetFocus(IntPtr hWnd); 

Cela attendra jusqu'à ce que l'application commence avant de tenter de mettre en focus.

+0

Merci, cela peut faire l'affaire. Y a-t-il quelque chose que je puisse faire qui soit plus déterministe que de dormir pour m'assurer qu'Excel est ouvert? –

+0

Voir ma publication éditée. –

1

Avec Excel 2010, j'ai trouvé que la solution d'Evan Mulawski ne fonctionnait pas. Une exception a été levée lorsque vous essayez d'appeler .WaitForInputIdle, car lorsque vous ouvrez votre deuxième (ou troisième, quatrième) feuille de calcul Excel, le processus Excel que vous démarrez détecte la première instance d'Excel, lui demande d'ouvrir le document et se ferme immédiatement. Cela signifie que votre objet Process n'a plus de processus pour appeler .WaitForInputIdle. Je l'ai résolu avec la classe d'aide suivante que j'ai rassemblée. Je n'ai pas testé cela de manière approfondie avec des applications autres qu'Excel, mais cela fonctionne bien pour focaliser Excel et devrait en théorie fonctionner avec n'importe quelle application "single instance".

Appelez simplement ShellHelpers.OpenFileWithFocus("C:\Full\Path\To\file.xls") pour l'utiliser.

Merci à Evan Mulawski pour le concept de code original, que je construit sur :)

using System; 
using System.Runtime.InteropServices; 
using System.Text; 
using System.Diagnostics; 
using System.Threading; 
namespace Resolv.Extensions.System.UI 
{ 

    public static class ShellHelpers 
    { 
     private const long FindExecutable_SE_ERR_FNF = 2;   //The specified file was not found. 
     private const long FindExecutable_SE_ERR_PNF = 3;   // The specified path is invalid. 
     private const long FindExecutable_SE_ERR_ACCESSDENIED = 5; // The specified file cannot be accessed. 
     private const long FindExecutable_SE_ERR_OOM = 8;   // The system is out of memory or resources. 
     private const long FindExecutable_SE_ERR_NOASSOC = 31;  // There is no association for the specified file type with an executable file. 

     [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] 
     private static extern bool SetForegroundWindow(IntPtr hWnd); 

     [DllImport("shell32.dll", EntryPoint = "FindExecutable")] 
     private static extern long FindExecutableA(string lpFile, string lpDirectory, StringBuilder lpResult); 


     private class ProcessInfo 
     { 
      public string ProcessPath { get; set; } 
      public Process Process { get; set; } 
     } 

     /// <summary> 
     /// Opens the specified file in the default associated program, and sets focus to 
     /// the opened program window. The focus setting is required for applications, 
     /// such as Microsoft Excel, which re-use a single process and may not set focus 
     /// when opening a second (or third etc) file. 
     /// </summary> 
     /// <param name="filePath"></param> 
     /// <returns></returns> 
     public static bool OpenFileWithFocus(string filePath) 
     { 
      string exePath; 
      if (!TryFindExecutable(filePath, out exePath)) 
      { 
       return false; 
      } 

      Process viewerProcess = new Process(); 
      viewerProcess.StartInfo.FileName = exePath; 
      viewerProcess.StartInfo.Verb = "open"; 
      viewerProcess.StartInfo.ErrorDialog = true; 
      viewerProcess.StartInfo.Arguments = filePath; 

      ProcessInfo info = new ProcessInfo() {Process = viewerProcess, ProcessPath = exePath}; 

      viewerProcess.Start(); 

      ThreadPool.QueueUserWorkItem(SetWindowFocusForProcess, info); 
      return true; 
     } 


     /// <summary> 
     /// To be run in a background thread: Attempts to set focus to the 
     /// specified process, or another process from the same executable. 
     /// </summary> 
     /// <param name="processInfo"></param> 
     private static void SetWindowFocusForProcess(object processInfo) 
     { 
      ProcessInfo windowProcessInfo = processInfo as ProcessInfo; 
      if (windowProcessInfo == null) 
       return; 

      int tryCount = 0; 

      Process process = windowProcessInfo.Process; 

      while (tryCount < 5) 
      { 
       try 
       { 
        process.WaitForInputIdle(1000);   // This may throw an exception if the process we started is no longer running 
        IntPtr hWnd = process.MainWindowHandle; 

        if (SetForegroundWindow(hWnd)) 
        { 
         break; 
        } 
       } 
       catch 
       { 
        // Applications that ensure a single process will have closed the 
        // process we opened earlier and handed the command line arguments to 
        // another process. We should find the "single" process for the 
        // requested application. 
        if (process == windowProcessInfo.Process) 
        { 
         Process newProcess = GetFirstProcessByPath(windowProcessInfo.ProcessPath); 
         if (newProcess != null) 
          process = newProcess; 
        } 

       } 

       tryCount++; 
      } 
     } 

     /// <summary> 
     /// Gets the first process (running instance) of the specified 
     /// executable. 
     /// </summary> 
     /// <param name="executablePath"></param> 
     /// <returns>A Process object, if any instances of the executable could be found running - otherwise NULL</returns> 
     public static Process GetFirstProcessByPath(string executablePath) 
     { 
      Process result; 
      if (TryGetFirstProcessByPath(executablePath, out result)) 
       return result; 

      return null; 
     } 

     /// <summary> 
     /// Gets the first process (running instance) of the specified 
     /// executable 
     /// </summary> 
     /// <param name="executablePath"></param> 
     /// <param name="process"></param> 
     /// <returns>TRUE if an instance of the specified executable could be found running</returns> 
     public static bool TryGetFirstProcessByPath(string executablePath, out Process process) 
     { 
      Process[] processes = Process.GetProcesses(); 

      foreach (var item in processes) 
      { 
       if (string.Equals(item.MainModule.FileName, executablePath, StringComparison.InvariantCultureIgnoreCase)) 
       { 
        process = item; 
        return true; 
       } 
      } 

      process = null; 
      return false; 
     } 

     /// <summary> 
     /// Return system default application for specified file 
     /// </summary> 
     /// <param name="filePath"></param> 
     /// <returns></returns> 
     public static string FindExecutable(string filePath) 
     { 
      string result; 
      TryFindExecutable(filePath, out result, raiseExceptions: true); 
      return result; 
     } 


     /// <summary> 
     /// Attempts to find the associated application for the specified file 
     /// </summary> 
     /// <param name="filePath"></param> 
     /// <param name="executablePath"></param> 
     /// <returns>TRUE if an executable was associated with the specified file. FALSE 
     /// if there was an error, or an association could not be found</returns> 
     public static bool TryFindExecutable(string filePath, out string executablePath) 
     { 
      return TryFindExecutable(filePath, out executablePath, raiseExceptions: false); 
     } 


     /// <summary> 
     /// Attempts to find the associated application for the specified file. Throws 
     /// exceptions if the file could not be opened or does not exist, but returns 
     /// FALSE when there is no application associated with the file type. 
     /// </summary> 
     /// <param name="filePath"></param> 
     /// <param name="executablePath"></param> 
     /// <param name="raiseExceptions"></param> 
     /// <returns></returns> 
     public static bool TryFindExecutable(string filePath, out string executablePath, bool raiseExceptions) 
     { 
      // Anytime a C++ API returns a zero-terminated string pointer as a parameter 
      // you need to use a StringBuilder to accept the value instead of a 
      // System.String object. 
      StringBuilder oResultBuffer = new StringBuilder(1024); 

      long lResult = 0; 

      lResult = FindExecutableA(filePath, string.Empty, oResultBuffer); 

      if (lResult >= 32) 
      { 
       executablePath = oResultBuffer.ToString(); 
       return true; 
      } 

      switch (lResult) 
      { 
       case FindExecutable_SE_ERR_NOASSOC: 
        executablePath = ""; 
        return false; 

       case FindExecutable_SE_ERR_FNF: 
       case FindExecutable_SE_ERR_PNF: 
        if (raiseExceptions) 
        { 
         throw new Exception(String.Format("File \"{0}\" not found. Cannot determine associated application.", filePath));  
        } 
        break; 

       case FindExecutable_SE_ERR_ACCESSDENIED: 
        if (raiseExceptions) 
        { 
         throw new Exception(String.Format("Access denied to file \"{0}\". Cannot determine associated application.", filePath));  
        } 
        break; 

       default: 
        if (raiseExceptions) 
        { 
         throw new Exception(String.Format("Error while finding associated application for \"{0}\". FindExecutableA returned {1}", filePath, lResult)); 
        } 
        break; 
      } 

      executablePath = null; 
      return false; 
     } 


    } 
} 

En prime, ma classe d'aide a deux autres méthodes utiles (telles que la recherche des cas en cours d'exécution d'un particulier exécutable, ou déterminer si un fichier particulier a une application associée).

MISE À JOUR: En fait, il semble Excel 2010 -t lancement des processus distincts lorsque vous appelez Process.Start sur l'exécutable Excel, ce qui signifie mon code qui trouve d'autres instances du même .exe ne sont pas requis pour Excel et ne court jamais. Quand j'ai commencé à utiliser la solution d'Evan Mulawski, j'appelais Process.Start sur le CSV que j'essayais d'ouvrir, ce qui signifiait qu'Excel maintenait un seul processus (et provoquait donc des exceptions).

Il est possible qu'exécuter l'exe d'Excel (après avoir d'une certaine manière déterminé où il vit sur le PC) est ce que Evan suggérait dans sa réponse et j'ai peut-être mal compris.

Quoi qu'il en soit, comme un bonus supplémentaire, l'exécution de l'exe Excel (plutôt que d'appeler Process.Start sur un fichier CSV ou XLS) signifie que vous obtenez des instances Excel séparés, ce qui vous signifie également des fenêtres Excel séparés et peut les mettre sur différents moniteurs ou les voir côte à côte sur le même écran. Généralement, lorsque vous double-cliquez sur des fichiers Excel (dans les versions Excel antérieures à 2013), vous les ouvrez tous dans la même instance/fenêtre Excel et vous ne pouvez pas les coller ou les placer sur des moniteurs séparés. Interface de documents (beurk!)

Les cris de joie

Daniel

Questions connexes