Sort Excel table (ListObject) on protected sheet using Excel Interop

414 Views Asked by At

Sorting Excel tables (ListObjects) is not allowed on protected sheets. You'll see the following error message:

enter image description here

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..

1

There are 1 best solutions below

0
On BEST ANSWER

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#:


  1. Right-click on your project -> Add -> New Item -> Ribbon (XML)

  2. 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.

  1. 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;
        }
    }
  1. On ThisWorkbook.cs -> InternalStartup() add this:
this.SheetCalculate += new Excel.WorkbookEvents_SheetCalculateEventHandler(ThisWorkbook_SheetCalculate);
  1. 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;
}
  1. 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;
        }
    }
}
  1. 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).