I recently tried to introduce a concept of a tooltip that appeared as a speech bubble; that is, a box that has a pointer to the originating control.
Standing on the Shoulders of Giants
The following is a rundown of links that I used extensively during this investigation:
http://jobijoy.blogspot.co.uk/2008/12/xaml-balloon-comments-expression-blend.html
http://stackoverflow.com/questions/11446250/how-to-style-wpf-tooltip-like-a-speech-bubble
http://stevenhollidge.blogspot.co.uk/2012/04/custom-tooltip-and-popup.html
Possible Approaches
In investigating this, I created four separate projects; these essentially boiled down to three different approaches:
1. A styled tooltip with no arrow
2. A tooltip using the Expression Blend “Callout” method
3. A styled tooltip using the “PathGeometry” to define a pointer
(1) had the advantage that it looked much better, but doesn’t have a concept of an arrow to the source control. (2) was by far the easiest, but the arrow style makes it look a bit like a cartoon.
In the end I opted for (3), the solution looks a little like this:
<Window.Resources> <LinearGradientBrush x:Key="LightBrush" StartPoint="0,0" EndPoint="0,1"> <GradientBrush.GradientStops> <GradientStopCollection> <GradientStop Color="#FFF" Offset="0.0"/> <GradientStop Color="#EEE" Offset="1.0"/> </GradientStopCollection> </GradientBrush.GradientStops> </LinearGradientBrush> <SolidColorBrush x:Key="SolidBorderBrush" Color="#888" /> <Style x:Key="{x:Type ToolTip}" TargetType="ToolTip"> <Setter Property="OverridesDefaultStyle" Value="true" /> <Setter Property="HorizontalOffset" Value="0" /> <Setter Property="VerticalOffset" Value="0" /> <Setter Property="Background" Value="GhostWhite" /> <Setter Property="Foreground" Value="Gray" /> <Setter Property="FontSize" Value="12" /> <Setter Property="FontFamily" Value="Segoe UI" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="ToolTip"> <Canvas Width="200" Height="100"> <Path x:Name="Container" Canvas.Left="0" Canvas.Top="0" Margin="0" Data="M 50,10 L60,0 L70,10 L100,10 L100,100 L0,100 L0,10 L50,10" Fill="{TemplateBinding Background}" Stroke="Black"> <Path.Effect> <DropShadowEffect BlurRadius="10" Opacity="0.5" ShadowDepth="4" /> </Path.Effect> </Path> <TextBlock Canvas.Left="50" Canvas.Top="28" Width="100" Height="65" Text="{TemplateBinding Content}" TextWrapping="Wrapwithoverflow" /> </Canvas> </ControlTemplate> </Setter.Value> </Setter> </Style>
You can style the bubble a bit:
<LinearGradientBrush x:Key="LightBrush" StartPoint="0,0" EndPoint="0,1"> <GradientBrush.GradientStops> <GradientStopCollection> <GradientStop Color="#FFF" Offset="0.0"/> <GradientStop Color="#EEE" Offset="1.0"/> </GradientStopCollection> </GradientBrush.GradientStops> </LinearGradientBrush> <SolidColorBrush x:Key="SolidBorderBrush" Color="#888" /> <Style x:Key="{x:Type ToolTip}" TargetType="ToolTip"> <Setter Property="OverridesDefaultStyle" Value="true" /> <Setter Property="HorizontalOffset" Value="-50" /> <Setter Property="VerticalOffset" Value="0" /> <Setter Property="Background" Value="#BE1C1C1C" /> <Setter Property="Foreground" Value="Gray" /> <Setter Property="FontSize" Value="12" /> <Setter Property="FontFamily" Value="Segoe UI" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="ToolTip"> <Canvas Width="200" Height="100"> <Path x:Name="Container" Canvas.Left="0" Canvas.Top="0" Margin="0" Data="M 50,10 L60,0 L70,10 L200,10 L200,100 L0,100 L0,10 L50,10" Stroke="Black"> <Path.Effect> <DropShadowEffect BlurRadius="10" Opacity="0.5" ShadowDepth="4" /> </Path.Effect> <Path.Fill> <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0"> <GradientStop Color="#CF181818" Offset="0"/> <GradientStop Color="#BE1C1C1C" Offset="1"/> </LinearGradientBrush> </Path.Fill> </Path> <TextBlock Canvas.Left="50" Canvas.Top="28" Width="100" Height="65" Text="{TemplateBinding Content}" TextWrapping="Wrapwithoverflow" /> </Canvas> </ControlTemplate> </Setter.Value> </Setter> </Style>
I then looked into the concept of centring the arrow, the following links looked like they might help:
http://www.blackwasp.co.uk/WPFPathMarkupsyntax.aspx
And I did try expanding the syntax:
<Path.Data> <PathGeometry> <PathGeometry.Figures> <PathFigureCollection> <PathFigure IsClosed="True" StartPoint="{Binding RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource ControlToCentreConverter}}"> <PathFigure.Segments> <PathSegmentCollection> <LineSegment Point="60,0"/> <LineSegment Point="70,10"/> <LineSegment Point="200,10"/> <LineSegment Point="200,100"/> <LineSegment Point="0,100"/> <LineSegment Point="0,10"/> </PathSegmentCollection> </PathFigure.Segments> </PathFigure> </PathFigureCollection> </PathGeometry.Figures> </PathGeometry>
The idea was to bind the line segments. I’m not saying it’s not possible; it clearly is, but it started to get prohibitively complex. If anyone comes up with a simple way of doing this (or even a complex one) then please add a link in the comments.