Docking WPF controls in the VSTO Task Pane

One of my favorite features in VSTO is the custom task pane. It provides a very natural and unobtrusive mechanism to expose your add-in functionality, fully integrated into Office, and makes it possible to use WPF for user interface development.

If you want your Task Pane to look seamless to your user, you will probably need to play a bit with Docking. If not, two specific issues could arise:

• Your WPF control is fairly small, and doesn’t take all the surface of the Task Pane, leaving the original WinForms color background visible in the uncovered areas,
• Your WPF control is too large for the Task Pane surface, leaving parts of the control invisible to the user, who cannot access them.

The first situation is mostly aesthetics (it just looks ugly), but the second case is a bit problematic, as it could make your Task Pane virtually unusable.

To illustrate the issue, let’s create an Excel 2007 Add-In project “AddInLab” in Visual Studio, add a WinForms control TaskPaneWpfHostControl, drop an ElementHost control in there, which we rename to wpfElementHost, instead of elementHost1, and set its Dock property to Fill so that it takes up the entire surface of the control. We’ll edit the code-behind, to provide access to the ElementHost via a public property:

namespace AddInLab
{
using System.Windows.Forms;
using System.Windows.Forms.Integration;

public partial class TaskPaneWpfControlHost : UserControl
{
{
InitializeComponent();
}

public ElementHost WpfElementHost
{
get
{
return this.wpfElementHost;
}
}
}
}


Now let’s add two preposterous WPF controls in our project, SmallWpfControl (a 50 by 50 red square), and BigWpfControl (a 1000 by 1000 green square):

<UserControl x:Class="AddInLab.SmallWpfControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid Width="50" Height="50" Background="Red">
<TextBlock Text="Tiny" Foreground="White"/>
</Grid>
</UserControl>

<UserControl x:Class="AddInLab.BigWpfControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid Height="1000" Width="1000" Background="Green">
<TextBlock FontSize="42" Text="Big Big Control" Foreground="White"/>
</Grid>
</UserControl>


In the add-in start-up method, we can now create a task pane, and add the SmallWpfControl to it, like this:

public partial class ThisAddIn
{
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
var wpfControl = new SmallWpfControl();
}
// rest of the code omitted
}


Running this code produces the following result:

Replacing with the big control produces an even less satisfying result:

So how can we address that issue?

We would like to see two things happen:

• The entire surface of the Task Pane should be covered by a WPF control,

• If our WPF control is too large, we should have scroll bars allowing us to navigate over the entire surface of the control.

To achieve that result, we will add another Matrioshka to the collection, and create a new WPF control responsible for the layout: it will occupy all the space available, and display scroll bars when they are needed. That control, TaskPaneWpfControl, contains three WPF controls:

• a ScrollViewer, with the two ScrollBars set to Auto. The purpose of this control is to display automatically scrollbars if the size of the contents exceed the surface available. The cool thing about this is that if scrollbars are not needed, they won’t be displayed at all, leaving the entire surface available,

• a DockPanel, which will expand to fill the entire surface it has available,

• a StackPanel, which will display WPF controls stacked from the top of the Task Pane.

<UserControl x:Class="AddInLab.TaskPaneWpfControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<ScrollViewer
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<DockPanel Background="Yellow">
</DockPanel>
</ScrollViewer>
</UserControl>


We can now modify our add-in start-up code to use our new control:

private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
var wpfControl = new SmallWpfControl();