Sorting Excel tables (ListObjects) is not allowed on protected sheets. You'll see the following error message:
I spent weeks looking for a solution with no success. Everything out there is outdated with Excel 2007 code samples. There are no tutorials or guides on how circumvent this limitation.
Here's how I was able to finally overcome..
There is no trappable event when sorting from the Excel's filter drop-down menu of a table. You can, however, trap the events when an ascending, descending or sort dialog commands are invoked from the Ribbon's Home and Data tabs.
Using Excel 2016 Interop (document-level customization), Visual Studio 2015 and 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>
Next, add the events' callback functions. SortNoAlerts
unprotects the sheet for ascending / descending button clicks. But if the user chooses 'Custom Sort' (Home tab) - or - 'Sort' (Data tab), a dialog will appear, sure it will unprotect the sheet and protect it right back if OK is pressed, but if the user Cancels, ThisWorkbook_SheetCalculate
will never trigger leaving the sheet unprotected. So we add the SortDialogNoAlerts
which unprotects the sheet but also starts a timer that uses p/Invoke FindWindow
to look for the Sort dialog window. When the Window is no longer found, it protects it if not already protected.
- 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);
}
That will allow sorting tables on protected sheets. Don't be confused, the AllowSort
option of worksheet.Protect()
it's only for the cells of the sheet that are not part of a table (ListObject).