Ty Anderson

How to preserve the ribbon control state (or, if you develop custom Outlook ribbons…)

If you decide to build an Outlook add-in, you’re going to want to customize the user interface.

If you start customizing the Outlook user interface, you’ll probably build a custom ribbon.

If you build a custom Outlook ribbon, you’ll want to put some controls on it like a button that toggles, a checkbox, and a drop down control.

If you add these types of controls and finish writing your code, you’ll want to test your add-in.

If you hit F5, start Outlook, and test your addin, you’ll be tempted to open multiple inspector windows.

If you open multiple inspector windows, click your controls, and switch back and forth between inspectors…

You will discover the problem I aim to solve today.

Multiple Outlook inspectors, multiple problems?

Outlook utilizes two types of windows. The primary window is the Outlook Explorer window in which the user navigates between the main parts of Outlook (e.g. mail, contacts, calendar, etc.). Typically, you only have one Outlook explorer window open, although you can open multiple.

The secondary window is the Outlook Inspector window. This is the window that displays individual Outlook items (e.g. mail, contacts, calendar, etc.). It is quite common to have multiple inspector windows open at any given time. And this is the source of our problem.

What we want… what we expect… is for our custom ribbon to behave like Figure 1. But…

Custom Ribbons do not preserve their state across multiple inspector windows by default. You have to code this functionality.

Figure 1. Two Outlook inspector windows maintaining separate control states

Two Outlook inspector windows maintaining separate control states

This is our problem. How do we make it happen? I’m so glad you asked!

A strategy for maintaining a separate ribbon control state

Boiled down to its essence, the strategy is to create a custom user property and store its value in the Outlook item displayed in the inspector window. If you have multiple controls you want to maintain, you add additional user properties (see Figure 2).

Figure 2. How to maintain a control’s state across all open Outlook inspector windows

How to maintain a control's state across all open Outlook inspector windows

When the user takes an action that changes the control’s state, you update the control’s corresponding user property. When the user switches between Outlook inspector windows, you set the control state by reading the corresponding user properties and updating the ribbon controls.

Building the Outlook add-in project

This plugin for Outlook 2013, 2010 and 2007 includes a single custom ribbon with three controls: a button, a checkbox, and a drop down. In terms of code, there are a handful of methods (custom methods and control events). We are going to use Add-in Express for Office and .net.

With Visual Studio open (I’m using Visual Studio 2012), create a new project according to these steps.

  1. Create a new ADX COM Add-in project. Move through the New Microsoft Office COM Add-in wizard dialog and select VB.NET as in this example, or C#, or C++, as the language of choice and Microsoft Office as the target application. The minimum supported version needs to be Office 2007 or higher.
  2. After you have the project open in Visual Studio, add an ADXRibbonTab control to the AddinModule‘s in design surface. Change its Caption property to Preserve State.
  3. Add a ribbon button group (AdxRibbonButtonGroup control) to ribbon tab and set its caption to Preservation Group.
  4. Add a button (AdxRibbonButtoncontrol) to the ribbon button group and set these properties:
    1. Caption = FlagIt!
    2. Size = Large
    3. ToggleButton = True
    4. ImageMSO = FlagToday
    5. Add a check box (AdxRibbonCheckBox control) to the button group and set its caption to Check Me Out.
    6. Add a drop-down control (AdxRibbonDropdown)to the group.
      1. Its caption needs to be Favorite Major.
      2. Add five items to the items collection and set their captions to: (None), Australian Open, French Open, Wimbledon, and U.S. Open respectively.
      3. Set the drop-down’s SelectedItemIndex property to 0.

Hopefully, your Outlook ribbon is similar to Figure 3.

Figure 3. The custom ribbon in design view

The custom ribbon in design view

Writing the code

Before we write some code, you need to add Outlook events to the AddinModule. Right click on the AddInModule design surface and click Add Events. In the Add Application Events dialog box select Microsoft Outlook Events and click OK. This action adds an adxOutlookEvents control to the AddInModule and make Outlook events available in the AddinModule class.

Now we are ready to code.

The InspectorActivate Event

We’ll begin with the end. The InspectorActivate event is the one that syncs control state with the Outlook item’s custom user properties. Open the AddInModule’s code view and add the following code:

Private Sub adxOutlookEvents_InspectorActivate(sender As Object, _
	inspector As Object, folderName As String) _
	Handles adxOutlookEvents.InspectorActivate
 
    Dim flagValue As String = GetStateProperty(inspector, "FlagIt")
    AdxRibbonButton1.Pressed = False ' default value
    If Not String.IsNullOrEmpty(flagValue) Then
        AdxRibbonButton1.Pressed = Convert.ToBoolean(flagValue)
    End If
 
    Dim checkValue As String = GetStateProperty(inspector, "CheckIt")
    AdxRibbonCheckBox1.Pressed = False ' default value
    If Not String.IsNullOrEmpty(checkValue) Then
        AdxRibbonCheckBox1.Pressed = Convert.ToBoolean(checkValue)
    End If
 
    Dim favValue As String = GetStateProperty(inspector, "Favorite")
    AdxRibbonDropDown1.SelectedItemIndex = 0 ' default index
    If Not String.IsNullOrEmpty(favValue) Then
        AdxRibbonDropDown1.SelectedItemIndex = Convert.ToInt32(favValue)
    End If
End Sub

This procedure does the same thing three times… once for each of our Outlook ribbon controls as follows:

  1. Calls the GetStateProperty function (explained further below) and stores it in a variable.
  2. Sets the default state of the control.
  3. If a value was returned by GetStateProperty, it sets the value of the control’s relevant state property (i.e. Pressed or SelectedItemIndex).

This is how the magic happens but there is a supporting cast of methods.

SetStateProperty method – save the control’s state to the user property

The SetStateProperty method accepts two parameters: propName and propValue. The method then retrieves the user property and sets its value. If the user property does not exist, the method creates it. Insert the following code into the AddinModule class.

Private Sub SetStateProperty(propName As String, propValue As String)
    Dim inspector As Outlook._Inspector = Me.OutlookApp.ActiveInspector
	Dim mailItem As Outlook._MailItem = Nothing
	Dim userProperties As Outlook.UserProperties = Nothing
	Dim userProperty As Outlook.UserProperty = Nothing
 
	mailItem = TryCast(inspector.CurrentItem, Outlook._MailItem)
	If mailItem IsNot Nothing Then
		userProperties = mailItem.UserProperties
		userProperty = userProperties.Find(propName)
		If (userProperty IsNot Nothing) Then
			userProperty.Value = propValue
		Else
			userProperty = userProperties.Add(propName, Outlook.OlUserPropertyType.olText)
			userProperty.Value = propValue
		End If
		mailItem.Save()
	End If
 
	If userProperty IsNot Nothing Then Marshal.ReleaseComObject(userProperty)
	If userProperties IsNot Nothing Then Marshal.ReleaseComObject(userProperties)
	If mailItem IsNot Nothing Then Marshal.ReleaseComObject(mailItem)
	If inspector IsNot Nothing Then Marshal.ReleaseComObject(inspector)
End Sub

The method also checks to ensure it is working with the mail item and cleans up its reference COM objects.

GetStateProperty function – retrieve the user property’s value

GetStateProperty works like SetStateProperty but in reverse. This function accepts a context object and a propName string as parameters. The context is the inspector window while the propName is the user property whose values we want to retrieve. Insert the following code into the AddinModule class.

Private Function GetStateProperty(context As Object, propName As String) As String
	Dim result As String = String.Empty
	Dim inspector As Outlook._Inspector = TryCast(context, Outlook._Inspector)
	If inspector IsNot Nothing Then
		Dim mailItem As Outlook._MailItem = TryCast(inspector.CurrentItem, Outlook._MailItem)
		If mailItem IsNot Nothing Then
			Dim userProperties As Outlook.UserProperties = mailItem.UserProperties
			Dim userProperty As Outlook.UserProperty
 
			userProperties = mailItem.UserProperties
			userProperty = userProperties.Find(propName)
			If userProperty IsNot Nothing Then
				result = userProperty.Value.ToString()
				Marshal.ReleaseComObject(userProperty)
			End If
 
			Marshal.ReleaseComObject(userProperties)
			Marshal.ReleaseComObject(mailItem)
		End If
	End If
	Return result
End Function

AdxRibbonButton1_OnClick Event

The ribbon button’s click event calls the SetStateProperty method. It specifies “FlagIt” as the propName and passes the button’s pressed state as a string value.

Private Sub AdxRibbonButton1_OnClick(sender As Object, _
	control As IRibbonControl, pressed As Boolean) _
	Handles AdxRibbonButton1.OnClick
 
	SetStateProperty("FlagIt", pressed.ToString())
End Sub

AdxRibbonCheckBox1_OnClick Event

The ribbon check box’s click event calls the SetStateProperty method. It specifies “CheckIt” as the propName and passes the check box’s pressed state as a string value.

Private Sub AdxRibbonCheckBox1_OnClick(sender As Object, _
	control As IRibbonControl, pressed As Boolean) _
	Handles AdxRibbonCheckBox1.OnClick
 
	SetStateProperty("CheckIt", pressed.ToString())
End Sub

AdxRibbonDropDown1_OnAction Event

The ribbon drop down’s OnAction event also calls the SetStateProperty method. It specifies “Favorite” as the propName and passes the drop down control’s selectedIndex value as a string.

Private Sub AdxRibbonDropDown1_OnAction(sender As Object, _
	control As IRibbonControl, selectedId As String, selectedIndex As Integer) _
	Handles AdxRibbonDropDown1.OnAction
 
	SetStateProperty("Favorite", selectedIndex.ToString())
End Sub

Give it a test

You are now ready to test the add-in. If you have Outlook open, close it. Then press F5 in Visual Studio. When Outlook displays, open a few mail items in inspector windows. Change some custom ribbon controls’ state values and switch between inspectors. Your controls should successfully maintain their property state:

Ribbon controls preserve their states in different inspector windows of Outlook 2013

If they don’t, you can complain to the author in the comments section.

If you complain to the author, he’ll probably become bothered.

If he becomes bothered, he’ll spend all day wondering why it didn’t work.

If he spends all day wondering why it didn’t work, he’ll find a fix.

If he finds a fix, he’ll post it in the comments…

Available downloads:

This sample Outlook add-in was developed using Add-in Express for Office and .net:

Sample Outlook PreserveState add-in – VB.NET

You may also be interested in:

15 Comments

  • Eric Legault says:

    Awesome, makes much more sense than my unfinished article from a few years ago! How did you do this without using Invalidate and Property Changing?? Mind = blown!

  • Ty Anderson says:

    Thanks Eric. I started down the path of using Property Changing but it wasn’t 100% reliable. With some help from Dmitry, I moved to using the click (or onChange events).

    Rock On my friend!

  • Rajkumar N says:

    Hi Add-in-Express Team,

    My purchaser mail id : [removed for security reasons]

    I have used the AdxRibbonCommand for handle the button click in Ribbon tab control
    (Ex) Home Tab – Cut

    I have used OnAction event for handle the button click and it is working also.
    But when I have used Key press like (Alt + H + X), the action of cut are also triggered.
    My Question is How to separate the OnAction event and KeyDown event and It does not affect the Excel’s cut functionality

    I need your kindly quick reply for me

    Regards
    Rajkumar N

  • Dmitry Kostochko (Add-in Express Team) says:

    Hi Rajkumar,

    I think it is impossible to distinguish between these events because both of them (clicking on the Cut ribbon button and pressing the keyboard combination) lead to the same action – calling Excel’s Cut functionality.

  • Romain V says:

    Very nice solution ! I agree with Ty, PropertyChanging is not reliable.

    Only problem with this method is, if the user simply opens the inspector, and then try to close it, he would get the “Do you want to save your changes?” message, as we have change the userProperties (but he hasn’t !).

    Do you know a way to avoid this ?

  • Ty Anderson says:

    Hi Romain,

    Great catch!
    The way to prevent this message box is to save the mail item after updating its custom properties.
    This should be done in the SetStateProperty method as follows (look for the bold text):

    Private Sub SetStateProperty(propName As String, propValue As Boolean)
            Dim inspector As Outlook.Inspector
            Dim mailItem As Outlook.MailItem
            Dim userProperties As Outlook.UserProperties
            Dim userProperty As Outlook.UserProperty
            inspector = Me.OutlookApp.ActiveInspector 'I don't think this guarantees I have the right inspector but I'm trying to hurry at the moment
            mailItem = inspector.CurrentItem
            userProperties = mailItem.UserProperties
            userProperty = userProperties.Find(propName)
            If (userProperty IsNot Nothing) Then
                userProperty.Value = propName
            Else
                userProperty = userProperties.Add(propName, Outlook.OlUserPropertyType.olText)
                userProperty.Value = propName
            End If
            mailItem.Save()
            If (userProperty IsNot Nothing) Then Marshal.ReleaseComObject(userProperty)
            If (userProperties IsNot Nothing) Then Marshal.ReleaseComObject(userProperties)
            If (mailItem IsNot Nothing) Then Marshal.ReleaseComObject(mailItem)
        End Sub

    I tested this change on my system and it resolved the issue.

    Ty

  • Romain V says:

    Thanks for your reply Ty.

    I haven’t tested your solution yet, because I expect it to raise a Write event. I feel like this would be even worse, especially for meetingItems.

    Do you confirm it does that ? If so, would you have another solution ?

    Romain V

  • Ty Anderson says:

    Romain,

    You are correct. It will raise the Write event.
    If you need to suppress, I suggest:

    1. Declare a class level object with WithEvents (Dim WithEvents mi As Outlook.MailItem)
    2. Respond to the object’s Write and cancel the event:

    Private Sub mi_Write(ByRef Cancel As Boolean) Handles mi.Write
    Cancel = True
    End Sub

    This might do the trick. I have not tested with AppointmentItems.
    I assume you want to suppress Outlook’s prompting to send updates to other meeting attendees?

    Ty

  • Surender Singh says:

    Hi Ty, This is a great article! But I have the same problem as mentioned in the comments by Romain. I do not wish to call Save in the SetProperty call because that goes and creates a draft for this item.
    The nature of my product is that we do not allow user saving data when a flag is On.
    How can I preserve the ribbon state without calling Save.

    Thanks much for your help!

  • Ty Anderson says:

    Hello Surender,

    I’m sorry for the delay but Dmitry and I took some time to discuss it.
    The best way to do this is according to the strategy I explained.
    If you avoiding a draft email is a must, then you could try tracking the Inspector window’s HWND property.
    The AddInModule actually has a method for this (GetOutlookWindowHandle).
    If you combine this tracking with a KeyValuePair collection to track the ribbon controls and their state, you can make it work.

    Something like this:
    internal KeyValuePair<IntPtr, Settings> _settings;

    Another option is to use store the MailItem instead:
    internal KeyValuePair<Outlook._MailItem, Settings> _settings;

    We have not tested either of this ideas they can work.
    That said, the easier option is to call Save. The draft will delete after sending.
    You can also peform periodic checks of the drafts folder and delete them if required.

    Ty

  • Eric Legault says:

    I just stumbled upon this again, and after implementing a similar solution that relies on PropertyChanging events I’m now more than a little concerned that it’s not “100% reliable”. In what way exactly? I’ve found it much more reliable than setting the Ribbon controls directly, which appears to be very unreliable in that the state doesn’t always update – even with Inspector.Activate events. I instead use Wrapper classes and set control state values in class properties, and read those properties during the PropertyChanging event. When I change the control state I do NOT set the control property – just the relevant class property – and call Invalidate() and let the PropertyChanging event retrieve the class property value into e.Value.

  • Eric Legault says:

    What I also meant to say is there are definitely pros and cons to each approach. Ty’s way is more simple by far and very elegant, and should handle the majority of situations. Using wrappers is decidedly more complex, but gives you more options for handling advanced or special scenarios (especially when ribbon updates need to happen due to changes in the message itself – Inspector.Activate only fires when switching windows). If handling PropertyChanging events aren’t 100% reliable then Ty’s way will always work.

  • Dmitry Kostochko (Add-in Express Team) says:

    Hello Eric,

    Thank you for sharing your thoughts with our readers!

  • Malick says:

    I have found another way (tested for excel with C# but the idea is the same) you can see my post here : https://stackoverflow.com/questions/23418686/excel-2013-vsto-ribbon-edit-controls/33153525#33153525

  • Andrei Smolin (Add-in Express Team) says:

    Hello Malick,

    That page doesn’t describe another way; it contains a problem description that doesn’t relate to Add-in Express.

Post a comment

Have any questions? Ask us right now!