Home > Mobile >  General style with specific properties
General style with specific properties

Time:04-04

I would like to have a dynamic ToolTip style that changes the foreground color based on the text of different string properties (in this code example, the string is PosError, but it could be any property name, and if the value is "Error", the DataTrigger is triggered.

As for now, I have to do it like this for each and every TextBox: Change the property binding of the DataTrigger and the text of the ToolTip (in this example, the string property is PosExtreme):

<TextBox 
    Width="150" 
    FontSize="25"
    MaxWidth="150"
    Name="posTab"
    TextAlignment="Center"
    Text="{Binding PosTabStrength, UpdateSourceTrigger=PropertyChanged}">
    <TextBox.ToolTip>
        <ToolTip>
            <ToolTip.Style>
                <Style TargetType="{x:Type ToolTip}">
                    <Style.Triggers>
                         <DataTrigger Binding="{Binding PosError}" Value="Error">
                             <DataTrigger.Setters>
                                 <Setter Property="Foreground">
                                     <Setter.Value>
                                         <LinearGradientBrush StartPoint="0 0" EndPoint="0 1">
                                             <GradientStop Color="#F08080" Offset="0"/>
                                             <GradientStop Color="#F04080" Offset="1"/>
                                         </LinearGradientBrush>
                                      </Setter.Value>
                                  </Setter>
                              </DataTrigger.Setters>
                          </DataTrigger>
                      </Style.Triggers>
                          <Setter Property="FontSize" Value="16"/>
                          <Setter Property="HasDropShadow" Value="True" />
                          <Setter Property="OverridesDefaultStyle" Value="true" />
                          <Setter Property="Foreground">
                              <Setter.Value>
                                  <LinearGradientBrush StartPoint="0 0" EndPoint="0 1">
                                      <GradientStop Color="#10F080" Offset="0"/>
                                      <GradientStop Color="#10B080" Offset="1"/>
                                  </LinearGradientBrush>
                              </Setter.Value>
                          </Setter>
                          <Setter Property="Template">
                              <Setter.Value>
                                  <ControlTemplate TargetType="ToolTip">
                                      <Border 
                                          CornerRadius="3"
                                          BorderThickness="1"
                                          Background="#CF001233"
                                          SnapsToDevicePixels="True"
                                          Width="{TemplateBinding Width}"
                                          Height="{TemplateBinding Height}">
                                          <Border.Style>
                                              <Style TargetType="{x:Type Border}">
                                                  <Setter Property="BorderBrush">
                                                      <Setter.Value>
                                                          <LinearGradientBrush StartPoint="0 0" EndPoint="0 1">
                                                              <GradientStop Color="#A0A0F0" Offset="0"/>
                                                              <GradientStop Color="#8080F0" Offset="1"/>
                                                              <LinearGradientBrush.RelativeTransform>
                                                                  <RotateTransform CenterX="0.5" CenterY="0.5"/>
                                                              </LinearGradientBrush.RelativeTransform>
                                                          </LinearGradientBrush>
                                                      </Setter.Value>
                                                  </Setter>
                                              <Style.Triggers>
                                                  <EventTrigger RoutedEvent="Border.Loaded">
                                                      <BeginStoryboard>
                                                          <Storyboard RepeatBehavior="Forever">
                                                              <DoubleAnimation 
                                                                  From="359"
                                                                  To="0"
                                                                  Duration="00:00:2" 
                                                                  Storyboard.TargetProperty="(Border.BorderBrush).(Brush.RelativeTransform).(RotateTransform.Angle)"/>
                                                          </Storyboard>
                                                      </BeginStoryboard>
                                                  </EventTrigger>
                                              </Style.Triggers>
                                          </Style>
                                      </Border.Style>
                                  <ContentPresenter Margin="5" HorizontalAlignment="Center" VerticalAlignment="Center"/>
                              </Border>
                          </ControlTemplate>
                      </Setter.Value>
                  </Setter>
              </Style>
            </ToolTip.Style>
            <TextBlock Text="{Binding PosExtreme}"/>
        </ToolTip>
    </TextBox.ToolTip>
</TextBox>

I would like to have the ToolTip style as ResourceDictionary file and not having to copy and paste the code and modify it for every TextBox with it's proper ToolTip style and ToolTip text. Is this possible?

CodePudding user response:

Your main blocker for reusing the style are the bindings to concrete properties and the hard-coded Error string. You need to pass them from outside of the style and control template.

Your comparison of the DataTrigger can be encapsulated in a value converter that returns a boolean for the comparison of any bound property (PosError) with a fixed value (Error) passed as parameter.

public class ErrorComparisonToBoolConverter : IValueConverter
{
   public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
   {
      return value != null && parameter != null && value.Equals(parameter);
   }

   public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
   {
      throw new InvalidOperationException();
   }
}

In the DataTrigger, we do not bind a concrete property anymore, but the Tag of the ToolTip, which is a general purpose property.

Gets or sets an arbitrary object value that can be used to store custom information about this element.

We assume that the comparison is done for us already and the Tag only contains the result true or false. This simplifies the DataTrigger and the dependency on PosError is gone.

Next, you can eliminate the TextBlock inside the ToolTip. ToolTip is a ContentControl and exposes a Content property. This content is shown automatically using the ContentPresenter inside the ControlTemplate. The dependency on PosExtreme is gone, too.

Move the style out in a resource dictionary in scope and create an instance of the converter, too.

<local:ErrorComparisonToBoolConverter x:Key="ErrorComparisonToBoolConverter"/>
<Style x:Key="MyToolTipStyle" TargetType="{x:Type ToolTip}">
   <Style.Triggers>
      <DataTrigger Binding="{Binding Tag, RelativeSource={RelativeSource Self}}" Value="True">
          <DataTrigger.Setters>
              <Setter Property="Foreground">
                  <Setter.Value>
                      <LinearGradientBrush StartPoint="0 0" EndPoint="0 1">
                          <GradientStop Color="#F08080" Offset="0"/>
                          <GradientStop Color="#F04080" Offset="1"/>
                      </LinearGradientBrush>
                   </Setter.Value>
               </Setter>
           </DataTrigger.Setters>
       </DataTrigger>
   </Style.Triggers>
    <Setter Property="FontSize" Value="16"/>
    <Setter Property="HasDropShadow" Value="True" />
    <Setter Property="OverridesDefaultStyle" Value="true" />
    <Setter Property="Foreground">
        <Setter.Value>
            <LinearGradientBrush StartPoint="0 0" EndPoint="0 1">
                <GradientStop Color="#10F080" Offset="0"/>
                <GradientStop Color="#10B080" Offset="1"/>
            </LinearGradientBrush>
        </Setter.Value>
    </Setter>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="ToolTip">
                <Border 
                    CornerRadius="3"
                    BorderThickness="1"
                    Background="#CF001233"
                    SnapsToDevicePixels="True"
                    Width="{TemplateBinding Width}"
                    Height="{TemplateBinding Height}">
                    <Border.Style>
                        <Style TargetType="{x:Type Border}">
                            <Setter Property="BorderBrush">
                                <Setter.Value>
                                    <LinearGradientBrush StartPoint="0 0" EndPoint="0 1">
                                        <GradientStop Color="#A0A0F0" Offset="0"/>
                                        <GradientStop Color="#8080F0" Offset="1"/>
                                        <LinearGradientBrush.RelativeTransform>
                                            <RotateTransform CenterX="0.5" CenterY="0.5"/>
                                        </LinearGradientBrush.RelativeTransform>
                                    </LinearGradientBrush>
                                </Setter.Value>
                            </Setter>
                            <Style.Triggers>
                                <EventTrigger RoutedEvent="Border.Loaded">
                                    <BeginStoryboard>
                                        <Storyboard RepeatBehavior="Forever">
                                            <DoubleAnimation 
                                                From="359"
                                                To="0"
                                                Duration="00:00:2" 
                                                Storyboard.TargetProperty="(Border.BorderBrush).(Brush.RelativeTransform).(RotateTransform.Angle)"/>
                                        </Storyboard>
                                    </BeginStoryboard>
                                </EventTrigger>
                            </Style.Triggers>
                        </Style>
                    </Border.Style>
                    <ContentPresenter Margin="5" HorizontalAlignment="Center" VerticalAlignment="Center"/>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

You can reuse the style now and bind PosExtreme or any other property to the Content of ToolTip and the error property to Tag. The Tag binding uses the value converter to compare the bound value to the expected error code passed as CommandParameter.

<TextBox.ToolTip>
    <ToolTip Style="{StaticResource MyToolTipStyle}"
             Content="{Binding PosExtreme}"
             Tag="{Binding PosError, ConverterParameter=Error, Converter={StaticResource ErrorComparisonToBoolConverter}}">
    </ToolTip>
</TextBox.ToolTip>

Remarks

  • The solution uses the Tag property to propagate the error property. In your example it works, but if you need to bind even more properties in the style or control template, you would have to create attached properties for each that you can bind instead of Tag. For a single property, Tag is just convenient.
  • You could also create a custom ToolTip control that exposes dependency properties for the properties used in the style and control template instead of attached properties and do the comparison within.
  • Depending on how your data items are implemented, you could eliminate the converter completely, by exposing a bool property, if that makes sense.
  • Related