Как сделать составной компонент пригодным для использования с дизайнером? - PullRequest
14 голосов
/ 13 апреля 2011

Я экспериментирую с написанием пользовательских компонентов WinForms и написал пару простых компонентов валидатора для использования с подклассом ErrorProvider, который автоматически подключает события проверки. Все эти компоненты могут быть добавлены в форму и подключены с использованием только дизайнера, благодаря IExtenderProvider.

Теперь, пытаясь подняться на один уровень выше, я пытаюсь получить составной валидатор для использования с дизайнером. Я могу получить его и работать с кодом, но это действительно легко. Я бы хотел, чтобы он работал только для дизайнеров.

Моя трудность заключается в том, чтобы выставить свойство, являющееся набором других валидаторов в той же форме. Все валидаторы наследуются непосредственно от Компонента и реализуют интерфейс IControlValidator. Я открыт, чтобы изменить это, чтобы они наследовали от ValidatorComponent базового класса, если это поможет.

Я подумал о паре решений, но либо они мне не нравятся, либо я не могу заставить их работать:

  1. Превратите валидаторы в невидимые элементы управления, а составные валидаторы содержат их, аналогично тому, что делает Panel;

    Этот мне не нравится, потому что это скорее хак, и необходимость совмещать их с истинным контролем просто кажется неправильной;

  2. Используйте редактор коллекций, как вы используете для панелей инструментов;

    Я посмотрел в Интернете и нашел пару статей об этом, но не смог заставить его работать. По крайней мере, без создания моей собственной формы редактора, что было бы слишком хлопотно для экспериментального проекта.

    Признаюсь, я не тратил много времени, пытаясь это сделать, потому что я понял, что использование стандарта CollectionEditor ограничит меня в использовании фиксированного набора типов валидаторов (не так ли?)

    Я также подумал о создании простого класса ValidatorReference с одним свойством типа IControlValidator и использовании его в качестве типа элемента для простого редактора коллекций. Затем я бы добавил один из них и в сетке его свойств установил свойство для существующего компонента валидатора. Кажется, что с этим легко начать работать, но он теряет свою привлекательность, потому что это такой очевидный взлом.

У кого-нибудь есть еще идеи? Я что-то упускаю, а на самом деле это просто?

Ответы [ 2 ]

7 голосов
/ 20 апреля 2011

Почему бы не создать редактор для этого ???Вы думаете, что это звучит излишне, но на самом деле это не так.

Я продемонстрирую на примере.

Описание образца

В этом примере я создам элемент управления с именемButtonActivityControl, который может сделать несколько ссылок на другие элементы управления в одной форме, используя свойство Buttons, которое является массивом типа Button (т.е. Button[]).

Свойство помеченос помощью специального редактора, который позволяет легко ссылаться на элементы управления на странице.В редакторе отображается форма, состоящая из флажка со списком, который используется для выбора нескольких элементов управления в одной и той же форме.

Шаги для создания образца

1) Форма с именемReferencesCollectionEditorForm

  • поместите внутри него CheckedListBox,
  • поместите кнопку 'ОК'
  • поместите следующий код в класс формы

Код ссылки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();
        }
    }
}

2) UITypeEditor

Код ссылкиCollectionEditor:

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 голос
/ 29 апреля 2011

Это не рабочий код, я постарался сделать его кратким, поэтому достаточно проиллюстрировать идею. Инициализация и удаление обрабатываются в файле .Designer, созданном VS2010.

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