如何使复合组件在设计器中可用?

本文关键字:何使 复合 组件 | 更新日期: 2023-09-27 17:49:45

我正在尝试编写自定义WinForms组件,我编写了几个简单的验证器组件,用于ErrorProvider的一个子类,该子类自动连接验证事件。多亏了IExtenderProvider,所有这些组件都可以添加到表单中,并仅使用设计器进行连接。

现在在尝试上升一个级别,我试图得到一个复合验证器与设计人员可用。我可以启动它并使用代码,但这真的很容易。我想让它只以设计师的方式工作。

我的困难在于暴露一个属性,该属性是具有相同形式的其他验证器的集合。所有的验证器都直接从Component继承,并实现一个IControlValidator接口。如果有帮助,我愿意改变这一点,让他们从ValidatorComponent基类继承。

我想到了几个解决方案,但要么我不喜欢它们,要么我不能让它们工作:

  1. 使验证器成为不可见的控件,并且有复合验证器包含它们,类似于Panel所做的;

    我不喜欢这个,因为它更像是一个hack,不得不在真正的控件之间来回切换感觉很不对;

  2. 使用集合编辑器,就像使用工具栏一样;

    我在网上找了几篇关于这个的文章,但是我不能让它工作。至少不用构建我自己的编辑器表单,这对于一个实验项目来说太麻烦了。

    我承认我没有花太多时间来尝试,因为我意识到使用标准的CollectionEditor会将我锁定在使用一组固定的验证器类型(它会,不是吗?)

    我还想到创建一个简单的ValidatorReference类,具有IControlValidator类型的单个属性,并将其用作简单集合编辑器的元素类型。然后,我将添加其中一个,并在其属性网格中将属性设置为现有的验证器组件。这个看起来很容易上手,但却失去了吸引力,因为它是如此明显的黑客。

还有人有其他的想法吗?是不是我遗漏了什么,而这其实很简单?

如何使复合组件在设计器中可用?

为什么不创建一个编辑器来做到这一点呢?你觉得这听起来有点小题大做,其实不然。

我将用一个示例来演示。

样本描述

在这个示例中,我将创建一个名为ButtonActivityControl的控件,它能够使用一个名为Buttons的属性以相同的形式对其他控件进行多个引用,该属性是一个类型为Button的数组(即Button[])。

该属性被标记为自定义编辑器,这使得在页面中引用控件变得容易。编辑器显示了一个由复选列表框组成的表单,该列表框用于选择位于同一表单中的多个控件。

创建示例的步骤

1)一个名为ReferencesCollectionEditorForm的表单

  • 放置一个checklistbox在里面,
  • 放置一个'Ok'按钮
  • 将以下代码放入
  • 表单类中

reference Code of collectioneditorform:

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

这不是生产代码,我尽量保持简短,这样它就足以说明这个想法。初始化和处理是在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;
    }
  }
}