繁体   English   中英

从C#启动Excel进入后台

[英]Launching Excel from C# goes to background

我有一个.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);

启动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);

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

我会在Excel窗口上使用SetForeground

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

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

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

使用Excel 2010,我发现Evan Mulawski的解决方案不起作用。 尝试调用.WaitForInputIdle时引发异常,因为打开第二个(或第三个或第四个)Excel电子表格时,启动的Excel进程会检测到第一个Excel实例,告诉它打开文档,然后立即关闭。 这意味着您的Process对象不再具有调用.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;
        }


    }
}

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

更新:实际上,当您在excel可执行文件上调用Process.Start时,似乎Excel 2010 确实启动了单独的进程,这意味着Excel不需要查找同一.exe的其他实例的代码,并且永远不会运行。

当我开始使用Evan Mulawski的解决方案时,我正在尝试打开CSV上的Process.Start,这意味着excel维护的是单个进程(因此导致异常)。

可能运行excel exe(在以某种方式弄清楚了它在PC上的位置之后)是Evan在他的回答中建议的,我可能会误解了。

无论如何,作为一项额外的好处,运行Excel exe(而不是在CSV或XLS文件上调用Process.Start)意味着您将获得单独的Excel实例,这也意味着将获得单独的Excel 窗口并将其放在不同的监视器上或查看它们在同一屏幕上并排显示。 通常,当您双击Excel文件(在2013年之前的Excel版本中)时,最终它们都在同一Excel实例/窗口中打开,并且无法平铺它们或将它们放在单独的监视器上,因为2013年之前的Excel版本仍然是Single文档界面(糟糕!)

干杯

丹尼尔

回答我自己的问题。

原来这是一个DevExpress错误/功能。 单击它们后,AlertControl会重新聚焦。

DevExpress以通常令人印象深刻的快速方式已经解决了该问题。 看到这个项目

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM