Mese az Excelről és a context menüről

TL;DR: 2010-es Excelnél újabb verzióban soha ne használd a Workbook_SheetBeforeRightClick() event handlert arra, hogy saját CommandBart hozz létre – helyette ott a Custom UI editor for Microsoft Office, amivel ugyan csak bedrótozott hierarchiát tudsz csinálni, viszont cserébe nem fagy majd random szénné alatta a host környezet. A dolog úgy indult, hogy egy Office update után elkezdett furán viselkedni a VBA kód pár táblában. Jó szokás szerint a hibajelenségnek semmi köze nem volt a hiba okához, a Microsoftos alkalmazásnak pedig esze ágában sem volt bármiféle notificationt küldeni, hogy neki XY kóddarabbal lenne baja, sokkal inkább a számukra már sokszor bevált utat választba, nemes egyszerűséggel vagy rommá fagyasztotta a futtató környezetet, vagy a teljes Excel UI elfelejtett a végfelhasználóval kommunikálni. Mutatom:

Excel 2010+ event handler madness from Gabor Penoff on Vimeo.

A videóban jól látszik, hogy a testData tábla kiválasztása után az megnyílik, majd az Excel előbb mintha elfelejtené a screent update-elni, később viszont teljesen meggárgyul és a saját UI elemei is megszűnnek működni. Nézzük végig, mi történik belül. A 2. kattintásra a standard context menünek kellene előbújnia, azonban én ezt elkapom és a saját cuccaimat pakolom a gyári menü helyére. Mindez úgy történik, hogy a Workbook_SheetBeforeRightClick() event handlert használom:
Private Sub Workbook_SheetBeforeRightClick(ByVal Sh As Object, ByVal Target As Excel.Range, Cancel As Boolean)
    rightClickMenuShow (rightClickMenuTitle)
    Cancel = RightClickCancel
End Sub
A rightClikMenuShow() törli az előző custom menüt, majd újra létrehozza és megjeleníti:
Sub rightClickMenuShow(rightClickMenuName As String)
    RightClickCancel = True
    Call rightClickMenuDelete(rightClickMenuName)
    Call rightClickMenuCreate(ActiveSheet)
    CommandBars(rightClickMenuTitle).ShowPopup
End Sub
A törlés így néz ki:
Sub rightClickMenuDelete(rightClickMenuName As String)
    Dim bar As CommandBar
    For Each bar In CommandBars
        If UCase(Trim(bar.Name)) = UCase(Trim(rightClickMenuTitle)) Then bar.Delete
    Next bar
End Sub
Az új menü létrehozása pedig így:
Sub rightClickMenuCreate(S_Caller As Worksheet)
    Dim rightClickMenuName As String
    Dim rightClickMenuIndex As Long
    rightClickMenuName = rightClickMenuTitle
    Call rightClickMenuDelete(rightClickMenuName)
    Application.CommandBars.Add Name:=rightClickMenuName, Position:=msoBarPopup
    rightClickMenuIndex = 0
    rightClickMenuIndex = rightClickMenuIndex + 1
    CommandBars(rightClickMenuName).Controls.Add Type:=msoControlButton, Before:=rightClickMenuIndex
    With CommandBars(rightClickMenuName).Controls(rightClickMenuIndex)
        .FaceId = 263
        .Style = msoButtonIconAndCaption
        .Caption = "Excel's &Default Shortcut Menu"
        .OnAction = "ShowDefaultRightClickMenu"
    End With
    rightClickMenuIndex = rightClickMenuIndex + 1
    CommandBars(rightClickMenuName).Controls.Add Type:=msoControlButton, Before:=rightClickMenuIndex
    With CommandBars(rightClickMenuName).Controls(rightClickMenuIndex)
        .Style = msoButtonCaption
        .Caption = "Freeze me please"
        .OnAction = "myTestSub"
        .BeginGroup = True
    End With
End Sub
Röviden ennyi a jobb klikkes menüből a myTestSub() makrót elindító event handler mechanizmus. Miután ebben hozzá nem nyúlok a képernyő frissítését szabályozó Application.ScreenUpdating metódushoz, nézzük meg, mit csinál a myTestSub():
Sub myTestSub()
    Dim f As Variant
    f = Application.GetOpenFilename
    If f = False Then
        MsgBox "No file selected."
    Else
        Workbooks.Open Filename:=f, ReadOnly:=True
    End If
End Sub
Láthatóan ennek sincs köze a ScreenUpdating-hez, mégis elpusztul tőle az Excel. Miután a probléma nyilván nem egy ilyen ~20 soros toolban jött elő, pár órát elvett az életemből mire rájöttem, hogy az 2010-es verziónál frissebb Exceleknek azzal van baja, ahogyan én a right click menüt újradefiniálom a Workbook_SheetBeforeRightClick() event handlerben egy CommandBars(rightClickMenuTitle).ShowPopup() hívással. A helyes megoldás ilyenkor az, hogy ezt a popup generátort úgy ahogy van kidobod és létrehozol egy XML-t, ami a fenti két eljárás meghívásához legyártja majd az UI eleme(ke)t a jobb klikkre megjelenő context menüben:
<customUI xmlns="http://schemas.microsoft.com/office/2009/07/customui">
    <contextMenus>
        <contextMenu idMso="ContextMenuCell">
            <menu id="appMenu" label="engine" insertBeforeMso="Cut">
                <button id="item_id1" label="(Do not) freeze me please" onAction="myTestSub"/>
            </menu>
        </contextMenu>
    </contextMenus>
</customUI>
Ebben az esetben azonban ne felejtsd el, hogy fel kell készíteni a hívott rutinodat az IRibbonControl paraméter fogadására:
Sub myTestSub(control As IRibbonControl)
Tanulság nincs, csak ledokumentáltam 4 óra őrjöngés után a megoldást. ]]>

Leave a Reply

Your email address will not be published.