- Introduction to Form Regions
- Form Region Types and Custom Message Classes
- Creating an Outlook Forms-Based Form Region
- Outlook Form Region Programmability
- Conclusion
Creating an Outlook Forms-Based Form Region
As you saw in the introduction, there are two ways to create form regions in VSTO. The first is to use the Windows Forms designers inside Visual Studio. The second is to use the Outlook Forms Designer inside Outlook. A form region designed with the Outlook Forms Designer is integrated with your .NET code through COM interop. The form region and form controls are COM objects that Visual Studio generates wrappers to communicate with.
If you use Windows Forms, you can use a forms engine that is .NET-based, which may be more familiar to you. The design-time experience for using Windows Forms is much more integrated with Visual Studio. If you use the Outlook Forms Designer, you need to design the form in Outlook and then import it into Visual Studio. If you decide that you want to change the layout or the controls on the form, you have to delete the form region in Visual Studio, go back to Outlook and redesign the form region, and then reimport it into Visual Studio via the wizard.
Although using Windows Forms is more convenient, Outlook Forms have a lot of features that Windows Forms do not. Outlook Forms, for example, have automatic data binding support to bind to properties of the Outlook items with which they are associated. Also, some Outlook Forms controls are more Outlook-like than any of the Windows Forms controls you have available to you. These controls allow you to replicate functionality available in built-in Outlook Inspector windows. Outlook Forms provide controls such as these: the Outlook Body control, which allows you to edit the item body of an Outlook item with the same editor provided for builtin Outlook editors; a Business Card control, which displays the same business-card view that is built into Outlook; and a Category control, which provides a UI for visualizing the categories with which an Outlook item is associated. So in many cases you may pick an Outlook Forms-based form region because it provides more Outlook-aware controls for you to use.
The first step in creating a form region by using Outlook Forms is launching Outlook. Next, choose Tools > Forms > Design a Form to bring up the Design Form dialog box, shown in Figure 16-22. Pick the type of built-in Outlook item type that you want to start from—for this example, Task—and then click the Open button.
Figure 16-22 The Design Form dialog box in the Outlook Forms Designer.
Next, drop down the Design button and then the Form Region button, and choose New Form Region, as shown in Figure 16-23. (You can also use the Open Form Region command under the Form Region button if you already have a form region in an .OFS file.) A new page titled (Form Region) will appear; you can design your form region in that page.
Figure 16-23 Creating a new form region in the Outlook Forms Designer.
The Tools group in the Ribbon allows you to bring up the design-time tools you will need. The Field Chooser tool lets you drag and drop fields into your form region from the Outlook item that the form region will display. These fields are automatically data bound—an advantage over Windows Forms, which require you to write additional code to bind your controls to the Outlook item associated with your form region. Also in the Tools group, the Controls Toolbox button brings up the toolbox, which displays a set of standard controls. The Advanced Properties button displays the properties window, which you can use to set properties for the selected control in the Forms Designer.
The initial set of tools in the Controls toolbox doesn’t have any of the cool controls we mentioned earlier, so let’s get them added to the toolbox. Right-click a blank area of the Controls toolbox, and choose Custom Controls from the context menu. The Additional Controls dialog box appears, as shown in Figure 16-24. Check all the controls in the list that start with Microsoft Office Outlook; then click the OK button.
Figure 16-24 Add all the controls that start with Microsoft Office Outlook to the Controls toolbox.
Figure 16-25 shows the final design environment with all the tools visible and all the additional Outlook controls added to the Controls toolbox.
Figure 16-25 The Outlook Forms Designer with the toolbox, Properties window, and Field Chooser tool.
Now drag some Outlook controls to the design surface. You’ll create the same form region you created in the introduction but use Outlook Forms this time. Find the OlkListBox control by hovering over the controls in the Controls toolbox and finding the control that shows OlkListBox in its tooltip. Drag and drop a OlkListBox, and size it to fill most of the design surface while leaving a strip at the bottom for buttons. Right-click the list-box control you added to the form, and choose Properties from the context menu to bring up the Properties dialog box, shown in Figure 16-26. Click the Layout tab, and drop down the Horizontal combo box to pick Grow/Shrink with Form. This setting allows the list box to size to fill the Inspector window.
Figure 16-26 The Properties dialog box with the Layout tab.
While the Properties dialog box is open, take some time to explore the rest of it. The Properties dialog box has a Display tab that lets you set the caption of the control, visibility, font, and color. The Layout tab lets you set size and position, as well as several useful autosizing and alignment settings. The Value tab lets you set up an automatic binding to an Outlook item property. Finally, the Validation tab lets you set up some validation rules for the control.
Next, drag two additional Outlook controls onto the design surface. Drag and drop two OlkCommandButton controls at the bottom of the Outlook Form region. The OlkCommandButton will display with a look and feel more consistent with the Outlook UI than with a CommandButton. Right-click each of the OlkCommandButton controls, and choose Properties from the context menu to display the Properties window. In the Display tab, set the caption of one button to Add and the other to Delete. Also, in the Layout tab, set the Vertical drop-down menu to Align Bottom for each of the two buttons to ensure that the buttons stay at the bottom of the form when the form is resized. The final form region should look like Figure 16-27.
Figure 16-27 A form region designed in the Outlook Form Designer.
With a form region designed, you need to export the form region to an Outlook form region file with a .OFS extension, which then can be imported into Visual Studio. To save the form region as an .OFS file, drop down the Design button; then drop down the Form Region button and choose Save Form Region As to bring up the Save dialog box. Save it as MyFormRegion for this example, as shown in Figure 16-28.
Figure 16-28 Saving the form region to an Outlook form region file (.OFS file).
Start Visual Studio, and either create a new Outlook add-in project or open an existing Outlook add-in project. Choose Project > Add New Item. Click the Office category to show just the Office-specific items. In the list of Office items, click Outlook Form Region (refer to Figure 16-2). Name the form region—for this exercise, FormRegion2. Then click the Add button.
In the first page of the New Outlook Form Region wizard that appears, pick Import an Outlook Form Storage (.ofs) File, as shown in Figure 16-29. Click the Browse button, and locate the .OFS file that you saved earlier.
Figure 16-29 Importing an .OFS file in the New Outlook Form Region wizard.
With a .OFS file selected, click the Next button to move to the page where you set the type of the form region. For this example, select Separate as the Form Region type. Then click the Next button. On the next page, set the name you want to use for the form region—for this exercise, Subtasks—and make sure that the check boxes for Inspectors that are in compose mode and Inspectors that are in read mode are checked. Click the Next button to move to the final page. On this page, make sure that only the check box next to Task is checked; then click the Finish button. Visual Studio creates a new project item for the form region.
No visual designer is displayed within Visual Studio—just generated code. As we mention earlier in this chapter, if you want to change the form region, you have to delete the form region code item from your Visual Studio project, go back to Outlook and reopen the .ofs file, modify your form region, save it an .ofs file, exit Outlook, and then re-create the form region in Visual Studio.
The code looks very similar to the code for a Windows Forms-based form region. As before, you have a form region class with a nested form region factory class. The form region factory class has a FormRegionInitializing event handler where you can write code to determine whether to show the form region. The event handler is passed a parameter e of type FormRegionInitializingEventArgs that can be used to get the Outlook item that the form region is about to be shown for (e.OutlookItem) and to cancel the showing of the form region if necessary by setting e.Cancel to true.
The form region class has a FormRegionShowing event handler that is invoked before the form region is displayed (but too late to prevent the display of the form region altogether). In this event handler, you can write code to initialize the form region and use this.OutlookItem to access the Outlook item associated with the form region.
When the form region is closed, the FormRegionClosed event handler is invoked. This event handler is a good place to save any changes made to the Outlook item by your form region and do any final cleanup.
There are also some major differences between a Windows Forms-based form region and an Outlook Forms-based form region. Because there is no design view, no property grid like the one in Windows Forms allows you to interact with key settings—especially the Manifest settings that are editable in a Windows Forms-based form region. To compensate for this deficiency in Outlook Forms-based form regions, VSTO adds a second method called InitializeManifest to the nested form region factory code, as shown in Listing 16-4. In this method, you can modify the code to change the Form region type or any of the other settings that you initially set in the Form Region wizard.
Listing 16-4. The Default Code in a New Outlook Forms-Based Form Region
using System; using System.Collections.Generic; using System.Linq; using System.Resources; using System.Text; using Office = Microsoft.Office.Core; using Outlook = Microsoft.Office.Interop.Outlook; namespace OutlookAddIn2 { public partial class FormRegion2 { #region Form Region Factory [Microsoft.Office.Tools.Outlook. FormRegionMessageClass(Microsoft.Office.Tools.Outlook. FormRegionMessageClassAttribute.Task)] [Microsoft.Office.Tools.Outlook. FormRegionName("OutlookAddIn2.FormRegion2")] public partial class FormRegion2Factory { private void InitializeManifest() { ResourceManager resources = new ResourceManager(typeof(FormRegion2)); this.Manifest.FormRegionType = Microsoft.Office.Tools.Outlook.FormRegionType.Separate; this.Manifest.Title = resources.GetString("Title"); this.Manifest.FormRegionName = resources.GetString("FormRegionName"); this.Manifest.Description = resources.GetString("Description"); this.Manifest.ShowInspectorCompose = true; this.Manifest.ShowInspectorRead = true; this.Manifest.ShowReadingPane = false; } // Occurs before the form region is initialized. // To prevent the form region from appearing, set e.Cancel to // true. Use e.OutlookItem to get a reference to the current // Outlook item. private void FormRegion2Factory_FormRegionInitializing( object sender, Microsoft.Office.Tools.Outlook. FormRegionInitializingEventArgs e) { } } #endregion // Occurs before the form region is displayed. // Use this.OutlookItem to get a reference to the current // Outlook item. Use this.OutlookFormRegion to get a reference // to the form region. private void FormRegion2_FormRegionShowing(object sender, System.EventArgs e) { } // Occurs when the form region is closed. Use this.OutlookItem // to get a reference to the current Outlook item. Use // this.OutlookFormRegion to get a reference to the form/ // region. private void FormRegion2_FormRegionClosed(object sender, System.EventArgs e) { } } }
Another obvious difference is that the form region class created when you import a .OFS file does not derive from System.Windows.Forms.UserControls. It derives from a class in VSTO called Microsoft.Office.Tools .Outlook.Imported-FormRegion.
As you write your code in the form region class, you will find that VSTO has created member variables for all the controls you used in the .OFS file. For this example, you created a OlkListBox with a default name of OlkListBox1, an OlkCommandButton with a name of OlkCommandButton1, and an OlkCommandButton with a name of OlkCommandButton2. The import of the .OFS file converts the names used in the Outlook Form designer to camel case, so you have three controls named olkListBox1, olkCommandButton1, and olkCommandButton2.
These controls are of types that come from the Microsoft.Office .Interop.Outlook namespace. This namespace has types for many of the built-in Outlook controls. Some of the controls in the toolbox generate types that come from the Microsoft.Vbe.Interop.Forms namespace. Table 16-3 shows the names of the controls in Outlook’s Controls toolbox and the .NET types associated with these controls.
Table 16-3. Mapping Between Outlook Controls and .NET Types
Name |
Type |
Microsoft Forms 2.0 CheckBox |
Microsoft.Office.Interop.Outlook.OlkCheckBox |
Microsoft Forms 2.0 ComboBox |
Microsoft.Office.Interop.Outlook.OlkComboBox |
Microsoft Forms 2.0 CommandButton |
Microsoft.Office.Interop.Outlook.OlkCommandButton |
Microsoft Forms 2.0 Frame |
Microsoft.Vbe.Interop.Forms.UserForm |
Microsoft Forms 2.0 Image |
Microsoft.Vbe.Interop.Forms.Image |
Microsoft Forms 2.0 Label |
Microsoft.Office.Interop.Outlook.OlkLabel |
Microsoft Forms 2.0 ListBox |
Microsoft.Office.Interop.Outlook.OlkListBox |
Microsoft Forms 2.0 MultiPage |
Microsoft.Vbe.Interop.Forms.MultiPage |
Microsoft Forms 2.0 OptionButton |
Microsoft.Office.Interop.Outlook.OlkOptionButton |
Microsoft Forms 2.0 ScrollBar |
Microsoft.Vbe.Interop.Forms.ScrollBar |
Microsoft Forms 2.0 SpinButton |
Microsoft.Vbe.Interop.Forms.SpinButton |
Microsoft Forms 2.0 TabStrip |
Microsoft.Vbe.Interop.Forms.TabStrip |
Microsoft Forms 2.0 TextBox |
Microsoft.Office.Interop.Outlook.OlkTextBox |
Microsoft Forms 2.0 ToggleButton |
Microsoft.Vbe.Interop.Forms.ToggleButton |
Microsoft Office Outlook Business Card Control |
Microsoft.Office.Interop.Outlook.OlkBusinessCardControl |
Microsoft Office Outlook Category Control |
Microsoft.Office.Interop.Outlook.OlkCategory |
Microsoft Office Outlook Check Box Control |
Microsoft.Office.Interop.Outlook.OlkCheckBox |
Microsoft Office Outlook Combo Box Control |
Microsoft.Office.Interop.Outlook.OlkComboBox |
Microsoft Office Outlook Command Button Control |
Microsoft.Office.Interop.Outlook.OlkCommandButton |
Microsoft Office Outlook Contact Photo Control |
Microsoft.Office.Interop.Outlook.OlkContactPhoto |
Microsoft Office Outlook Date Control |
Microsoft.Office.Interop.Outlook.OlkDateControl |
Microsoft Office Outlook Frame Header Control |
Microsoft.Office.Interop.Outlook.OlkFrameHeader |
Microsoft Office Outlook InfoBar Control |
Microsoft.Office.Interop.Outlook.OlkInfoBar |
Microsoft Office Outlook Label Control |
Microsoft.Office.Interop.Outlook.OlkLabel |
Microsoft Office Outlook List Box Control |
Microsoft.Office.Interop.Outlook.OlkListBox |
Microsoft Office Outlook Option Button Control |
Microsoft.Office.Interop.Outlook.OlkOptionButton |
Microsoft Office Outlook Page Control |
Microsoft.Office.Interop.Outlook.OlkPageControl |
Microsoft Office Outlook Recipient Control |
Microsoft.Office.Interop.Outlook._DRecipientControl |
Microsoft Office Outlook Sender Photo Control |
Microsoft.Office.Interop.Outlook.OlkSenderPhoto |
Microsoft Office Outlook Text Box Control |
Microsoft.Office.Interop.Outlook.OlkTextBox |
Microsoft Office Outlook Time Control |
Microsoft.Office.Interop.Outlook.OlkTimeControl |
Microsoft Office Outlook Time Zone Control |
Microsoft.Office.Interop.Outlook.OlkTimeZoneControl |
Microsoft Office Outlook View Control |
Microsoft.Office.Interop.OutlookViewCtl.ViewCtl |
Microsoft Office Outlook Body Control |
Microsoft.Office.Interop.Outlook._DDocSiteControl |
As you write code against Outlook controls, you will discover that you sometimes need to cast the primary Outlook control types listed in Table 16-3 to either of two different types: Microsoft.Office.Interop.Outlook.OlkControl and Microsoft.Vbe.Interop.Forms.Control. When you cast to an OlkControl, you can set properties to configure Outlook-specific layout and binding options like those that are settable by the Properties dialog box in the Outlook Forms Designer. When you cast to a Control, you can set basic positioning properties that are common to all controls. Remember that before writing code to cast to a Microsoft.Vbe.Interop.Forms.Control, you must ensure that your project has a reference to the Microsoft Forms 2.0 Object Library.
Listing 16-5 is similar to Listing 16-2. The only difference is that it uses Outlook Forms controls, so some of the code for adding and removing items to the OlkListBox is different.
Listing 16-5. Form Region Code for a Simple Subtasks Form Region Based on Outlook Forms
using System; using System.Collections.Generic; using System.Linq; using System.Resources; using System.Text; using Office = Microsoft.Office.Core; using Outlook = Microsoft.Office.Interop.Outlook; namespace OutlookAddIn2 { public partial class FormRegion2 { Outlook.TaskItem task; Outlook.UserProperty subTasks; #region Form Region Factory [Microsoft.Office.Tools.Outlook. FormRegionMessageClass(Microsoft.Office.Tools.Outlook. FormRegionMessageClassAttribute.Task)] [Microsoft.Office.Tools.Outlook.FormRegionName( "OutlookAddIn2.FormRegion2")] public partial class FormRegion2Factory { private void InitializeManifest() { ResourceManager resources = new ResourceManager(typeof(FormRegion2)); this.Manifest.FormRegionType = Microsoft.Office. Tools.Outlook.FormRegionType.Separate; this.Manifest.Title = resources.GetString("Title"); this.Manifest.FormRegionName = resources. GetString("FormRegionName"); this.Manifest.Description = resources. GetString("Description"); this.Manifest.ShowInspectorCompose = true; this.Manifest.ShowInspectorRead = true; this.Manifest.ShowReadingPane = false; } // Occurs before the form region is initialized. // To prevent the form region from appearing, set e.Cancel to // true. Use e.OutlookItem to get a reference to the current // Outlook item. private void FormRegion2Factory_FormRegionInitializing( object sender, Microsoft.Office.Tools.Outlook. FormRegionInitializingEventArgs e) { } } #endregion // Occurs before the form region is displayed. // Use this.OutlookItem to get a reference to the current // Outlook item. Use this.OutlookFormRegion to get a reference // to the form region. private void FormRegion2_FormRegionShowing(object sender, System.EventArgs e) { this.olkCommandButton1.Click += new Microsoft.Office.Interop.Outlook. OlkCommandButtonEvents_ClickEventHandler( olkCommandButton1_Click); this.olkCommandButton2.Click += new Microsoft.Office.Interop.Outlook. OlkCommandButtonEvents_ClickEventHandler( olkCommandButton2_Click); task = this.OutlookItem as Outlook.TaskItem; if (task != null) { // Check for custom property SubTasks subTasks = task.UserProperties.Find("SubTasks", true); if (subTasks == null) { subTasks = task.UserProperties.Add("SubTasks", Outlook.OlUserPropertyType.olText, false, Outlook.OlUserPropertyType.olText); } } // Convert string string subTasksString = subTasks.Value.ToString(); if (!String.IsNullOrEmpty(subTasksString)) { string[] delimiters = new string[1]; delimiters[0] = System.Environment.NewLine; string[] tasks = subTasksString.Split(delimiters, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < tasks.Length; i++) { olkListBox1.AddItem(tasks[i], i); } } } // Occurs when the form region is closed. Use this.OutlookItem // to get a reference to the current Outlook item. Use // this.OutlookFormRegion to get a reference to the form // region. private void FormRegion2_FormRegionClosed(object sender, System.EventArgs e) { if (subTasks == null || task == null) return; string oldTasks = subTasks.Value.ToString(); StringBuilder builder = new StringBuilder(); for (int i = 0; i < olkListBox1.ListCount; i++) { string t = olkListBox1.GetItem(i); if (!String.IsNullOrEmpty(t)) { builder.AppendLine(t); } } string newTasks = builder.ToString(); if (!String.IsNullOrEmpty(newTasks) && !String.IsNullOrEmpty(oldTasks)) { if (newTasks.CompareTo(oldTasks) == 0) return; // no changes } subTasks.Value = newTasks; task.Save(); } // New Button void olkCommandButton1_Click() { // Just add current time as a subtask for simplicity this.olkListBox1.AddItem( System.DateTime.Now.ToShortTimeString(), this.olkListBox1.ListCount); } // Delete button void olkCommandButton2_Click() { if (this.olkListBox1.ListIndex != -1) { olkListBox1.RemoveItem(olkListBox1.ListIndex); } } } }