【问题标题】:How can I get the ToolTip on the Form that contains a control?如何在包含控件的窗体上获取工具提示?
【发布时间】:2020-05-22 12:20:47
【问题描述】:

我有一个类 Validator,它验证项目所有表单中的字段。
我想(从函数中)引用包含正在验证的控件的表单上的工具提示。此控件是函数参数。

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);
     }
    // [...]
}

错误 CS0039 无法通过引用转换、装箱转换、拆箱转换、包装转换或空类型转换将类型“System.Windows.Forms.Control”转换为“System.Windows.Forms.ToolTip”

【问题讨论】:

  • 请详细说明。 1)调用此方法时是否打开了多个表单? 2)你在哪里调用这个方法?从表格?如果是,那么您可以将它的 ToolTip 作为第二个参数传递。喜欢:public static Boolean ValidateText(object sender, ToolTip ttHelp) { .. }.
  • 是的,当我调用此方法时,我打开了多个表单。我有一个类验证器,它验证所有表单中的字段。我的项目几乎完成了,如果我将第二个参数传递给函数,我必须在任何地方修改它的调用 - 有很多工作要做,但如果我可以从函数中引用工具提示就足以修改函数。非常感谢您的关注!

标签: c# winforms tooltip


【解决方案1】:

ToolTip 不是 Control,它是 Component,因此您不会在 Form 的 Controls 集合中找到它。
它是 System.ComponentModel.IContainer components 私有字段的一部分,通常在分部类的表单设计器部分中定义。

要查找某个控件的ToolTip,可以使用ToolTip.GetToolTip([Control])方法,然后验证返回的字符串是否为空。

如果您可以访问表单的 components 字段 - 即,从包含要验证的控件的表单调用 ValidateText() 方法 - 您可以将组件容器传递给该方法:

► 如果表单没有组件,container(在这两种方法中)将是null
► 如果表单没有工具提示组件,var toolTip 将是null

→ 保留object sender,然后转换为Control,因为在这里您只想访问公共属性,例如Text,它们属于公共Control 类。您不需要知道sender 的类型:Control 是所有控件的基本类型,并公开了所有公共属性。

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;
}

否则,您可以通过反射访问组件集合,以防传递给 ValidateText() 方法的 Control 实例可能具有未确定的来源。

使用[form].GetType().GetField("components").GetValue([form]) as IContainer,我们可以访问components 字段值,然后像以前一样继续。

→ 这里,sender 已经属于 Control 类型,因为这是 sender 的真正性质

ValidateText([Control]) 重载了之前的方法。当您为container 赋值后,您可以调用ValidateText(sender, 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;
}

【讨论】:

  • 非常感谢,但是 form.GetType().GetField("components", flags) 为空。我在表单中公开了 components 字段。传递第二个参数的工作量太大——修改项目中的所有函数调用。
  • 不要将components 字段设为公开。如果form.GetType().GetField().GetValue() 返回null,那么Form 没有组件(因此components 字段从未被赋值)或者您没有使用此处提供的代码。另外,第一种方法重载了第二种方法。您无需更改代码中的任何内容。您可以呼叫其中一个或让第二个 (ValidateText(Control sender)) 呼叫第一个。随你喜欢。
【解决方案2】:

请阅读 Jimi 的 answer 以了解您的代码为何不起作用。

另外您的Validator 类可以重构如下,以以不同的方式验证 TextBox 控件。单个控件、由 Form 或其他容器托管的一组控件,以及 OpenForms 的控件。

还建议将ErrorProvider 组件用于此类任务。 Validator 类同时提供 ToolTipErrorProvider 服务。请注意,目标控件的基类是TextBoxBase 类,因此您可以验证从该基类派生的任何控件,例如: TextBox、RichTextBox、ToolStripTextBox...等

请阅读每个成员的解释。

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>());
    }
}

现在你可以按如下方式使用它:

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();            
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-22
    • 1970-01-01
    • 2011-02-13
    • 2021-07-24
    • 1970-01-01
    • 2010-09-15
    相关资源
    最近更新 更多