代码之家  ›  专栏  ›  技术社区  ›  Mike Q

从C启动Excel转到后台

  •  1
  • Mike Q  · 技术社区  · 14 年前

    我有一个.xlsx文件,我希望从C_在Excel中启动。为此,我使用 Process.start() API与 open 动词。

    除了Excel窗口短暂出现然后隐藏在主应用程序后面之外,这一切都很好。

    奇怪的是,在完全相同的代码部分使用完全相同的API启动一个PDF(默认视图为adoboe viewer),可以很好地工作,PDF看起来最大化并保持在那里。这似乎是排除了我的应用程序在Excel启动后将自己移回前端的可能性。

    有人知道这可能是什么原因吗?

    编辑:添加的代码

        ProcessStartInfo startInfo = new ProcessStartInfo(filename);
        startInfo.WindowStyle = windowStyle; // maximized
    
        startInfo.Verb = "open";
        startInfo.ErrorDialog = false;
    
        Process.Start(startInfo);
    
    4 回复  |  直到 10 年前
        1
  •  4
  •   Evan Mulawski    14 年前

    开始Excel:

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

    从user32.dll导入setfocus函数:

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

    将导入置于函数之外。您可能需要休眠主线程才能等待Excel启动。

    编辑:

    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}
    

    进口:

    [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);
    

    这将等到应用程序启动后,再尝试将焦点设置为它。

        2
  •  3
  •   Cédric Guillemette    14 年前

    我会用 SetForeground 在Excel窗口中。

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

    要获得Excel的句柄,必须执行以下操作:

    Process p = Process.Start(startInfo);
    System.IntPtr hWnd = p.Handle;
    
        3
  •  1
  •   Mike Q    14 年前

    回答我自己的问题。

    原来这是一个devexpress错误/功能。当你点击它的时候,它的警报控制会把焦点恢复回来。

    DevExpress以通常令人印象深刻的即时方式已经解决了这个问题。见 this item

        4
  •  1
  •   Daniel Scott    10 年前

    通过Excel2010,我发现Evan Mulawski的解决方案不起作用。尝试调用.waitForInputIdle时引发异常,因为当打开第二个(或第三个或第四个)Excel电子表格时,开始检测第一个Excel实例的Excel进程会通知它打开文档,然后立即关闭。这意味着您的进程对象不再具有要调用的进程。WaitForInputIdle。

    我用下面的助手类解决了这个问题。我没有对Excel以外的应用程序进行过广泛的测试,但是它可以很好地集中于Excel,理论上它可以与任何“单实例”应用程序一起工作。

    打电话就行了 ShellHelpers.OpenFileWithFocus("C:\Full\Path\To\file.xls") 使用它。

    感谢Evan Mulawski提供的原始代码概念,这是我建立在以下基础上的:

    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;
            }
    
    
        }
    }
    

    另外,helper类还有一些其他有用的方法(例如查找特定可执行文件的运行实例,或者确定特定文件是否有关联的应用程序)。

    更新: 实际上,它似乎是Excel2010 调用process时启动单独的进程。从Excel可执行文件开始,这意味着我的代码查找同一.exe的其他实例对于Excel来说不是必需的,并且从不运行。

    当我开始使用Evan Mulawski的解决方案时,我正在调用process.start从我试图打开的csv开始,这意味着Excel维护了一个进程(因此导致了异常)。

    可能运行excel exe(在不知何故地找出它在PC上的位置之后)是Evan在回答中建议的,我可能误解了。

    总之,作为一个额外的好处,运行Excel exe(而不是调用process.start在csv或xls文件上)意味着您将获得单独的Excel实例,这也意味着您将获得单独的Excel 窗户 可以放在不同的显示器上,也可以在同一屏幕上并排查看。通常,双击Excel文件(在2013年之前的Excel版本中)时,它们都会在同一个Excel实例/窗口中打开,无法平铺或放置在单独的监视器上,因为2013年之前的Excel版本仍然是单文档界面(糟糕!)

    干杯

    丹尼尔