Meisam Seyed Aliroteh
Meisam Seyed Aliroteh

Reputation: 599

Getting PlacementTarget of a ToolTip in code behind

As this MSDN page suggests you should be able to use data binding to set a placement target on a tooltip.

In my UWP app I have the following XAML and code behind:

<Button x:Name="MyButton">
    <ToolTipService.ToolTip>
        <ToolTip PlacementTarget="{Binding ElementName=MyButton}"
                 Loaded="FunkyMethod">
            <Border Width="150"
                    Height="50"
                    CornerRadius="10"
                    Background="DeepSkyBlue">
                <TextBlock Text="This is a tooltip"
                           HorizontalAlignment="Center"
                           VerticalAlignment="Center"
                           Foreground="White"/>
            </Border>
        </ToolTip>
    </ToolTipService.ToolTip>

    <Button.Content>Button with ToolTip</Button.Content>
</Button>
private void FunkyMethod(object sender, RoutedEventArgs e)
{
    var tooltip = sender as ToolTip;
    var button = tooltip?.PlacementTarget as Button; // always returns NULL for the placement target

    if (tooltip != null && button != null)
    {
        // do something funcky here.
    }
}

But the placement target never gets set and I always get NULL. Does anyone know why?

For context, I am trying to write code that would compute the horizontal/vertical offset needed to place a tooltip to the right of its parent and vertically centered. Something like this:

enter image description here

But I haven't been able to find a way of achieving this (Placement=Right or Top didn't work). So What I'm trying to do is to do it in code behind at runtime, by a) set Placement=Mouse, b) figure out the parent control via PlacementTarget, c) find out the mouse position relative to the parent control, d) compute the horizontal/vertical offset and set it for the tooltip

Upvotes: 1

Views: 1272

Answers (2)

Meisam Seyed Aliroteh
Meisam Seyed Aliroteh

Reputation: 599

So I still haven't heard back from Microsoft on this. As Justin XL pointed out x:Bind would work cleanly in some scenarios. But I was able to find a workaround that works for my scenarios. Here are the snippets:

<Style x:Key="ToolTipStyle1"
        TargetType="ToolTip">
    <Setter Property="Placement"
            Value="Mouse" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="ToolTip">
                <Grid x:Name="LayoutRoot"
                      MaxWidth="300"
                      MinHeight="36"
                      MaxHeight="300">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto" />
                        <ColumnDefinition Width="*" />
                    </Grid.ColumnDefinitions>

                    <Polygon Fill="#FF1589EE"
                             Width="10"
                             Height="12"
                             VerticalAlignment="Center"
                             Points="0,6 10,0 10,12" />

                    <Border Grid.Column="1"
                            Background="#FF1589EE">
                        <TextBlock Margin="20,5,5,5"
                                   HorizontalAlignment="Left"
                                   VerticalAlignment="Center"
                                   Foreground="White"
                                   FontSize="16"
                                   TextWrapping="Wrap"
                                   TextTrimming="CharacterEllipsis"
                                   Text="{TemplateBinding Content}" />
                    </Border>

                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="OpenStates">
                            <VisualState x:Name="Closed">
                                <Storyboard>
                                    <FadeOutThemeAnimation TargetName="LayoutRoot" />
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Opened">
                                <Storyboard>
                                    <FadeInThemeAnimation TargetName="LayoutRoot" />
                                </Storyboard>
                            </VisualState>
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>
                </Grid>

            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

<Button Width="150"
        Height="100"
        PointerMoved="MyButton_OnPointerMoved"
        PointerExited="MyButton_OnPointerExited"
        Content="Parent Control">
    <ToolTipService.ToolTip>
        <ToolTip Loaded="MyToolTip_OnLoaded"
                    Style="{StaticResource ToolTipStyle1}"
                    Content="This is a tooltip" />
    </ToolTipService.ToolTip>
</Button>
public sealed partial class MainPage : Page
{
    public MainPage()
    {
        this.InitializeComponent();
    }

    private PointerPoint _devicePointer;
    private FrameworkElement _elementContainingDevicePointer;
    private void MyButton_OnPointerMoved(object sender, PointerRoutedEventArgs e)
    {
        _elementContainingDevicePointer = sender as FrameworkElement;
        _devicePointer = e.GetCurrentPoint(_elementContainingDevicePointer);
    }

    private void MyButton_OnPointerExited(object sender, PointerRoutedEventArgs e)
    {
        _devicePointer = null;
        _elementContainingDevicePointer = null;
    }

    private void MyToolTip_OnLoaded(object sender, RoutedEventArgs e)
    {
        var tooltip = sender as ToolTip;

        if (_devicePointer != null && _elementContainingDevicePointer != null && tooltip != null)
        {
            var x = _elementContainingDevicePointer.ActualWidth - Math.Max(0, _devicePointer.Position.X);
            var y = _elementContainingDevicePointer.ActualHeight / 2 - Math.Max(0, _devicePointer.Position.Y) - tooltip.ActualHeight / 2;

            tooltip.HorizontalOffset = x + 5;
            tooltip.VerticalOffset = y - 10;
        }
    }
}

Upvotes: 0

Justin XL
Justin XL

Reputation: 39006

Try changing the traditional binding to PlacementTarget="{x:Bind MyButton}". Note the default Mode of this is OneTime but I think it's OK for your case.

Upvotes: 2

Related Questions