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!

Saturday, November 5, 2016

Focusing was unexpectedly hard!

Today i demonstrate how to set the focus on a textbox after selecting a tab item. I assumed that doing so would just require a call to textboxname.Focus. Of course if I had been right, i would have had to blog about the topic of my next blog post today....


I was working on a WPF application in visual basic containing a tab control with two TabItems. My goal was to set the keyboard focus on a specific textbox contained on the second tab item, as soon as it was clicked. After trying to call the Focus method from tabcontrol.SelectionChanged i did a little digging, and apparently the page was still too busy updating its layout when i called the focus method.

Explicitly calling UpdateLayout did not work. The way around that was to set the focus asynchronously, by using the windows dispatcher. Doing this creates a new thread , allowing the old threat to continue doing its job, while the new thread sets the focus.

The code i have used for this:

Application.Current.Dispatcher.BeginInvoke _
(System.Windows.Threading.DispatcherPriority.Background, _
New Action(Sub() txtWatermark.Focus()))

This code produced the desired result, by creating a new sub with one instruction, called a Lambda Expression. If you would like to have a bigger sub to do the work, the code becomes:


dim action as Action = sub() FocusOnTextBlock()

Private Sub NextTabItem
TabsApp.SelectedIndex += 1
Application.Current.Dispatcher.BeginInvoke
(System.Windows.Threading.DispatcherPriority.Background, action ))
End Sub

Private Sub FocusOnTextblock()
txtWatermark.Focus()
End Sub
On a side note: I just tested the methods shown in the linked article on Focus. It did not work for me. If I missed another way to set the focus, let me know! I hope that this helps someone out and happy coding!