使用 Excel Interop 在受保护的 sheet 上排序 Excel table (ListObject)
Sort Excel table (ListObject) on protected sheet using Excel Interop
在受保护的工作表上不允许对 Excel 表 (ListObjects) 进行排序。您将看到以下错误消息:
我花了数周时间寻找解决方案,但没有成功。 Excel 2007 代码示例中的所有内容都已过时。没有关于如何规避此限制的教程或指南。
这是我最终克服的方法..
从 table 的 Excel 过滤器下拉菜单中排序时没有可捕获的事件。但是,当从功能区的主页和数据选项卡调用升序、降序或排序对话框命令时,您可以捕获事件。
使用 Excel 2016 Interop(文档级自定义)、Visual Studio 2015 和 C#:
Right-click on your project -> Add -> New Item -> Ribbon (XML)
On your Ribbon.xml:
<?xml version="1.0" encoding="UTF-8"?>
<customUI xmlns="http://schemas.microsoft.com/office/2009/07/customui" onLoad="Ribbon_Load">
<commands>
<command idMso="SortAscendingExcel" onAction="SortNoAlerts" />
<command idMso="SortDescendingExcel" onAction="SortNoAlerts" />
<command idMso="SortCustomExcel" onAction="SortDialogNoAlerts" /><!--TabHome-->
<command idMso="SortDialog" onAction="SortDialogNoAlerts" /><!--TabData-->
</commands>
</customUI>
接下来,添加事件的回调函数。 SortNoAlerts
取消对 sheet 的升序/降序按钮点击的保护。但是如果用户选择'Custom Sort'(主页选项卡) - 或 - 'Sort'(数据选项卡),肯定会出现一个对话框如果按下 OK,它将解除对 sheet 的保护并立即保护它,但如果用户取消,ThisWorkbook_SheetCalculate
将永远不会触发 sheet 不受保护。所以我们添加了 SortDialogNoAlerts
来取消对 sheet 的保护,但也启动了一个计时器,该计时器使用 p/Invoke FindWindow
来查找排序对话框 window。当 Window 不再被找到时,它会保护它(如果尚未受到保护)。
- On your Ribbon.cs callbacks:
public void SortNoAlerts(Office.IRibbonControl control, ref bool cancelDefault)
{
Excel.Worksheet ws = null;
try
{
ws = (Excel.Worksheet)Globals.ThisWorkbook.ActiveSheet;
ws.Unprotect("your password");
cancelDefault = false;
}
catch (Exception) { }
finally
{
if (ws != null) Marshal.ReleaseComObject(ws); ws = null;
}
}
public void SortDialogNoAlerts(Office.IRibbonControl control, ref bool cancelDefault)
{
Excel.Worksheet ws = null;
try
{
ws = (Excel.Worksheet)Globals.ThisWorkbook.ActiveSheet;
ws.Unprotect("your password");
Globals.ThisWorkbook._myActionPane.tmrWaitSortWinClose.Enabled = true;
cancelDefault = false;
}
catch (Exception) {
Globals.ThisWorkbook._myActionPane.tmrWaitSortWinClose.Enabled = false;
}
finally
{
if (ws != null) Marshal.ReleaseComObject(ws); ws = null;
}
}
- On ThisWorkbook.cs -> InternalStartup() add this:
this.SheetCalculate += new Excel.WorkbookEvents_SheetCalculateEventHandler(ThisWorkbook_SheetCalculate);
- On ThisWorkbook.cs -> add this:
public bool sortDialogVisible;
private void ThisWorkbook_SheetCalculate(object sh)
{
Excel.Worksheet ws = (Excel.Worksheet)sh;
ws.EnableOutlining = true;
ws.Protect("your password", true, Type.Missing, Type.Missing, true, true, true, Type.Missing, Type.Missing, true, Type.Missing, Type.Missing, true, true, true, Type.Missing);
Marshal.ReleaseComObject(ws); ws = null;
}
- Add a timer named tmrWaitSortWinClose and set Interval = 750:
private void tmrWaitSortWinClose_Tick(object sender, EventArgs e)
{
Globals.ThisWorkbook.sortDialogVisible = Native.FindWindow("NUIDialog", "Sort") == IntPtr.Zero;
if (Globals.ThisWorkbook.sortDialogVisible)
{
Excel.Worksheet ws = null;
try
{
ws = (Excel.Worksheet)Globals.ThisWorkbook.ActiveSheet;
if (!ws.ProtectContents)
{
ws.Protect("your password", true, Type.Missing, Type.Missing, true, true, true, Type.Missing, Type.Missing, true, Type.Missing, Type.Missing, true, true, true, Type.Missing);
}
tmrWaitSortWinClose.Enabled = false;
}
catch (Exception) { tmrWaitSortWinClose.Enabled = false; }
finally
{
if (ws != null) Marshal.ReleaseComObject(ws); ws = null;
}
}
}
- Add a class named Native.cs:
public class Native
{
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
}
这将允许在受保护的 sheet 上排序 table。不要混淆,worksheet.Protect()
的 AllowSort
选项仅适用于 sheet 中不属于 table (ListObject) 的单元格。
在受保护的工作表上不允许对 Excel 表 (ListObjects) 进行排序。您将看到以下错误消息:
我花了数周时间寻找解决方案,但没有成功。 Excel 2007 代码示例中的所有内容都已过时。没有关于如何规避此限制的教程或指南。
这是我最终克服的方法..
从 table 的 Excel 过滤器下拉菜单中排序时没有可捕获的事件。但是,当从功能区的主页和数据选项卡调用升序、降序或排序对话框命令时,您可以捕获事件。
使用 Excel 2016 Interop(文档级自定义)、Visual Studio 2015 和 C#:
Right-click on your project -> Add -> New Item -> Ribbon (XML)
On your Ribbon.xml:
<?xml version="1.0" encoding="UTF-8"?>
<customUI xmlns="http://schemas.microsoft.com/office/2009/07/customui" onLoad="Ribbon_Load">
<commands>
<command idMso="SortAscendingExcel" onAction="SortNoAlerts" />
<command idMso="SortDescendingExcel" onAction="SortNoAlerts" />
<command idMso="SortCustomExcel" onAction="SortDialogNoAlerts" /><!--TabHome-->
<command idMso="SortDialog" onAction="SortDialogNoAlerts" /><!--TabData-->
</commands>
</customUI>
接下来,添加事件的回调函数。 SortNoAlerts
取消对 sheet 的升序/降序按钮点击的保护。但是如果用户选择'Custom Sort'(主页选项卡) - 或 - 'Sort'(数据选项卡),肯定会出现一个对话框如果按下 OK,它将解除对 sheet 的保护并立即保护它,但如果用户取消,ThisWorkbook_SheetCalculate
将永远不会触发 sheet 不受保护。所以我们添加了 SortDialogNoAlerts
来取消对 sheet 的保护,但也启动了一个计时器,该计时器使用 p/Invoke FindWindow
来查找排序对话框 window。当 Window 不再被找到时,它会保护它(如果尚未受到保护)。
- On your Ribbon.cs callbacks:
public void SortNoAlerts(Office.IRibbonControl control, ref bool cancelDefault)
{
Excel.Worksheet ws = null;
try
{
ws = (Excel.Worksheet)Globals.ThisWorkbook.ActiveSheet;
ws.Unprotect("your password");
cancelDefault = false;
}
catch (Exception) { }
finally
{
if (ws != null) Marshal.ReleaseComObject(ws); ws = null;
}
}
public void SortDialogNoAlerts(Office.IRibbonControl control, ref bool cancelDefault)
{
Excel.Worksheet ws = null;
try
{
ws = (Excel.Worksheet)Globals.ThisWorkbook.ActiveSheet;
ws.Unprotect("your password");
Globals.ThisWorkbook._myActionPane.tmrWaitSortWinClose.Enabled = true;
cancelDefault = false;
}
catch (Exception) {
Globals.ThisWorkbook._myActionPane.tmrWaitSortWinClose.Enabled = false;
}
finally
{
if (ws != null) Marshal.ReleaseComObject(ws); ws = null;
}
}
- On ThisWorkbook.cs -> InternalStartup() add this:
this.SheetCalculate += new Excel.WorkbookEvents_SheetCalculateEventHandler(ThisWorkbook_SheetCalculate);
- On ThisWorkbook.cs -> add this:
public bool sortDialogVisible;
private void ThisWorkbook_SheetCalculate(object sh)
{
Excel.Worksheet ws = (Excel.Worksheet)sh;
ws.EnableOutlining = true;
ws.Protect("your password", true, Type.Missing, Type.Missing, true, true, true, Type.Missing, Type.Missing, true, Type.Missing, Type.Missing, true, true, true, Type.Missing);
Marshal.ReleaseComObject(ws); ws = null;
}
- Add a timer named tmrWaitSortWinClose and set Interval = 750:
private void tmrWaitSortWinClose_Tick(object sender, EventArgs e)
{
Globals.ThisWorkbook.sortDialogVisible = Native.FindWindow("NUIDialog", "Sort") == IntPtr.Zero;
if (Globals.ThisWorkbook.sortDialogVisible)
{
Excel.Worksheet ws = null;
try
{
ws = (Excel.Worksheet)Globals.ThisWorkbook.ActiveSheet;
if (!ws.ProtectContents)
{
ws.Protect("your password", true, Type.Missing, Type.Missing, true, true, true, Type.Missing, Type.Missing, true, Type.Missing, Type.Missing, true, true, true, Type.Missing);
}
tmrWaitSortWinClose.Enabled = false;
}
catch (Exception) { tmrWaitSortWinClose.Enabled = false; }
finally
{
if (ws != null) Marshal.ReleaseComObject(ws); ws = null;
}
}
}
- Add a class named Native.cs:
public class Native
{
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
}
这将允许在受保护的 sheet 上排序 table。不要混淆,worksheet.Protect()
的 AllowSort
选项仅适用于 sheet 中不属于 table (ListObject) 的单元格。