Sort Excel table (ListObject) on protected sheet u

2019-09-11 10:17发布

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条回答
手持菜刀,她持情操
2楼-- · 2019-09-11 11:10

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

查看更多
登录 后发表回答