Search code examples
c#wpfxamldynamictooltip

WPF/XAML - Use 'Style' to create tooltip with model element name


We have a very complex questionnaire in WPF with 100s of controls. Groups of controls are put together into UserControls to better organize sections. They are visible/collapsed based on a variety of factors. This questionnaire changes quarterly due to vendor changes. We bind a data model to the datacontext. The model property names match the database column name.

When the questionnaire goes to QA, they would like some easy way to relate a questionnaire control to a database column. As part of their due diligence, they verify that the value in the database matches what they entered in the GUI.

My thought, was to add a tooltip (or similar mechanism) that would display the binding property. We don't want the end user to see this, so we need to turn this dynamic tooltip on/off with a config setting.

I am trying to find an easy way to simply use reflection in a global <style>. It would build the tooltip with the binding property name on all controls (enabled/disabled by a config option). I thought maybe I could use a converter to do this, but I'm not able to figure out exactly how to make that work.

I want the tooltip to show 'VariableName1' or 'VariableName2' on controls (VariableName1/VariableName2 is the name of the database column).

<TextBox Text="{Binding Path=VariableName1, ValidatesOnDataErrors=True}"/>

or

<CheckBox Style="{DynamicResource Radio}" IsChecked="{Binding Path=VariableName2}"/>

I tried an approach like this, thinking that in the 'QATooltips' method, I could do reflection and build the tooltip. But the converter never gets called. So I have something wired up wrong.

<UserControl.Resources>
    <local:QATooltips x:Key="QATooltips" />
    <Style TargetType="{x:Type TextBlock}">
        <Setter Property="ToolTip">
            <Setter.Value>
                <ToolTip Content="{Binding Converter={StaticResource QATooltips}}" />
            </Setter.Value>
        </Setter>
    </Style>
</UserControl.Resources>

Solution

  • You can do this by calling GetBindingExpression on the element in question, passing in the dependency property you're interested in. The name will be in the ResolvedSourcePropertyName property of the resulting expression.

    The converter you're looking for is something like this:

    public class BindingConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return (value as FrameworkElement)?.GetBindingExpression(parameter as DependencyProperty)?.ResolvedSourcePropertyName;
        }
    
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return Binding.DoNothing;
        }
    }
    

    Note how it's expecting the DP that you're interested in to be passed in as the command parameter, that way you don't have to create a separate converter type for each of your different binding types.

    Back in your XAML you simply do this:

    xmlns:controls="clr-namespace:System.Windows.Controls;assembly=PresentationFramework"
    
    <Window.Resources>
    
        <local:BindingConverter x:Key="BindingConverter" />
    
        <Style TargetType="{x:Type TextBlock}">
            <Setter Property="ToolTip">
                <Setter.Value>
                    <ToolTip Content="{Binding Path=PlacementTarget, RelativeSource={RelativeSource Mode=Self}, Converter={StaticResource BindingConverter}, ConverterParameter={x:Static controls:TextBlock.TextProperty}}"/>
                </Setter.Value>
            </Setter>
        </Style>
    
        <Style TargetType="{x:Type RadioButton}">
            <Setter Property="ToolTip">
                <Setter.Value>
                    <ToolTip Content="{Binding Path=PlacementTarget, RelativeSource={RelativeSource Mode=Self}, Converter={StaticResource BindingConverter}, ConverterParameter={x:Static controls:RadioButton.IsCheckedProperty}}"/>
                </Setter.Value>
            </Setter>
        </Style>
    
    </Window.Resources>
    
    <StackPanel Orientation="Vertical">
        <TextBlock Text="{Binding MyTextField}" />
        <RadioButton IsChecked="{Binding MyBooleanField}" Content="Click Me" />
    </StackPanel>
    

    (BTW, the reason your original code didn't work is because the ToolTips aren't child elements of their "parent" control's visual tree, that's why you have to bind via PlacementTarget instead).