Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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>
like image 668
sohum Avatar asked Mar 12 '12 20:03

sohum


1 Answers

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.

like image 102
Edward Avatar answered Nov 08 '22 13:11

Edward