如何在包含控件的窗体上获取工具提示?
How can I get the ToolTip on the Form that contains a control?
我有一个 class、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);
}
// [...]
}
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
一个 ToolTip is not a Control, it's a Component,因此您不会在表单的控件集合中找到它。
它是 System.ComponentModel.IContainer components
私有字段的一部分,通常在部分 class 的表单设计器部分中定义。
要查找控件的ToolTip,可以使用ToolTip.GetToolTip([Control])方法,然后验证返回的字符串是否为空。
如果您可以访问表单的 components
字段 - 即从包含要验证的控件的表单调用 ValidateText()
方法 - 您可以将组件容器传递给该方法:
► 如果表单没有组件,container
(在两种方法中)将是 null
。
► 如果表单没有工具提示组件,var toolTip
将是 null
。
→ 保持 object sender
,然后转换为 Control,因为在这里您只想访问属于公共控件 class 的公共属性,例如 Text
。您不需要知道 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;
}
请阅读 Jimi 的 以了解您的代码为何不起作用。
Plus 您的 Validator
class 可以重构为 validate TextBox 控件以不同的方式.单个控件、由窗体或其他容器托管的控件组以及 OpenForms.
的控件
还有 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,因此您可以验证派生自该基 class 的任何控件,例如; 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();
}
我有一个 class、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);
}
// [...]
}
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
一个 ToolTip is not a Control, it's a Component,因此您不会在表单的控件集合中找到它。
它是 System.ComponentModel.IContainer components
私有字段的一部分,通常在部分 class 的表单设计器部分中定义。
要查找控件的ToolTip,可以使用ToolTip.GetToolTip([Control])方法,然后验证返回的字符串是否为空。
如果您可以访问表单的 components
字段 - 即从包含要验证的控件的表单调用 ValidateText()
方法 - 您可以将组件容器传递给该方法:
► 如果表单没有组件,container
(在两种方法中)将是 null
。
► 如果表单没有工具提示组件,var toolTip
将是 null
。
→ 保持 object sender
,然后转换为 Control,因为在这里您只想访问属于公共控件 class 的公共属性,例如 Text
。您不需要知道 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;
}
请阅读 Jimi 的
Plus 您的 Validator
class 可以重构为 validate TextBox 控件以不同的方式.单个控件、由窗体或其他容器托管的控件组以及 OpenForms.
还有 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,因此您可以验证派生自该基 class 的任何控件,例如; 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();
}