C# Question

Tooltip Visibility Toggle

I have a

TextBox
that displays the result of a poll option as a percentage. Within said
TextBox
, I define a
ToolTip
that displays the result of a poll option by the number of votes said option obtained.

Poll with percentage and number of votes

The typical use case of a
Tooltip
is hover over the
TextBox
to display the tooltip and mouse away to no longer display it. I am working on a touchscreen, so the way I "hover over" to display the tooltip is to touch the
TextBox
(which is the percentage).

One requirement I have is to no longer display the tooltip (if currently displayed) by touching the percentage a second time (ie, no longer display the tooltip the same way I displayed it, by touching the percentage). Is there a way to toggle tooltip visibility like this?




Edit: Code

.xaml:

<TextBlock Text="{Binding Item.Percentage, StringFormat='\{0\}%'}"
FontFamily="{Binding Item.PollStyle.PercentageFont}"
Foreground="{Binding Item.PollStyle.PercentageColor}"
FontSize="{Binding Item.PollStyle.PercentageFontSize}"
Visibility="{Binding Item.PollStyle.PercentageVisibility}"
HorizontalAlignment="Center"
PreviewMouseLeftButtonDown="ToolTip_OnMouseLeftButtonDown"
PreviewTouchDown="ToolTip_OnTouchDown">
<TextBlock.ToolTip>
<ToolTip>
<ToolTip.Effect>
<DropShadowEffect ShadowDepth="2" RenderingBias="Performance" Direction="315" />
</ToolTip.Effect>
<ToolTip.Placement>Left</ToolTip.Placement>
<ToolTip.VerticalAlignment>Center</ToolTip.VerticalAlignment>
<TextBlock Text="{Binding Item.Votes, StringFormat='\{0\} votes'}"
FontFamily="{Binding Item.PollStyle.ResultsFont}"
Foreground="{Binding Item.PollStyle.ResultsColor}"
FontSize="{Binding Item.PollStyle.ResultsFontSize}"
Visibility="{Binding Item.PollStyle.ResultsVisibility}"
HorizontalAlignment="Center" />
</ToolTip>
</TextBlock.ToolTip>
</TextBlock>


.xaml.cs

private void ToolTip_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
ToggleToolTip(sender, e);
}

private void ToolTip_OnTouchDown(object sender, TouchEventArgs e)
{
// denote touch has been handled so it isn't promoted into a mouse event
e.IsHandled = true;
ToggleToolTip(sender, e);
}

private void ToggleToolTip(object sender, EventArgs e)
{
var source = (sender as TextBlock);
var tooltip = source.ToolTip as ToolTip;
tooltip.Visibility = tooltip.Visibility == Visibility.Hidden ? Visibility.Visible : Visibility.Hidden;
}

Answer

I took a different approach that fits my requirements much better. Instead of using a ToolTip, I'm using a Popup.

Popup Toggle

PS: IsManipulationEnabled must be set to True to handle touch events(1).

A few notes:

  1. To get the parent for the PlacementTarget (in my case, the Popup's parent is the TextBlock), use the following code: PlacementTarget="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=TextBlock}}"
  2. By default, a Popup's background is black. To add transparency, set AllowTransparency to true, then create a Grid within the popup and set the Background of said grid to Transparent.
  3. To animate a Popup similar to a Tooltip, use PopupAnimation. Note this can only be used when AllowTransparency is set to true(2).
  4. This feels like a hack, but in the code behind, I do the following (below) to find the child of the TextBlock (which is the Popup), including using the visual tree. I am open to a better way to do this (without naming the object as I have multiple TextBlocks with Popups to address).

Overall, feel this is a better way than trying to toggle the Tooltip's visibility.

Finding the child of the TextBlock:

var source = sender as TextBlock;
var popup = source.FindChildren<Popup>().First();
if (popup != null) popup.IsOpen = !popup.IsOpen;

Code sample:

.xaml

<TextBlock Text="{Binding Item.Percentage, StringFormat='\{0\}%'}"
                  FontFamily="{Binding Item.PollStyle.PercentageFont}"
                  Foreground="{Binding Item.PollStyle.PercentageColor}"
                  FontSize="{Binding Item.PollStyle.PercentageFontSize}"
                  Visibility="{Binding Item.PollStyle.PercentageVisibility}"
                  HorizontalAlignment="Center" 
                  PreviewMouseLeftButtonDown="Popup_OnPreviewMouseLeftButtonDown"
                  PreviewTouchDown="Popup_OnPreviewTouchDown"
                  IsManipulationEnabled="True">
                  <Popup Placement="Left" PlacementTarget="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=TextBlock}}"
                         AllowsTransparency="True" PopupAnimation="Fade">
                     <Grid Background="Transparent">
                        <TextBlock Text="{Binding Item.Votes, StringFormat='\{0\} votes'}"
                                   FontFamily="{Binding Item.PollStyle.ResultsFont}"
                                   Foreground="{Binding Item.PollStyle.ResultsColor}"
                                   FontSize="{Binding Item.PollStyle.ResultsFontSize}"
                                   Visibility="{Binding Item.PollStyle.ResultsVisibility}"
                                   HorizontalAlignment="Center">
                           <TextBlock.Effect>
                              <DropShadowEffect ShadowDepth="2" RenderingBias="Performance"
                                                                      Direction="315" />
                           </TextBlock.Effect>
                        </TextBlock>
                     </Grid>
                  </Popup>
</TextBlock>

.xaml.cs

private void Popup_OnPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    TogglePopup(sender, e);
}

private void Popup_OnPreviewTouchDown(object sender, TouchEventArgs e)
{
    // denote touch has been handled so it isn't promoted into a mouse event
    e.IsHandled = true;
    TogglePopup(sender, e);
}

private void TogglePopup(object sender, EventArgs e)
{
    var source = sender as TextBlock;
    var popup = source.FindChildren<Popup>().First();
    if (popup != null) popup.IsOpen = !popup.IsOpen;
}