sohum
sohum

Reputation: 3247

WPF Tooltip positioning

I have a style on my WPF Tooltip which basically makes it look like a speech bubble. The pointy part of the bubble stops aligning properly when the control is to the right of the window because WPF re-aligns the tooltip to fit in the window. Is there anyway to apply a different style based on how the tooltip is being positioned?

My XAML looks like this:

<Style x:Key="MyToolTip" TargetType="{x:Type ToolTip}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type ToolTip}">
                <Grid x:Name="Grid">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="20" />
                        <RowDefinition Height="*" />
                    </Grid.RowDefinitions>
                    <Rectangle Fill="#fff" Stroke="#FF000000" RadiusX="4" RadiusY="4" Grid.Row="1" />
                    <Path Fill="#fff" Stretch="Fill" Stroke="#FF000000" HorizontalAlignment="Left" Margin="8,0,0,-1.5" Width="20" Grid.Row="0"
                        Data="M 0,21 L 10,0 20,21" />
                    <ContentPresenter Margin="8" Grid.Row="1" />
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
    <Setter Property="Placement" Value="Bottom" />
    <Setter Property="HorizontalOffset" Value="-2" />
    <Setter Property="Width" Value="250" />
    <Setter Property="Height" Value="Auto" />
</Style>

Upvotes: 7

Views: 8966

Answers (1)

Edward
Edward

Reputation: 8616

Standard WPF Windows 7 style

Standard WPF Windows 7 style

The standard WPF tooltip positions at the mouse pointer, which look perfect in my opinion.

The picture below illustrates your problem

Styled ToolTip with no code-behind

If you really want to do what you are asking, it is possible: you need code-behind on the style to calculate the horizontal adjustment, put this into ToolTip.Tag, and bind the pointy part border to the calculated adjustment:

Revised ToolTip style -

    <Style TargetType="{x:Type ToolTip}">
        <!-- As before, except Margin of the pointy part is now bound to calculated ToolTip.Tag -->
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type ToolTip}">
                    <Grid x:Name="Grid">
                        <Grid.RowDefinitions>
                            <RowDefinition Height="20" />
                            <RowDefinition Height="*" />
                        </Grid.RowDefinitions>
                        <Rectangle Fill="#fff" Stroke="#FF000000" RadiusX="4" RadiusY="4" Grid.Row="1" />
                        <Path Fill="#fff" Stretch="Fill" Stroke="#FF000000" HorizontalAlignment="Left" 
                              Margin="{TemplateBinding Tag}" Width="20" Grid.Row="0"
                    Data="M 0,21 L 10,0 20,21" />
                        <ContentPresenter Margin="8" Grid.Row="1" />
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
        <Setter Property="Placement" Value="Bottom" />
        <!-- Event to adjust horizontal position of the pointy part -->
        <EventSetter Event="Opened" Handler="ToolTipOpenedHandler" />
    </Style>

Code-behind

private void ToolTipOpenedHandler(object sender, RoutedEventArgs e)
{
    ToolTip toolTip = (ToolTip) sender;
    UIElement target = toolTip.PlacementTarget;
    Point adjust = target.TranslatePoint(new Point(8, 0), toolTip);
    toolTip.Tag = new Thickness(adjust.X, 0, 0, -1.5);
}

This answers your question as asked,

Styled ToolTip - Top Right

but is not sufficient when tooltip is near the bottom of the screen:

Styled ToolTip - Bottom Right

To fix this you can amend the code-behind to detect that the tooltip is above the target and set the tooltip Position to Top, and a Property Trigger style the tooltip with the pointy part below the rectangle -

Complete XAML (includes wide, narrow and multi-line tooptips)

<Window x:Class="WpfToolTip.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

        Title="MainWindow" Height="250" Width="250">
    <Window.Resources>
        <Style TargetType="{x:Type ToolTip}">
            <!-- As before, except Margin of the pointy part is now bound to calculated ToolTip.Tag -->
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type ToolTip}">
                        <Grid x:Name="Grid">
                            <Grid.RowDefinitions>
                                <RowDefinition Height="20" />
                                <RowDefinition Height="*" />
                            </Grid.RowDefinitions>
                            <Rectangle MinWidth="40" Fill="#fff" Stroke="#FF000000" RadiusX="4" RadiusY="4" Grid.Row="1" />
                            <Path Fill="#fff" Stretch="Fill" Stroke="#FF000000" HorizontalAlignment="Left" 
                                  Margin="{TemplateBinding Tag}" Width="20" Grid.Row="0"
                        Data="M 0,21 L 10,0 20,21" />
                            <ContentPresenter Margin="8" Grid.Row="1" />
                        </Grid>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
            <Setter Property="Placement" Value="Bottom" />
            <!-- Event to adjust horizontal position of the pointy part -->
            <EventSetter Event="Opened" Handler="ToolTipOpenedHandler" />
            <Style.Triggers>
                <Trigger Property="Placement" Value="Top">
                    <!-- When placement is Top, place the pointy part below the rectangle part -->
                    <Setter Property="Template">
                        <Setter.Value>
                            <ControlTemplate TargetType="{x:Type ToolTip}">
                                <Grid>
                                    <Grid.RowDefinitions>
                                        <RowDefinition Height="*" />
                                        <RowDefinition Height="20" />
                                    </Grid.RowDefinitions>
                                    <Rectangle MinWidth="40" Fill="#fff" Stroke="#FF000000" RadiusX="4" RadiusY="4" Grid.Row="0" />
                                    <Path Fill="#fff" Stretch="None" Stroke="#FF000000" HorizontalAlignment="Left" Width="20" Grid.Row="1" 
                                        Data="M 0,0 L 10,20 20,0" Margin="{TemplateBinding Tag}" />
                                    <ContentPresenter Margin="8" Grid.Row="0" />
                                </Grid>
                            </ControlTemplate>
                        </Setter.Value>
                    </Setter>
                </Trigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>
    <Grid>
        <TextBlock VerticalAlignment="Top" HorizontalAlignment="Left" Background="Aqua" ToolTipService.ToolTip="ToolTip for TopLeft - MMMMMMMMMWWWWWWWWWW">TopLeft</TextBlock>
        <TextBlock VerticalAlignment="Top" HorizontalAlignment="Right" Background="Aqua" ToolTipService.ToolTip="ToolTip for TopRight - MMMMMMMMMWWWWWWWWWW">TopRight</TextBlock>
        <TextBlock VerticalAlignment="Center" HorizontalAlignment="Left" Background="Aqua" ToolTipService.ToolTip="i">CenterLeft</TextBlock>
        <TextBlock VerticalAlignment="Center" HorizontalAlignment="Right" Background="Aqua" ToolTipService.ToolTip="i">CenterRight</TextBlock>
        <TextBlock VerticalAlignment="Bottom" HorizontalAlignment="Left" Background="Aqua" Text="BottomLeft">
            <TextBlock.ToolTip>
                <TextBlock>Multi-line ToolTip for Bottomleft - MMMMMMMMMWWWWWWWWWW<LineBreak/>x<LineBreak/>y<LineBreak/>z</TextBlock>
            </TextBlock.ToolTip>
        </TextBlock>
        <TextBlock VerticalAlignment="Bottom" HorizontalAlignment="Right" Background="Aqua" ToolTipService.ToolTip="ToolTip for BottomRight - MMMMMMMMMWWWWWWWWWW">BottomRight</TextBlock>
    </Grid>
</Window>

Code-behind

private void ToolTipOpenedHandler(object sender, RoutedEventArgs e)
{
    ToolTip toolTip = (ToolTip)sender;
    UIElement target = toolTip.PlacementTarget;
    Point adjust = target.TranslatePoint(new Point(8, 0), toolTip);
    if (adjust.Y > 0)
    {
        toolTip.Placement = PlacementMode.Top;
    }
    toolTip.Tag = new Thickness(adjust.X, -1.5, 0, -1.5);
}

Final result

Final result - Top left

Final fixed result - Bottom right

The pointy part now adjusts horizontally when tooltip is near the right of the screen, and vertically when tooltip is near the bottom of the screen.

Upvotes: 12

Related Questions