Tuesday, March 18, 2014

WPF - Custom ToolTip Placement with more Options

WPF allows us various options to customize ToolTips. We also have different options for appropriate placement of ToolTip. But they are very limited and just include positioning the ToolTip on top, bottom, right and left of a control. In this post, we are trying to create a behavior to appropriately place our ToolTip. The behavior should allow us to position it around different edges and corners e.g. Bottom - Right.



Let us create a WPF project for defining this behavior. We need to first add these enumerations to allow users to specify the placement of ToolTip.


Now let us add a behavior for placement of tooltip. You would need to add an assembly reference of System.Windows.Interactivity from Blend SDK to use Behavior<T>. In order to place the tooltip, we are using the two enumerations defined above. Since these would be static values assigned in XAML, we are introducing them as CLR properties. If we need to bind them, we can add them as dependency properties.


Since we are custom placing the tooltip, we can use CustomPlacementCallBack delegate defined for a ToolTip. We can assign a computation method to the delegate. The method can calculate the placement of the ToolTip based on the option specified in the HorizontalPlacement and VerticalPlacement properties


Now we need to calculate where we want to show the ToolTip. In addition to the options specified using VerticalPlacement and HorizontalPlacement properties, the delegate arguments provide us the details about the size of target control, from targetSize parameter, on which the ToolTip is being displayed. We also have the details about the ToolTip size from popupSize parameter. The behavior's user can also specify further offsets for the ToolTip using ToolTipService.VerticalOffset and ToolTipService.HorizontalOffset attached properties on the control. They are available in offset parameter. Now we have all the details to calculate the exact position to place the ToolTip.


Let's first define the templates for out ToolTip. This is how we want to display our ToolTip.


Now let us see how we need to add this to a target control. Here we are adding our behavior to a Border control. Using the attached properties in ToolTipService, we have specified that we want to place the tooltip based on some custom calculations. For a complete description of features of ToolTipService, we can refer MSDN.


It must be remembered that, by default, when we just select Custom placement option, all the calculations would be using the top left of the target control as a reference point.



Now let's see how to calculate the vertical and horizontal offset of the ToolTip. For the Horizontal offset, we just need to move it all the way on the right side of the target. So we can use target's Width for this purpose. We can also add the horizontal offset which might be defined on the control. We can do similar calculations for calculating vertical offset.


Here is how we are calculating the horizontal offset. Based on this, you can imagine the calculation of vertical offset.



What if ToolTip cannot be displayed on Calculated Position?
Since tool tips depend on the location of target control. Now the window containing the target control might be situated as such that the tooltip cannot be displayed because of being cut by screen corners. In that case, if we just use the above code, WPF just moves the ToolTip on its own so that it can be shown. But we can totally control the alternate location if it cannot be displayed on the location we first calculated. If we just look at the signatures of CustomToolTipPlacement delegate, it is returning an array. The placement options would be prioritized as such that the first element has the highest priority. The runtime can use the next one if it cannot be displayed on the first location.

If we look at the definition of above behavior, we have defined calculation methods as protected virtual. We can override this in a child behavior to achieve the required effect. Let's do it as follows:


Get it from Git

No comments: