我正在尝试编写自定义WinForms组件,我编写了几个简单的验证器组件,用于ErrorProvider
的子类,它自动挂钩验证事件。由于IExtenderProvider
,所有这些组件都可以添加到表单中并仅使用设计器进行连接。
现在尝试上升一个级别,我正在尝试使用复合验证器以便与设计人员一起使用。我可以解决它并使用代码,但这非常简单。我想让它以设计师的方式工作。
我的困难在于暴露属性,该属性是同一形式的其他验证器的集合。验证器都直接从Component继承,并实现IControlValidator
接口。我愿意改变它,让它们从ValidatorComponent
基类继承,如果有帮助的话。
我想到了几个解决方案,但要么我不喜欢它们,要么我不能让它们工作:
使验证器成为不可见的控件,并且复合验证器包含它们,类似于Panel
的作用;
这个我不喜欢,因为它更像是一个黑客,不得不把它们放在真正的控制之中,只是感觉不对;
使用集合编辑器,就像用于工具栏一样;
我环顾网络,发现了几篇关于此的文章,但我无法让它发挥作用。至少没有建立我自己的编辑表格,这对于实验项目来说太麻烦了。
我承认我没有花太多时间尝试这个,因为我意识到使用标准CollectionEditor
会让我失去使用一组固定的验证器类型(它会不会呢?)。 / p>
我还想过创建一个简单的ValidatorReference
类,其中包含IControlValidator
类型的单个属性,并将其用作简单集合编辑器的元素类型。然后我会添加其中一个,并在其属性网格中将属性设置为现有的验证器组件。这个似乎很容易上班,但失去吸引力,因为它是如此明显的黑客。
任何人都有其他想法吗?有什么我想念的东西,这其实很简单吗?
答案 0 :(得分:6)
为什么不创建编辑器来执行此操作? 你认为这听起来有点矫枉过正,但事实并非如此。
我将用样本进行演示。
在这个示例中,我将创建一个名为ButtonActivityControl
的控件,该控件能够使用名为Buttons
的属性对同一表单中的其他控件进行多次引用,该属性是Button类型(即Button[]
)。
该属性标有自定义编辑器,可以轻松引用页面中的控件。编辑器显示一个表单,其中包含一个选中的列表框,用于选择多个控件,这些控件的格式完全相同。
1)一个名为ReferencesCollectionEditorForm的表单
ReferencesCollectionEditorForm:
public partial class ReferencesCollectionEditorForm : Form
{
public ReferencesCollectionEditorForm(Control[] available, Control[] selected)
{
this.InitializeComponent();
List<Control> sel = new List<Control>(selected);
this.available = available;
if (available != null)
foreach (var eachControl in available)
this.checkedListBox1.Items.Add(new Item(eachControl),
selected != null && sel.Contains(eachControl));
}
class Item
{
public Item(Control ctl) { this.control = ctl; }
public Control control;
public override string ToString()
{
return this.control.GetType().Name + ": " + this.control.Name;
}
}
Control[] available;
public Control[] Selected
{
get
{
List<Control> selected = new List<Control>(this.available.Length);
foreach (Item eachItem in this.checkedListBox1.CheckedItems)
selected.Add(eachItem.control);
return selected.ToArray();
}
}
}
2)UITypeEditor
参考代码收集编辑:
public class ReferencesCollectionEditor : UITypeEditor
{
public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
{
List<Control> available = new List<Control>();
ButtonActivityControl control = context.Instance as ButtonActivityControl;
IDesignerHost host = provider.GetService(typeof(IDesignerHost)) as IDesignerHost;
IComponent componentHost = host.RootComponent;
if (componentHost is ContainerControl)
{
Queue<ContainerControl> containers = new Queue<ContainerControl>();
containers.Enqueue(componentHost as ContainerControl);
while (containers.Count > 0)
{
ContainerControl container = containers.Dequeue();
foreach (Control item in container.Controls)
{
if (item != null && context.PropertyDescriptor.PropertyType.GetElementType().IsAssignableFrom(item.GetType()))
available.Add(item);
if (item is ContainerControl)
containers.Enqueue(item as ContainerControl);
}
}
}
// collecting buttons in form
Control[] selected = (Control[])value;
// show editor form
ReferencesCollectionEditorForm form = new ReferencesCollectionEditorForm(available.ToArray(), selected);
form.ShowDialog();
// save new value
Array result = Array.CreateInstance(context.PropertyDescriptor.PropertyType.GetElementType(), form.Selected.Length);
for (int it = 0; it < result.Length; it++)
result.SetValue(form.Selected[it], it);
return result;
}
}
3)使用相同形式的其他控件的控件
自定义控制代码:
public class ButtonActivityControl : Control, ISupportInitialize
{
[Editor(typeof(ReferencesCollectionEditor), typeof(UITypeEditor))]
public Button[] Buttons { get; set; }
Dictionary<Button, bool> map = new Dictionary<Button, bool>();
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.FillRectangle(Brushes.White, e.ClipRectangle);
if (this.Site != null) return; // this code is needed otherwise designer crashes when closing
int h = e.ClipRectangle.Height / this.Buttons.Length;
int top = 0;
foreach (var button in this.Buttons)
{
e.Graphics.FillRectangle(map[button] ? Brushes.Black : Brushes.White, new Rectangle(0, top, e.ClipRectangle.Width, h));
top += h;
}
base.OnPaint(e);
}
void ISupportInitialize.BeginInit()
{
}
void ISupportInitialize.EndInit()
{
if (this.Site != null) return; // this is needed so that designer does not change the colors of the buttons in design-time
foreach (var button in this.Buttons)
{
button.Click += new EventHandler(button_Click);
button.ForeColor = Color.Blue;
map[button] = false;
}
}
void button_Click(object sender, EventArgs e)
{
map[(Button)sender] = !map[(Button)sender];
this.Invalidate();
}
}
现在创建一个包含自定义控件的表单,在其上放置一些按钮,然后在其上放置ButtonActivityControl。自定义控件有一个名为Buttons的属性,可以编辑。
就是这样!!
没有理由担心自定义编辑......而不是那么复杂...... 在半小时内完成。
我认为这就是答案......也就是说,我认为是这样! =)也许我没有理解这个问题......但那是最好的人可以做的:试图帮助别人!
修改强>
ReferencesCollectionEditor中需要此代码:
public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context)
{
return UITypeEditorEditStyle.Modal;
}
public override bool GetPaintValueSupported(ITypeDescriptorContext context)
{
return false;
}
答案 1 :(得分:1)
这不是生产代码,我试图保持简短,所以它足以说明这个想法。 初始化和处理在VS2010创建的.Designer文件中处理。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text.RegularExpressions;
using System.Windows.Forms;
namespace ValidationControls
{
[ProvideProperty("ErrorMessage", typeof (TextBoxBase))]
[ProvideProperty("RegEx", typeof (TextBoxBase))]
public partial class ValidationComponent : Component, IExtenderProvider
{
private readonly Dictionary<Control, string> _errorMessages =
new Dictionary<Control, string>();
private readonly Dictionary<Control, string> _regExDictionary =
new Dictionary<Control, string>();
private TextBoxBase _activeControl;
private ErrorProvider _errorProvider;
public ValidationComponent()
{
InitializeComponent();
}
public ValidationComponent(IContainer container)
{
container.Add(this);
InitializeComponent();
}
public ErrorProvider ErrorProvider
{
get { return _errorProvider; }
set { _errorProvider = value; }
}
#region IExtenderProvider Members
public bool CanExtend(object extendee)
{
return extendee is TextBoxBase;
}
#endregion
[DefaultValue("")]
[Category("Validation")]
public string GetRegEx(TextBoxBase control)
{
string value;
return _regExDictionary.TryGetValue(control, out value) ? value : string.Empty;
}
[Category("Validation")]
public void SetRegEx(TextBoxBase control, string value)
{
if (string.IsNullOrWhiteSpace(value))
{
_regExDictionary.Remove(control);
control.Validating -= OnControlValidating;
control.Validated -= OnControlValidated;
}
else
{
_regExDictionary[control] = value;
control.Validating += OnControlValidating;
control.Validated += OnControlValidated;
}
}
[Category("Validation")]
public string GetErrorMessage(TextBoxBase control)
{
string value;
return _errorMessages.TryGetValue(control, out value) ? value : string.Empty;
}
[Category("Validation")]
public void SetErrorMessage(TextBoxBase control, string value)
{
if (string.IsNullOrWhiteSpace(value))
{
_errorMessages.Remove(control);
}
else
{
_errorMessages[control] = value;
}
}
private void OnControlValidating(object sender, CancelEventArgs e)
{
_activeControl = (TextBoxBase) sender;
var regExPattern = GetRegEx(_activeControl);
if (Regex.IsMatch(_activeControl.Text, regExPattern, RegexOptions.Singleline))
return;
e.Cancel = true;
var errorMsg = GetErrorMessage(_activeControl);
if (_errorProvider != null)
_errorProvider.SetError(_activeControl, errorMsg);
}
private void OnControlValidated(object sender, EventArgs e)
{
if (sender != _activeControl)
return;
if (_errorProvider != null)
_errorProvider.SetError(_activeControl, "");
_activeControl = null;
}
}
}