1
votes

The PHB wants me to create a ribbon toggle button for Microsoft Word that:

  • when pressed, restricts editing to filling in forms and protects the document without a password.
  • when unpressed, unprotects the document (without a password).

I have the following customUI.xml:

<customUI xmlns="http://schemas.microsoft.com/office/2006/01/customui" onLoad="RibbonOnLoad">
    <commands>
        <command idMso="ProtectOrUnprotectDocument" onAction="ProtectOrUnprotectDocumentOnAction"/>
    </commands>
    <ribbon>
        <tabs>
            <tab id="LawTab" label="Law">
                <group id="ProtectGroup" label="Protect">
                    <toggleButton id="ToggleProtectionButton" imageMso="GreenBall" label="Protection" getPressed="ToggleProtectionButtonGetPressed" onAction="ToggleProtectionButtonOnAction"/>
                    <button id="InvalidateRibbonButton" imageMso="Refresh" label="Invalidate Ribbon" onAction="InvalidateRibbonButtonOnAction"/>
                </group>
            </tab>
        </tabs>
    </ribbon>
</customUI>

and the following VBA code:

Private ribbon As IRibbonUI

Sub InvalidateRibbonButtonOnAction(control As IRibbonControl)
    ribbon.Invalidate
End Sub

Sub ProtectOrUnprotectDocumentOnAction(control As IRibbonControl, ByRef cancelDefault)
    ribbon.Invalidate
    cancelDefault = False
End Sub

Sub RibbonOnLoad(ActiveRibbon As IRibbonUI)
    Set ribbon = ActiveRibbon
End Sub

Sub ToggleProtectionButtonGetPressed(control As IRibbonControl, ByRef returnValue)
    returnValue = ActiveDocument.ProtectionType <> wdNoProtection
End Sub

Sub ToggleProtectionButtonOnAction(control As IRibbonControl, ByVal pressed As Boolean)
    If pressed Then
        ActiveDocument.Protect wdAllowOnlyFormFields
    Else
        ActiveDocument.Unprotect
    End If
End Sub

I can repurpose the built-in ProtectOrUnprotect command so the corresponding built-in button invalidates the ribbon and consequently updates my custom button, but if I protect/unprotect with the built-in task pane (Review > Restrict Editing) or programmatically (ActiveDocument.Protect/Unprotect), my custom button stays ignorant of the change. How can I listen to the document-level event to protect/unprotect so I can update the state of my toggle button?

2
+1 for a good question. There are not many resources for ribbon manipulation. I will see if I can figure it out if I get a few free minutes...David Zemens
Please insert a message box in the ToggleProtectionButtonGetPressed function. Are you able to invoke this function through user action in Word? I am not able to do so. If you are, please let me know how you do it and I will keep trying. Otherwise, your solution may require an Add-In similar to what is needed for PowerPoint (here), IF it is even possible to respond to this particular event (it may not be)...David Zemens
I created an Invalidate Ribbon button. To test it, use the built-in task pane (Review > Restrict Editing) to start/stop protection. My custom button will ignore the actual protection state until you press the Invalidate Ribbon button.Homer

2 Answers

2
votes

How about forcing the built-in button to call your code?

To summarize, you can override the built-in Sheet Protect button to use your code (which hopefully causes your button to toggle) by adding the following to your ribbon XML (see the link for details):

<commands>
    <command idMso="ProtectOrUnprotectDocument" onAction="ToggleProtectionButtonOnAction"/>
</commands>
1
votes

I think Blackhawk's answer is on the right track: override the built-in command's onAction procedure. However, his XML example is for MS Excel and will not work in MS Word. This can be tweaked pretty easily, but unfortunately, I can't figure out how to solve this particular problem:

but if I use the built-in ProtectOrUnprotectDocument button to protect/unprotect, my custom button stays ignorant of the change. How can I listen to the document-level event to protect/unprotect so I can update the state of my toggle button?

There is no document-level event, even using a WithEvents application class, which responds to a change in the document's ProtectionType (in theory you should probably be able to use the ribbon .InvalidateControl method).

So the question (and possible resolution) is why you need a toggle button, when you can simply use the built-in button and hijack it's functionality with your own procedure to protect/unprotect as needed. You can even place the built-in button within your custom menu.

However, this looks promising:

http://sourcedaddy.com/ms-excel/getting-and-changing-control-values.html

REVISED

After some discussion and trial and error (you did a LOT of this on your own, and figured out what I could not), let's try this which is working for me.

Here is XML (you may need to modify the schemas if you're using older version).

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
   <customUI onLoad="RibbonOnLoad" xmlns="http://schemas.microsoft.com/office/2009/07/customui">
    <commands>
        <command idMso="ProtectOrUnprotectDocument" onAction="ToggleProtectionButtonOnAction" />
    </commands>
    <ribbon>
        <tabs>
            <tab id="LawTab" label="Law">
                <group id="ProtectGroup" label="Protect">
                    <toggleButton id="ToggleProtectionButton" imageMso="GreenBall" label="Protection" getPressed="ToggleProtectionButtonGetPressed" onAction="ToggleProtectionButtonOnAction"/>
                    <button id="InvalidateButton" imageMso="Refresh" label="Invalidate" onAction="InvalidateButtonOnAction"/>
                </group>
            </tab>
        </tabs>
    </ribbon>
</customUI>

What I did is to send both the ProtectOrUnprotectDocument command and your custom toggle button, ToggleProtectionButton, to the same onAction handler. Then I use some logic (which seems to be working) to selectively invalidate the ribbon, while a boolean variable captures the document's protection state, throughout the procedures and callbacks.

Here is VBA:

Option Explicit
Dim ribbon As IRibbonUI
Dim protectButton As IRibbonControl
Dim IsProtected As Boolean

Sub InvalidateRibbonButtonOnAction(control As IRibbonControl)
'MsgBox "Invalidate"
    ribbon.Invalidate
End Sub

Sub RibbonOnLoad(ActiveRibbon As IRibbonUI)
'MsgBox "onLoad"
    Set ribbon = ActiveRibbon
    IsThisDocumentProtected
End Sub

Sub ToggleProtectionButtonGetPressed(control As IRibbonControl, ByRef returnValue)
'MsgBox "GetPressed"
    IsThisDocumentProtected
    returnValue = IsProtected
End Sub

Sub ToggleProtectionButtonOnAction(control As IRibbonControl, ByVal pressed As Boolean)
    IsThisDocumentProtected
    If pressed Then
        If Not IsProtected Then
            ActiveDocument.Protect wdAllowOnlyFormFields
        Else
            ActiveDocument.Unprotect
        End If
    Else
        If IsProtected Then
            ActiveDocument.Unprotect
        End If

    End If
    If control.Id = "ProtectOrUnprotectDocument" Then
    '    MsgBox "Got here!"
    '   This will force ribbon invalidate only for the native command
        ProtectOrUnprotectDocumentOnAction control, False
    End If
End Sub


''''' This procedure is NOT a callback, but is called from the callbacks:

Private Sub IsThisDocumentProtected()
'''' Assigns value to module-level boolean variable for use throughout the module
    IsProtected = ActiveDocument.ProtectionType <> wdNoProtection
End Sub

''''' This is NOT a callback, either, but is invoked only for particular control press:
Private Sub ProtectOrUnprotectDocumentOnAction(control As IRibbonControl, ByRef cancelDefault)
    ribbon.Invalidate
    cancelDefault = False
End Sub

Limitations

if I protect/unprotect with the built-in task pane (Review > Restrict Editing) or programmatically (ActiveDocument.Protect/Unprotect), my custom button stays ignorant of the change.

This will not work for protection that is applied programmatically. As for the Review > Restrict Editing, I think you simply need to hijack that command's onAction procedure in the same way we have done, above, by adding another command to the XML and referring it to the same onAction procedure.