Monday, November 28, 2016

Always show tooltips!

An interface with disabled controls, without any discernible reason why they are disabled, might be confusing to the applications user. Tooltips can prevent this, but adding a second tooltip for a disabled control will result in XAML bloat. In this blog post i describe an alternative to have two tooltips on one control, without the overhead.

If you want to add a tooltip to a control in XAML, and a second tooltip to show when that control is disabled, the simplest way is to add a style, as shown here:


 <Button x:Name="btnClearAll" DockPanel.Dock="Left" Content="Clear _All" 
 IsEnabled="{Binding ElementName=lbSelectedFiles, Path=HasItems}" Padding="10,0" TabIndex="2" VerticalAlignment="Bottom">  
      <Button.Style>  
           <Style TargetType="Button">  
                <Setter Property="ToolTip" Value="Remove all files from the list" />  
                <Style.Triggers>  
                     <Trigger Property="IsEnabled" Value="False">  
                          <Setter Property="ToolTip" Value="No files in the list"/>  
                          <Setter Property="ToolTipService.ShowOnDisabled" Value="True"/>  
                     </Trigger>  
                </Style.Triggers>  
           </Style>  
      </Button.Style>  
 </Button> 


While this might be just fine for a single tooltip, a window with a lot of controls would add up to a LOT of extra XAML. I decided to look something more compact. One possibility I considered was to make a new dependency property and manipulate the tooltip text that way. But after reading up on that i decided there were two reasons to not do so:
1- It would have gotten pretty complicated,
2- it would not have fitted in smoothly with the existing property.

What i came up with is more elegant and a lot simpler: I used a converter to assign the tooltip text. Unfortunately a converter does not have (that I know of) a convenient way to tell which control called it, so I included the name of the caller in the parameter:

  <Window.Resources>  
     <local:FetchToolTip x:Key="ToolTipProvider"/>  
 </Window.Resources>  
  <Button x:Name="btnClearSelected" Content ="Remove Selected"  
        ToolTip="{Binding RelativeSource={RelativeSource Self}, Path=IsEnabled,  
        Converter={StaticResource ToolTipProvider}, ConverterParameter=btnClearSelected}">  
 </Button>  


The class FetchToolTip gets the tooltip form the project resources, based on the name of the caller and the value of the boolean:

 Public Class FetchToolTip  
   Implements IValueConverter  
   Public Function Convert(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.Convert  
     Dim bEnabled As Boolean  
     Dim strName As String  
     Dim strToolTip As String  
     Try  
       bEnabled = CBool(value)  
       strName = CStr(parameter)  
       If (bEnabled) Then  
         strToolTip = My.Resources.ResourceManager.GetString(strName)  
       Else  
         strToolTip = My.Resources.ResourceManager.GetString(strName & "_Disabled")  
       End If  
       Return strToolTip  
     Catch ex As Exception  
       MsgBox("exception in ToolTipProvider")  
       Debug.Write(Utilities.GetExceptionInfo(ex))  
     End Try  
     Return "Tooltip not found."  
   End Function  
   Public Function ConvertBack(value As Object, targetType As Type, parameter As Object, culture As CultureInfo) As Object Implements IValueConverter.ConvertBack  
     Throw New NotImplementedException  
   End Function  
 End Class  

The last bit of the puzzle then is to add some strings to our project to be used for the tooltip:


And Voila! Two tooltips per control, with overhead included only once!
Hope this helps someone, and have fun coding!

No comments:

Post a Comment