How to customize the appearance of advanced task panes and Outlook regions
Starting with version 8.2, Add-in Express began to open its internal infrastructure, which allows developers to customize some areas of advanced Excel, Word and PowerPoint task panes and Outlook regions. In the new release, it becomes possible to customize the header together with its controls as well as splitter and border. You can also modify the appearance of a minimized region, control the header visibility, change padding, get HitTest info and more.
Since the implementation of the Add-in Express for Office and .net infrastructure in the part of embedding forms is identical for all Office applications (the difference is only in class names), let’s take an Excel task pane as an example, examine its main elements, and see how you can customize them to your liking.
For example, take the default design of an advanced Excel task pane:
… and turn it into this one:
Simply changing the form color, i.e. setting BackColor to green and the UseOfficeThemeForBackGround property to false will not be enough, because this will paint only the form in the desired color, whereas the other parts of the pane will be drawn according to Excel’s color theme, like this:
In order to understand what to do next, I used a graphical editor and outlined the areas that make up the pane, and that will be of interest to us.
The above screenshot shows the pane in the expanded state, which consists of three main areas: Form, Header and Splitter areas. For better perception, these areas are offset from the edge of the pane at some distance called Padding. The Form area is of no interest to you because it accommodates the content you create at design time. But Header and Splitter together with their contents as well as the Padding area can be changed the way you want.
What is an advanced task pane?
In terms of Add-in Express, the task pane is a specific control (ADXContainerControl) within which your form(s) is located. When several forms are located on the pane, it stores them in a list.
When the task pane is redrawn, first it paints its background in some color, and then draws the header and splitter. The area under the form is not painted. When the user hovers over some button on the header or splitter, the pane is redrawn completely.
How to modify the task pane appearance
Add-in Express implements the basic functionality of the task pane in the AddinExpress.Extensions.ADXContainerControl class. From this class, the following classes are inherited: ADXContainerControlOL, ADXContainerControlXL, ADXContainerControlPP, ADXContainerControlWD, which consider the behavior and drawing specific to Outlook, Excel, Power Point and Word, respectively.
To change the standard design of the task pane, you need to create a new class inherited from one of the above classes, and override the required virtual methods or properties. In this example, we inherit from the ADXContainerControlXL class.
public class MyContainerControl : ADXContainerControlXL { }
In addition, you need to specify that the new class should be used instead of the default one. For this, override the GetContainerControlType method in the ADXExcelTaskPane class, and return the type of your new class.
protected override Type GetContainerControlType() { return typeof(MyContainerControl); }
And now, you need to override and implement the properties and methods of your new MyContainerControl class. Here, I will describe only the most essential ones, and you can download the full code of this example, using this link.
To fill the whole drawing area of the task pane with the required color, override the BackColor property:
public override Color BackColor { get { return Color.FromArgb(0x21, 0x73, 0x46); } }
Additionally, you can override the Padding property to adjust internal spacing of the Header, Splitter and Form areas relative to the pane’s borders, for example:
public override Padding Padding { get { Padding offsets = base.Padding; if (owner.RegionState == AddinExpress.XL.ADXRegionState.Hidden) offsets = new Padding(1, 2, 1, 2); return offsets; } }
To properly draw the icons on the buttons, you need to know the pane’s layout and position (vertical or horizontal). For this, you can override the OnInitialized method and store these values in internal variables.
This method will be called whenever the pane is initialized, e.g. after showing the pane, changing its layout, or switching to another Office theme.
protected override void OnInitialized() { base.OnInitialized(); owner = Owner as ExcelTaskPane1; layout = owner.Item.Position; isVertical = false; switch (layout) { case AddinExpress.XL.ADXExcelTaskPanePosition.Left: case AddinExpress.XL.ADXExcelTaskPanePosition.Right: isVertical = true; break; } }
And now, let’s see what methods are directly involved in drawing various pane controls.
Because the background is dark, we are using the white text color:
protected override void OnDrawText(DrawTextArgs e) { e.ForeColor = Color.White; base.OnDrawText(e); } protected override void OnDrawHeader(DrawArgs e) { //base.OnDrawHeader(e); e.DrawControls(); }
Please note that for the OnDrawHeader method, we do not call the base method and draw only child controls (by calling the e.DrawControls () method). Calling the base method isn't required here because of the task chosen: draw child controls on a custom background; if you call the base method, the background can be drawn with a gradient in some Excel versions (e.g. Excel 2010).
The next method that we are going to override is OnDrawControl (DrawControlArgs e). This method is called every time when any controls listed in ADXDrawControlType is rendered.
protected override void OnDrawControl(DrawControlArgs e) { if (e.ControlType == ADXDrawControlType.Separator) // Excel 2010 and below return; // Draw any control except for splitter button if (e.ControlType != ADXDrawControlType.Splitter) { // Determine whether the button is available or not. bool disabled = false; switch (e.ControlType) { case ADXDrawControlType.Previous: disabled = !IsScrollButtonPresent(false); break; case ADXDrawControlType.Next: disabled = !IsScrollButtonPresent(true); break; } // If the button is available and has been clicked, or the mouse cursor hovers // over it, fill the background color corresponding to the button state. if (!disabled && e.State != ADXDrawControlState.Up) { SolidBrush br = new SolidBrush(e.State == ADXDrawControlState.Focused ? btnBackColor : btnPressedColor); e.Graphics.FillRectangle(br, e.Bounds); br.Dispose(); } // Take the button image from the resources // and draw it using the DrawImage method. Image image = GetSkinImage(e.ControlType); if (image != null) { Size imgSize = image.Size; Rectangle destRect = new Rectangle( e.Bounds.Left + (e.Bounds.Width - imgSize.Width) / 2, e.Bounds.Top + (e.Bounds.Height - imgSize.Height) / 2, imgSize.Width, imgSize.Height); // If the button is disabled, draw it with 50% transparency. float alpha = disabled ? 0.5f : 1.0f; DrawImage(e.Graphics, image, destRect, new Rectangle(Point.Empty, imgSize), alpha); } e.DrawControls(); } else base.OnDrawControl(e); }
How to customize the appearance of the minimized task pane
In the Minimized state, we need to display all forms located on the pane. The forms reside in the ContainerItems collection. Every element of the collection is a ContainerItem object, which is displayed on the pane as a rectangle with an icon and caption.
To change the appearance of ContainerItems, override the OnDrawContainerItem method. You can draw the icon and text directly in this method, though we recommend using special methods – OnDrawIcon and OnDrawText.
protected override void OnDrawContainerItem(DrawContainerItemArgs e) { if (e.State == ADXDrawControlState.Selected) { // ContainerItem switches to the Selected state after the item has been clicked // and the floating form has appeared. SolidBrush br = new SolidBrush(Color.FromArgb(0x0a, 0x63, 0x32)); e.Graphics.FillRectangle(br, e.Bounds); br.Dispose(); } else if (e.State != ADXDrawControlState.Up) { SolidBrush br = new SolidBrush(e.State == ADXDrawControlState.Focused ? btnBackColor : btnPressedColor); e.Graphics.FillRectangle(br, e.Bounds); br.Dispose(); } // Call this method to raise the OnDrawIcon and OnDrawText methods in order // to draw the icon and text. e.DrawControls(); }
By the way, the ADXExcelTaskPane, ADXWordTaskPane, ADXPowerPointTaskPane and ADXOlForm classes have the OnDrawBorder method which draws the border of a form that appears only in the floating mode. This method is virtual and can also be overridden to render the border in the desired style, for example:
protected override void OnDrawBorder(Graphics graphics, Rectangle bounds) { base.OnDrawBorder(graphics, bounds); Pen pen = new Pen(Color.FromArgb(0x0a, 0x63, 0x32)); Rectangle R = new Rectangle(0, 0, bounds.Width-1, bounds.Height-1); graphics.DrawRectangle(pen, R); pen.Dispose(); }
In the below screenshot, you can see how the appearance of the minimized panes has changed after our manipulations.
The next screenshot shows the custom design of the floating pane.
To better understand the order in which the methods rendering an advanced task pane are called, have a look at the structure below:
Pane in the normal state:
OnDraw() { OnDrawHeader() //draw the header background { OnDrawControl(); //draw the previous button if it is visible OnDrawControl(); //draw the next button if it is visible OnDrawControl() //draw the caption control { OnDrawIcon(); //draw the icon image if it is visible OnDrawText(); //draw the caption OnDrawControl(); //draw the menu button } OnDrawControl(); //draw the close button OnDrawControl(); //draw the minimize button } OnDrawSplitter() //draw the splitter background { OnDrawControl(); //draw the splitter button } }
Minimized pane:
OnDraw() { OnDrawControl(); //draw the previous scroll button if it is visible OnDrawControl(); //draw the next scroll button if it is visible OnDrawControl(); //draw the minimize button OnDrawContainerItem() //draw the container item background { //draw the container item 1 OnDrawIcon(); //draw the icon image if it is visible OnDrawText(); //draw the form caption } OnDrawControl(); //draw the separator, supported only in some Office versions OnDrawContainerItem()//draw the container item background { //draw the container item 2 ... } }
Hidden pane:
OnDraw() { OnDrawSplitter()//draw the splitter background { OnDrawControl(); //draw the splitter button } }
Available downloads:
Custom Excel task pane – C# and VB.NET sample
20 Comments
Good beginning!
Hello, thanks for the article!
Is there a way that I can easily switch between the standard built-in themes without having to override all these functions and provide color codes and icons?
For example, lets say the that I am using Excel 2016 with the Dark Gray theme. By default, the Addin-Express CTP will be themed to match Excel 2016 Dark Gray, but what if I wanted it to instead show the Excel 2010 Black theme for the CTP? Can this be achieved using the current API or can the API be updated to have this functionality?
Hello,
Unfortunately, this method is not available for use. The design of our TaskPanes was developed for a specific version of the Office application and its color scheme. Applying these settings to a different color scheme and a different Office version will result in a distorted design or visual artifacts that anyway will have to be fixed by using the same customization as described in this article.
is it outlook panel or Excel pane ?
Hello Shyam,
This functionality works for Advanced Outlook regions as well as Word, Excel and Power Point Task panes.
Hi,
This worked thanks!
How can I change the borderstyle on a ADXContainerControlXL? It does not have the OnDrawBorder virtual method..
Hello Gert-Jan,
In your class descending from ADXContainerControlXL, you override the OnDraw method:
protected override void OnDraw(DrawContainerControlArgs e)
{
base.OnDraw(e);
Pen pen = new Pen(Color.DarkRed);
var rect = e.Bounds;
e.Graphics.DrawRectangle(pen, rect.Left, rect.Top, rect.Width – 1, rect.Height – 1);
pen.Dispose();
}
Thx Andrei! Instead of preventing the line I draw over it in the right color, that works fine.
Hi Alexander
I would like to try out the custom task pane sample but see it’s in C#.
Is there a VB version available to download?
Thanks!
Trevor
Hello Tom,
Sorry, we will need some tome to find out that code or to recreate it. I suppose, we will respond on Wednesday-Thursday.
Hello Tom,
We’ve updated the download above. Now it contains both C# and VB projects. You may need to refresh your browser cache (Ctrl+F5) to get the updated download package.
Hi Andrei, thanks I have opened the VB project but am having trouble – when I hit F5, dialog box says “debug target is missing…”.
I have provided the location to my Excel file (C:\Program Files\Microsoft Office\root\Office16\EXCEL.EXE) but still no joy. The application’s assembly name is CustomPaneVB and the build output path is bin\debug\
Am I doing something wrong, I assume I just build and run ?
I’ve had the same problem with the other download samples on your site so it must be a problem my end. I have another project which is opening fine with the above Excel location, so that works ok….
Thanks, Trevor
Hello Tom,
Set the VB project as the startup project, make sure that the Start External Program option is selected and the corresponding path points to the host Office application, register the VB add-in project and press F5. If this doesn’t help, start Excel using the Start menu, make sure your add-in is loaded and use menu Debug | Attach to Process to connect the debugger to your add-in code.
Thanks Andrei, I can load the sample up now.
So I am now including the ContainerPane.vb class, AddinImages.resx and AddinImages.Designer.vb from your sample into my project.
I’ve switched off Option Strict On to get rid of the integer/double conversion problems.
However IDE is reporting the AddinImages resources as being not declared generating a lot of errors. Do I need to somehow merge the AddinImages resources into My.Resources within my project?
Finally the top of Container.vb class has:
Private form as ExcelTaskPane1 = Nothing
For my code should this be:
Private form as AddinExpress.XL.ADXExcelTaskPane ????
I think these three files are all I need from the sample, is that right?
Thanks as always
Tom.
Hello Tom,
I suppose you haven’t imported the AddinImages correctly. If you copy it to your project folder, you should copy two files:
AddinImages.resx
AddinImages.Designer.vb
Then in your project you use ‘Add existing file’ to add any of these; the second one should be added automatically. Note that code in AddinImages.Designer.vb is in namespace My.Resources. Also note that ContainerPane.vb imports CustomPaneVB.My.Resources.
As to your other question, you should study the project: ExcelTaskPane1 is not an ADXExcelTaskPane.
Thanks Andrei,
The files were included but AddinImages.resx was causing my Errors pane to report it as an ‘invalid resources file’, so I managed to extract the PNG images from the AddinImages.resx file, rename them with ADX_ prefix and then add them as existing files directly into My Project / Resources window……
The example you’ve provided is great for suggesting further potential for pane customisation – but I’d really appreciate a little help on two items:
(1) How do I modify the text (caption) within the headerof the advanced excel task pane i.e. so it shows in 12pt font ?
(2) I am trying to remove the thin border around the edge of the pane area. I’ve tried adjusting the pen colors in the code but the color is always dotted grey. Can you tell me how I might do this?
Best regards
Tom
Hello Tom,
Protected Overrides Sub OnDrawText(e As DrawTextArgs)
Dim font = e.Font
e.Font = New Font(Me.Font.FontFamily, 12.0F)
If Not IsNothing(font) Then font.Dispose()
MyBase.OnDrawText(e)
End Sub
Protected Overrides Sub OnDraw(e As DrawContainerControlArgs)
MyBase.OnDraw(e)
Dim pen = New Pen(Color.White)
Dim rect = e.Bounds
e.Graphics.DrawRectangle(pen, rect.Left, rect.Top, rect.Width – 1, rect.Height – 1)
pen.Dispose()
End Sub
Andrei
You are a genius thank you! Your two code subs worked fine (changing pen from white to dimgray as I am using dark themes). In fact, it even inspired me to check out the containerpane events and I can see the OnDraw etc. events on the right, so now I am finally ‘on a roll’…
One last question if I may be so bold? I tried using the OnDrawHeader event to increase the height of the captioncontrol within the header to 30 points with the following:
Protected Overrides Sub OnDrawHeader(ByVal e As DrawArgs)
e.DrawControls()
Dim pen As Pen = New Pen(Color.DimGray)
Dim rect As Rectangle = e.Bounds
e.Graphics.DrawRectangle(pen, rect.Left, rect.Top, rect.Width, 30)
pen.Dispose()
End Sub
I was trying to increase the space between the large text in the header, and the top edge of the ListView control I have added in the pane (set to dock=fill). This didn’t work – how can I increase the height of the header so that the docked ListView form does not overlap the header control?
Very best wishes, Tom
Hello Tom,
Changing the header height isn’t supported. You can disable the header completely (see ADXExcelTaskPane.EnableHeader) and draw all required controls right on your pane.
Hi, I would like to draw the minimised state even when the taskpane is open in normal state. I want something like this in normal state –
`
OnDraw()
{
OnDrawHeader() //draw the header background
{
OnDrawControl(); //draw the previous button if it is visible
OnDrawControl(); //draw the next button if it is visible
OnDrawControl() //draw the caption control
{
OnDrawIcon(); //draw the icon image if it is visible
OnDrawText(); //draw the caption
OnDrawControl(); //draw the menu button
}
OnDrawControl(); //draw the close button
OnDrawControl(); //draw the minimize button
}
OnDrawSplitter() //draw the splitter background
{
OnDrawControl(); //draw the splitter button
}
OnDrawContainerItem() //draw the container item background
{
//draw the container item 1
OnDrawIcon(); //draw the icon image if it is visible
OnDrawText(); //draw the form caption
}
OnDrawControl(); //draw the separator, supported only in some Office versions
OnDrawContainerItem()//draw the container item background
{
//draw the container item 2
…
}
}
`