简体   繁体   中英

How can I get the ToolTip on the Form that contains a control?

I have a class, Validator , which validates fields in all the Forms of a project.
I want to refer (from the function) the ToolTip on the form that contains the control which is being validated. This control is the function argument.

public static Boolean ValidateText(object sender)
{
     //Error CS0039
     ToolTip ttHelp = (sender as TextBox).FindForm().Controls["myToolTip"] as ToolTip;

     if((sender as TextBox).Text == "") {
         ttHelp.SetToolTIp(someControl,someMessage);
     }
    // [...]
}

Error CS0039 Cannot convert type 'System.Windows.Forms.Control' to 'System.Windows.Forms.ToolTip' via a reference conversion, boxing conversion, unboxing conversion, wrapping conversion, or null type conversion

A ToolTip is not a Control, it's a Component , so you won't find it in a Form's Controls collection.
It's instead part of the System.ComponentModel.IContainer components private Field, usually defined in the Form's Designer section of the partial class.

To find the ToolTip of a control, you can use the ToolTip.GetToolTip([Control]) method, then verify whether the string returned is empty.

If you can access the Form's components Field - ie, the ValidateText() method is called from the Form that contains the Control to validate - you can pass the components container to the method:

► If the Form has no Components, container (in both methods) will be null .
► If the Form has no ToolTip components, var toolTip will be null .

→ Keeping object sender , then casting to Control, since here you want to access only common Properties, like Text , which belong to the common Control class. You don't need to know sender 's type: Control is the base type of all controls and exposes all the common properties.

bool result = ValidateText(this.someControl, this.components);
// [...]

public static bool ValidateText(object sender, IContainer container)
{
    if (container == null) {
        /* no components, decide what to do */
        // [...] 
    }

    var ctrl = sender as Control;
    var toolTip = container.Components.OfType<ToolTip>()
                 .FirstOrDefault(tt => !string.IsNullOrEmpty(tt.GetToolTip(ctrl)));

    if (toolTip != null) {
        string tooltipText = toolTip.GetToolTip(ctrl);
        // [...]
        if (ctrl.Text == "") { }
        return true;
    }
    return false;
}

Otherwise, you can access the components collection via reflection, in case the Control instance passed to the ValidateText() method may have an undetermined origin.

With [form].GetType().GetField("components").GetValue([form]) as IContainer we can access the components Field value then continue as before.

→ Here, sender is of type Control already, since this is the real nature of sender .

ValidateText([Control]) overloads the previous method. You can call ValidateText(sender, container) when you have assigned a value to container .

bool result = ValidateText(someControl); 
// [...]


using System.Reflection;

public static bool ValidateText(Control sender)
{
    var flags = BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetField;
    Form form = sender.FindForm();

    var container = form.GetType().GetField("components", flags).GetValue(form) as IContainer;
    if (container == null) {
        /* no components, decide what to do */
        // [...] 
    }

    // You can call ValidateText(sender, container) or continue
    var toolTip = container.Components.OfType<ToolTip>()
                 .FirstOrDefault(tt => !string.IsNullOrEmpty(tt.GetToolTip(sender)));

    if (toolTip != null) {
        string tooltipText = toolTip.GetToolTip(sender);
        // [...]
        return true;
    }
    return false;
}

Please read Jimi's answer to understand why your code doesn't work.

Plus your Validator class can be refactored as follows to validate the TextBox controls in different ways. A single control, group of controls hosted by a Form or other container, and the controls of the OpenForms .

Also The ErrorProvider component is recommended for tasks as such. The Validator class provides both, the ToolTip and ErrorProvider services. Note that, the base class of the target controls is the TextBoxBase class, so you can validate any control derives from this base class such as; TextBox, RichTextBox, ToolStripTextBox...etc.

Please read the explanatory comments of each member.

internal sealed class Validator 
{
    private static Validator Current;
    //Each Form has its own ToolTip...
    private readonly Dictionary<Form, ToolTip> ToolTips;
    private readonly ErrorProvider ErrPro;

    private Validator()
    {
        ToolTips = new Dictionary<Form, ToolTip>();
        ErrPro = new ErrorProvider();
    }

    static Validator() => Current = new Validator();

    /// <summary>
    /// Enable/disable the ToolTip service.
    /// </summary>
    public static bool ToolTipEnabled { get; set; } = true;

    /// <summary>
    /// Enable/disable the ErrorProvider service.
    /// </summary>
    public static bool ErrorProviderEnabled { get; set; } = false;

    /// <summary>
    /// Set/get the ToolTip/ErrorProvider message.
    /// </summary>
    public static string Message { get; set; } = "Hello World";

    /// <summary>
    /// Validate a single TextBox or RichTextBox control.
    /// </summary>
    /// <param name="txt">TextBox/RichTextBox..etc.</param>
    public static void Validate(TextBoxBase txt)
    {
        if (txt is null) return;
        var f = txt.FindForm();
        if (f is null) return;

        //Add a Form into the Dictionary and create a new ToolTip for it.
        if (!Current.ToolTips.ContainsKey(f))
        {
            Current.ToolTips.Add(f, new ToolTip());
            Current.ToolTips[f].ShowAlways = true; //Optional...

            //Cleanup. Remove the closed Forms and dispose the related disposables.
            f.HandleDestroyed += (s, e) =>
            {
                Current.ToolTips[f].Dispose();
                Current.ToolTips.Remove(f);
                if (Current.ToolTips.Count() == 0) Current.ErrPro.Dispose();
            };
        }

        if (txt.Text.Trim().Length == 0)
        {
            if (ToolTipEnabled)
                Current.ToolTips[f].SetToolTip(txt, Message);

            if (ErroProviderEnabled)
                Current.ErrPro.SetError(txt, Message);
        }
        else
        {
            Current.ToolTips[f].SetToolTip(txt, null);
            Current.ErrPro.SetError(txt, null);
        }
    }

    /// <summary>
    /// An overload that takes a container.
    /// </summary>
    /// <param name="container">Form/Panel/GroupBox...etc</param>
    public static void Validate(Control container)
    {
        if (container is null) return;

        foreach (var c in GetAllControls<TextBoxBase>(container))
            Validate(c);
    }

    /// <summary>
    /// Validates the open Forms.
    /// </summary>
    public static void ValidateOpenForms()
    {
        foreach (var f in Application.OpenForms.Cast<Form>())
            Validate(f);
    }

    /// <summary>
    /// Clear and remove the messages explicitly.
    /// </summary>
    public static void Clear()
    {
        Current.ToolTips.Values.ToList().ForEach(x => x.RemoveAll());
        Current.ErrPro.Clear();
    }

    /// <summary>
    /// A recursive function to get the controls from a given container.
    /// </summary>
    private static IEnumerable<T> GetAllControls<T>(Control container)
    {
        var controls = container.Controls.Cast<Control>();
        return controls.SelectMany(ctrl => GetAllControls<T>(ctrl)).Concat(controls.OfType<T>());
    }
}

Now you can use it as follows:

void TheCaller()
{
    //Set the desired properties.
    Validator.Message = "Hello World!";
    Validator.ErrorProviderEnabled = true;

    //Validate a single control.
    Validator.Validate(textBox1);

    //Or the controls of the current Form.
    Validator.Validate(this);

    //Or all the open Forms.
    Validator.ValidateOpenForms();            
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM