1
votes

I want to use tooltips as they were intended. But when an error occurs, I want to change them to show the error message, then, when the error is fixed, change them back.

So I have created an attached property to hold the tooltip. I assign the tooltip to the attached property and then use a style to copy that to the tooltip property. If there is an error, the style sets the tooltip to the error message instead.

So the triggers to set the error message in the tooltip are:

<Trigger Property="Validation.HasError"
                Value="true">
<Setter Property="BorderBrush"
                Value="{DynamicResource controls-errorBorderBrush}" />
<Setter Property="ToolTip"
                Value="{Binding RelativeSource={x:Static RelativeSource.Self},
    Path=(Validation.Errors)[0].ErrorContent}" />
</Trigger>

That seems fairly easy (and works)

When the error is fixed, I set it back (this doesn't work):

<Trigger Property="Validation.HasError"
Value="false">
<Setter Property="ToolTip"
    Value="{Binding Path=(wpfMisc:myCtrl.tooltipValue)}" />
</Trigger>

And in the xaml file I have:

<TextBox Text="this is a textbox with a myMisc based tooltip"
 Name="txtTooltip2"
 wpfMisc:myCtrl.tooltipValue="Tooltip Test tooltip" />

So of course, the problem is most likely in my attached property as it appears that the information is not being saved correctly. Here is that code:

public static string GettooltipValue(DependencyObject obj)
{
string value = obj.GetValue(tooltipValueProperty).ToString() ;
value = value.trimNull() ; // extension method to insure at least an empty string
return value ;
}

public static void SettooltipValue(DependencyObject obj, string value)
{
obj.SetValue(tooltipValueProperty, value.trimNull() );
}

public static readonly DependencyProperty tooltipValueProperty =
DependencyProperty.RegisterAttached("tooltipValue",
typeof(string),
typeof(myCtrl),
new UIPropertyMetadata(string.Empty));

So my guess is that I need to use something different in the UIPropertyMetaData, but not sure what I would use. Or is my whole approach just wrong?

I want to have data specific tooltips for all the data fields.

I did have this working by moving the tooltip to the tag property during an error, but I didn't want to leave it working that way because I know I would end up having problems when some other code wanted to use the tag in some special way.

Also, I know that some of the code is verbose - just a side effect of debugging...

And another dependency property in myCtrl is working just fine, so I know the xmlns, etc. references are correct.

On further research, I found the following in the output window: System.Windows.Data Error: 17 : Cannot get 'tooltipValue' value (type 'String') from '' (type 'layoutSettingsViewModel'). BindingExpression:Path=(0); DataItem='layoutSettingsViewModel' (HashCode=46457861); target element is 'TextBox' (Name=''); target property is 'ToolTip' (type 'Object') InvalidCastException:'System.InvalidCastException: Unable to cast object of type 'client.Models.layoutSettings.layoutSettingsViewModel' to type 'System.Windows.DependencyObject'.

layoutSettingsViewModel is the xaml view. So I think that the view itself is somehow getting the value instead of the controls.... Not sure though - I am guessing one of you knows exactly what it means and why... I hate trying to get up to speed on a new language...

Anyway, any help and/or suggestions are appreciated.

2
are you getting any binding errors in output window when Validation.HasError set to false? and how and where are you setting wpfMisc:myCtrl.tooltipValue?Also can you share the control on which you are applying these triggers?Nitin
2 things i would start with is , first try returning just obj.GetValue no need for all the other code for now since wpf(C#) might be optimizing your attached property code and it's usually expected to be the default implementation that's probably not the case (but just to be sure) , secondly , add a call back for your dp and see if the value actually changes , when and to what you except , let me know what u come up with.eran otzap
nit, I had the code, but for some reason, it didn't show. It is now. And I have been testing with a simple textbox although I will want to use it with any data entry control.JustMeToo
eran - I think I found the problem, just not sure how to fix it. I have edited the message above to include the message.JustMeToo

2 Answers

0
votes

I created similar functionality, but for Button controls. I will provide you with my working code for this and you simply have to replace Button with whichever control(s) you want to use. I had to create one AttachedProperty for the disabled ToolTip message and another to 'remember' the original value:

private static readonly DependencyPropertyKey originalToolTipPropertyKey = DependencyProperty.RegisterAttachedReadOnly("OriginalToolTip", typeof(string), typeof(ButtonProperties), new FrameworkPropertyMetadata(default(string)));

/// <summary>
/// Contains the original Button.ToolTip value to display when the Button.IsEnabled property value is set to true.
/// </summary>
public static readonly DependencyProperty OriginalToolTipProperty = originalToolTipPropertyKey.DependencyProperty;

/// <summary>
/// Gets the value of the OriginalToolTip property.
/// </summary>
/// <param name="dependencyObject">The DependencyObject to return the OriginalToolTip property value from.</param>
/// <returns>The value of the OriginalToolTip property.</returns>
public static string GetOriginalToolTip(DependencyObject dependencyObject)
{
    return (string)dependencyObject.GetValue(OriginalToolTipProperty);
}

/// <summary>
/// Provides Button controls with an additional tool tip property that only displays when the Button.IsEnabled property value is set to false.
/// </summary>
public static DependencyProperty DisabledToolTipProperty = DependencyProperty.RegisterAttached("DisabledToolTip", typeof(string), typeof(ButtonProperties), new UIPropertyMetadata(string.Empty, OnDisabledToolTipChanged));

/// <summary>
/// Gets the value of the DisabledToolTip property.
/// </summary>
/// <param name="dependencyObject">The DependencyObject to return the DisabledToolTip property value from.</param>
/// <returns>The value of the DisabledToolTip property.</returns>
public static string GetDisabledToolTip(DependencyObject dependencyObject)
{
    return (string)dependencyObject.GetValue(DisabledToolTipProperty);
}

/// <summary>
/// Sets the value of the DisabledToolTip property.
/// </summary>
/// <param name="dependencyObject">The DependencyObject to set the DisabledToolTip property value of.</param>
/// <param name="value">The value to be assigned to the DisabledToolTip property.</param>
public static void SetDisabledToolTip(DependencyObject dependencyObject, string value)
{
    dependencyObject.SetValue(DisabledToolTipProperty, value);
}

/// <summary>
/// Adds ro removes event handlers to the Button control that updates the Button.ToolTip value to the DisabledToolTip property value when the Button.IsEnabled property value is set to false.
/// </summary>
/// <param name="dependencyObject">The Button object.</param>
/// <param name="e">The DependencyPropertyChangedEventArgs object containing event specific information.</param>
public static void OnDisabledToolTipChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
    Button button = dependencyObject as Button;
    if (button != null && e.OldValue != e.NewValue) button.IsEnabledChanged += Button_IsEnabledChanged;
    else if (e.OldValue != null && e.NewValue == null) button.IsEnabledChanged -= Button_IsEnabledChanged;
}

private static void Button_IsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
{
    Button button = sender as Button;
    if (GetOriginalToolTip(button) == null) button.SetValue(originalToolTipPropertyKey, button.ToolTip.ToString());
    button.ToolTip = (bool)e.NewValue ? GetOriginalToolTip(button) : GetDisabledToolTip(button);
}

It is used like this:

<Button ToolTip="Normal ToolTip text to display" 
    Attached:ButtonProperties.DisabledToolTip="Text to automatically display when
    Button is disabled">
0
votes

For anyone who cares, here is the basic logic - built on the code that Sheridan shared. I know this can be made more concise, etc. But this makes it easy for a newer WPF developer to get started see how things work.

Here is the xaml style - which can be used for any control that supports a tooltip and data:

<Style TargetType="TextBox">
<Style.Triggers>
    <Trigger Property="Validation.HasError"
                        Value="true">
        <!-- We have an error, set the ErrorToolTip attached property to
        the error.  When the error is no more, it is automatically set 
        back to the original value (blank) so no need for a 2nd trigger -->
        <Setter Property="wpfMisc:myCtrl.ErrorToolTip"
                        Value="{Binding RelativeSource={x:Static RelativeSource.Self},
      Path=(Validation.Errors)[0].ErrorContent}" />

    </Trigger>

</Style.Triggers>
</Style>

And here is the code that can be added to a class (using myCtrl here) that holds dependency properties/attributes:

/// <summary>
/// Holds the default Tooltip value.  OnMyToolTipChanged used to set ToolTip
/// </summary>
public static DependencyProperty MyToolTipProperty = DependencyProperty.RegisterAttached("MyToolTip", typeof(string), typeof(myCtrl), new UIPropertyMetadata(string.Empty, OnMyToolTipChanged));

/// <summary>
/// Gets the value of the MyToolTip property.
/// </summary>
/// <param name="dependencyObject">The DependencyObject to return the MyToolTip property value from.</param>
/// <returns>The value of the MyToolTip property.</returns>
public static string GetMyToolTip(DependencyObject dependencyObject)
{
    return (string)dependencyObject.GetValue(MyToolTipProperty);
}

/// <summary>
/// Sets the value of the MyToolTip property.
/// </summary>
/// <param name="dependencyObject">The DependencyObject to set the MyToolTip property value of</param>
/// <param name="value">The value to be assigned to the MyToolTip property.</param>
public static void SetMyToolTip(DependencyObject dependencyObject, string value)
{
    dependencyObject.SetValue(MyToolTipProperty, value);
}

/// <summary>
/// Initially blank, set by style when an error occures (or goes away).  Uses OnErrorToolTipChanged to update ToolTip.
/// </summary>
public static DependencyProperty ErrorToolTipProperty = DependencyProperty.RegisterAttached("ErrorToolTip", typeof(string), typeof(myCtrl), new UIPropertyMetadata(string.Empty, OnErrorToolTipChanged));

/// <summary>
/// Gets the value of the ErrorToolTip property.
/// </summary>
/// <param name="dependencyObject">The DependencyObject to return the ErrorToolTip property value from</param>
/// <returns>The value of the ErrorToolTip property.</returns>
public static string GetErrorToolTip(DependencyObject dependencyObject)
{
    return (string)dependencyObject.GetValue(ErrorToolTipProperty);
}

/// <summary>
/// Sets the value of the ErrorToolTip property.
/// </summary>
/// <param name="dependencyObject">The DependencyObject to set the  ErrorToolTip property value of</param>
/// <param name="value">The value to be assigned to the ErrorToolTip property.</param>
public static void SetErrorToolTip(DependencyObject dependencyObject, string value)
{
    dependencyObject.SetValue(ErrorToolTipProperty, value);
}

/// <summary>
/// If an Error Tooltip is supplied, sets the ToolTip to that value, otherwise, resets it back to MyToolTipProperty
/// </summary>
/// <param name="dependencyObject">The control with the tooltip</param>
/// <param name="e">The DependencyPropertyChangedEventArgs object containing event specific information.</param>
public static void OnErrorToolTipChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
    if (dependencyObject is TextBox)
    {
        var txtControl = dependencyObject as TextBox;
        if (e.NewValue == null || e.NewValue.ToString() == string.Empty)
        {
            // No tooltip, reset to the original value
            txtControl.ToolTip = (string)dependencyObject.GetValue(MyToolTipProperty);
        }
        else
        {
            // Use the error tooltip
            txtControl.ToolTip = e.NewValue;
        }
    }
}

/// <summary>
/// This should only be called when the value is first assigned to the control.
/// </summary>
/// <param name="dependencyObject">The Control</param>
/// <param name="e">The DependencyPropertyChangedEventArgs object containing event 
/// specific information.</param>
public static void OnMyToolTipChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
    // What type of control - I may be able to use a generic parent that supports Tooltips, but until I have time to figure that out, using this to generate a valid control.ToolTip reference.
    if (dependencyObject is TextBox)
    {
        var txtControl = dependencyObject as TextBox;
        if (e.OldValue != e.NewValue)
        {
            txtControl.ToolTip = e.NewValue;
        }
    }
    else if (dependencyObject is ComboBox)
    {
        // Add code here for ComboBox and other tooltip controls (if we can't use a parent/interface reference instead.)
    }
}

Of course, there is much more in the style, etc. but I left all that out so that you had just the code necessary to solve this problem.

Hope this helps someone...

(If you think you can do it better, go for it, the more ideas, the better)