【发布时间】:2018-11-14 06:17:43
【问题描述】:
我有一个工具可以通过使用Application.Calculate 请求 Excel 刷新来自动执行 excel - 问题是(令人惊讶的)调用 Microsoft.Office.Interop.Excel.Application.Calculate 会在用户正在编辑单元格时将其踢出当前操作,重命名工作表,键入功能区控件(如字体框)等...(此处描述的问题:Calling Application.Calculate breaks formula being edited)
为了解决这个问题,我能够使用 WinApi 调用来检测几个“编辑”控件之一是否处于活动状态。如果我检测到 Excel 用户“忙碌”,我会暂停自动化以避免中断他们的编辑。
public static bool IsExcelBusy(Application xlApp, out string reason)
{
IntPtr excelHwnd = (IntPtr)xlApp.Hwnd;
uint excelThreadId = NativeMethods.GetWindowThreadProcessId(excelHwnd, out uint excelProcessId);
// Get the handle of whatever window is in the foreground (system-wide)
IntPtr foreground = NativeMethods.GetForegroundWindow();
// Problem: If a non-excel-owned process has focus, we cannot get the focused control
uint foregroundThreadId = NativeMethods.GetWindowThreadProcessId(foreground, out uint foregroundProcessId);
if (foregroundProcessId != excelProcessId)
return false; // How can we know what control has focus?
// Otherwise, the following works:
try
{
// We need to attach the thread that owns this window to get the focused control
uint thisThreadId = NativeMethods.GetCurrentThreadId();
NativeMethods.AttachThreadInput(foregroundThreadId, thisThreadId, true);
IntPtr focusedControlHandle = NativeMethods.GetFocus();
if (focusedControlHandle != IntPtr.Zero)
{
// Get the class name of the control that the user is currently interacting with (if any)
StringBuilder classNameResult = new StringBuilder(256);
NativeMethods.GetClassName(focusedControlHandle, classNameResult, 256);
string className = classNameResult.ToString();
// Determine if this control is at risk of being interrupted by a recalculations
switch (className)
{
case "EXCEL6":
reason = "User is editing a cell";
return true;
case "EXCEL<":
reason = "User is editing in the formula bar";
return true;
case "RICHEDIT60W":
reason = "User is editing a ribbon control";
return true;
case "Edit":
isActivitySensitive = true;
reason = "User is in the named range box";
return true;
case "EXCEL=":
isActivitySensitive = true;
reason = "User is renaming a sheet";
return true;
}
}
}
finally
{
NativeMethods.AttachThreadInput(foregroundThreadId, thisThreadId, false);
}
return false;
}
问题(在上面的 cmets 中突出显示)是 GetFocus() WinApi 调用仅适用于当前前台窗口。我真正想知道的是主 Excel 应用程序窗口中的焦点是什么控件,无论该窗口当前是否处于活动状态。
例如,如果用户正在输入公式(计算暂停)并且用户 alt-tab 切换到浏览器以搜索某些内容,我不想取消暂停自动化或他们的半输入公式将丢失。
我很确定我接下来需要的是一些类似于“GetFocus”的 winapi 函数,但它可以为当前不位于前台的应用程序窗口获取“活动”或“聚焦”控件。
我试图避免在用户离开和重新进入 Excel 应用程序时监控用户的每一个动作。我正在寻找尽可能轻量级和无状态的检查,以确定在任何给定情况下,用户是否处于 Excel 应用程序中的编辑操作中间。
【问题讨论】:
-
值得评论的是,我没有使用其他“技巧”来检测 Excel 用户是否处于编辑模式(如
Application.Interactive = False)的原因是即使这些 COM 操作也会“踢" 用户退出诸如重命名工作表之类的操作。我需要一个真正不引人注目的用户活动测试,而 Excel 不提供开箱即用的测试。 -
只有前台窗口有焦点控件。总的来说,只有一个控件具有焦点。听起来您正在使正在做的事情变得复杂,因此,命令重叠并变得混乱,
-
@ashleedawg 如何引用当您返回应用程序窗口后将拥有焦点的控件,但由于另一个应用程序具有焦点,因此当前没有焦点。我只能想到“主动控制”这个词。即使语言使我难以理解,希望您只需在 PC 上的应用程序之间跳来跳去就能明白我的意思。
-
@ashleedawg 没那么复杂。这就像一个想要向用户发送推送通知的应用程序,但如果他们正在开车,则不会。今天可能有一个 API 调用“用户是否在潜水”,但并非总是如此。所以他们需要使用启发式方法。 Excel 没有 API 调用或完善的启发式方法来确定用户是否正在编辑公式,但这并不意味着使用 Excel 自动化的善意开发人员不应该尝试找出这些信息。
标签: c# excel winapi com office-addins