c# uwp tooltip placement property not updating

2019-07-14 14:59发布

问题:

In C# UWP I am creating custom tooltip style.

I have changed the default style of tooltip as below.

    <Style TargetType="ToolTip">
        <Setter Property="Foreground" Value="White" />
        <Setter Property="Background" Value="{ThemeResource SystemControlBackgroundChromeMediumLowBrush}" />
        <Setter Property="BorderBrush" Value="{ThemeResource SystemControlForegroundChromeHighBrush}" />
        <Setter Property="BorderThickness" Value="{ThemeResource ToolTipBorderThemeThickness}" />
        <Setter Property="FontSize" Value="{ThemeResource ToolTipContentThemeFontSize}" />
        <Setter Property="Padding" Value="40,40,40,35"/>

        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="ToolTip">

                    <Grid Background="Transparent">

                        <Grid 
                            MinWidth="100"
                            MinHeight="90"
                            Height="{TemplateBinding Height}"
                            Width="{TemplateBinding Width}" 
                            Padding="15" 
                            Background="Transparent">

                         <local:ArrowDown  x:Name="arrowDown" TooltipPlacement="{TemplateBinding Placement}"/>

And my custom control ArrowDown is getting information of ToolTip placement, so I can show it depends if tooltip is under or above control.

In the ArrowDown control I have added a DependencyProperty as below:

    public PlacementMode TooltipPlacement
    {
        get { return (PlacementMode)GetValue(TooltipPlacementProperty); }
        set { SetValue(TooltipPlacementProperty, value); }
    }

    public static readonly DependencyProperty TooltipPlacementProperty =
        DependencyProperty.Register("TooltipPlacement", typeof(PlacementMode), typeof(ArrowDown), new PropertyMetadata(null, TooltipPlacementChangedCallback));

    private static void TooltipPlacementChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var self = (ArrowDown)d;
        self.CalculateArrowVisibility();
    }

    // Method to show or hide arrow
    private void CalculateArrowVisibility()
    {
    }

And the problem is that the CalculateArrowVisibility is fire only the first time when tooltip is shown, and it always returns Top for TooltipPlacement, no matter if tooltip is shown below or above control.

I need CalculateArrowVisibility to be fired whenever the tooltip is shown, and I need TooltipPlacement property to show if tooltip is Under or Above control.

Anyone have the idea about this?

回答1:

The fact is that you cannot use the ToolTipService attached properties (e.g. <Button ToolTipService.Placement="Bottom" ToolTipService.ToolTip="!!!" />) to define the tooltip and it placement. This way the Placement is not set on the actual ToolTip control itself, and that's why it will always return Top.

In order to have the ToolTip pass down its Placement value to your custom dependency property, you will have to attach it like the following -

<Button>
    <ToolTipService.ToolTip>
        <ToolTip Placement="Bottom" Content="Hahaha..." />
    </ToolTipService.ToolTip>
</Button>

Update

Turns out that even though the app Window pushes the tooltip above or below its parent, its Placement value is never changed, what's changed is its horizontal & vertical offsets.

So, in your case, if we could work out its exact vertical offset, we would be able to determine whether the tooltip is above or below (its parent).

Given we have a ToolTip Style in place, we can create an attached property of type ToolTip and attach it to the Grid that contains the ArrowDown control.

<Grid MinWidth="100"
      MinHeight="90"
      Height="{TemplateBinding Height}"
      Width="{TemplateBinding Width}"
      Padding="15"
      Background="Transparent"
      local:ToolTipHelper.ToolTip="{Binding RelativeSource={RelativeSource TemplatedParent}}">

Because the TemplatedParent of the Grid is the ToolTip, we can use RelativeSource binding to link the ToolTip on the screen with our attached property, as shown above.

Now, we have a reference to the actual ToolTip, let's find its offsets. After some digging, I've found that the offsets of the ToolTip are always 0, they are useless; however, the offsets of its parent - a Popup, sometimes gives me the correct values, but not always. This is because I was using the Opened event where those values weren't yet populated; as soon as I changed it to SizeChanged, they have been giving me the expected values.

public static class ToolTipHelper
{
    public static ToolTip GetToolTip(DependencyObject obj)
    {
        return (ToolTip)obj.GetValue(ToolTipProperty);
    }
    public static void SetToolTip(DependencyObject obj, ToolTip value)
    {
        obj.SetValue(ToolTipProperty, value);
    }
    public static readonly DependencyProperty ToolTipProperty =
        DependencyProperty.RegisterAttached("ToolTip", typeof(ToolTip), typeof(ToolTipHelper), 
        new PropertyMetadata(null, (s, e) =>
        {
            var panel = (Panel)s; // The Grid that contains the ArrowDown control.
            var toolTip = (ToolTip)e.NewValue;

            // We need to monitor SizeChanged instead of Opened 'cause the offsets
            // are yet to be properly set in the latter event.
            toolTip.SizeChanged += (sender, args) =>
            {
                var popup = (Popup)toolTip.Parent; // The Popup that contains the ToolTip.

                // Note we have to use the Popup's offset here as the ToolTip's are always 0.
                var arrowDown = (ArrowDown)panel.FindName("arrowDown");
                arrowDown.TooltipPlacement = popup.VerticalOffset > 0
                    ? PlacementMode.Bottom
                    : PlacementMode.Top;
            };
        }));
}

Now, with this approach, you should be able to use the ToolTipService attached properties too. So the following XAML would work.

<Button ToolTipService.ToolTip="!!!" Content="Hover Me" />

Hope this helps!