我有一个.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);
答案 0 :(得分:4)
启动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);
这将等到应用程序启动后再尝试将焦点设置为它。
答案 1 :(得分:3)
我会在Excel窗口中使用SetForeground。
[DllImport("user32.dll")]
private static extern bool SetForegroundWindow(IntPtr hWnd);
要获取Excel的句柄,您必须执行以下操作:
Process p = Process.Start(startInfo);
System.IntPtr hWnd = p.Handle;
答案 2 :(得分:1)
回答我自己的问题。
原来这是一个DevExpress错误/功能。它是AlertControl的一个东西,当你点击它时会重新聚焦。
DevExpress以通常令人印象深刻的提示方式已经解决了问题。见this item
答案 3 :(得分:1)
使用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 windows 并可以将它们放在不同的显示器上或在同一屏幕上并排查看它们。通常当您双击Excel文件(在2013年之前的Excel版本中)时,您最终会在同一个Excel实例/窗口中打开它们并且无法将它们平铺或放在单独的监视器上,因为2013之前的Excel版本仍然是单个文档界面(哎呀!)
干杯
丹尼尔