我有一个.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;
回答我自己的问题。
原来这是一个DevExpress的错误/功能。 这是AlertControl的东西,当你点击它时,会重新获得焦点。
通常令人印象深刻的时尚DevExpress已经解决了这个问题。 看到这个项目
在Excel 2010中,我发现Evan Mulawski的解决方案无效。 尝试调用.WaitForInputIdle时引发异常,因为当您打开第二个(或第三个或第四个)Excel电子表格时,开始的Excel进程会检测到Excel的第一个实例,告诉它打开文档,然后立即关闭。 这意味着你的Process对象不再有一个进程来调用.WaitForInputIdle on。
我用下面的帮手类解决了它,我把它放在一起。 我还没有对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.Mainmodulee.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 2010 确实启动单独的进程,当您在Excel可执行文件上调用Process.Start时,这意味着我的代码,发现其他同样的.exe的实例不需要为Excel,并且永远不会运行。
当我开始使用Evan Mulawski的解决方案时,我打电话给Process.Start打开了CSV,这意味着Excel正在维护一个进程(因此导致异常)。
可能运行excel的exe文件(以某种方式查明它在PC上的位置)是Evan在回答中所暗示的,我可能会误解。
无论如何,作为一个额外的好处,运行Excel EXE(而不是调用CSV或XLS文件的Process.Start)意味着你得到单独的Excel实例,这也意味着你得到单独的Excel 窗口 ,可以把它们放在不同的监视器或查看它们并排在同一个屏幕上。 通常,当您双击Excel文件(在2013版之前的Excel版本中)时,您最终会在同一个Excel实例/窗口中打开它们,并且无法平铺它们或将它们放在单独的监视器上,因为2013年之前的Excel版本仍然是Single文件接口(yuck!)
干杯
丹尼尔