UserControl как интерфейс, но видимый в дизайнере - PullRequest
7 голосов
/ 24 апреля 2009

Итак, у нас есть проект C # WinForms с формой, содержащей базилион UserControl с. Каждый UserControl, естественно, предоставляет все методы, свойства и т. Д. UserControl в дополнение к своим собственным отдельным элементам.

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

public class MyGiantForm
{
    ICustomerName cName;

    public MyForm()
    {
        InitializeComponent();

        var uc = new SomeCustomerNameUserControl();
        this.Controls.Add(uc);
        cName = uc;
    }
}

SomeCustomerNameUserControl, естественно, реализует ICustomerName, а ICustomerName содержит специфические свойства, которые меня действительно волнуют (скажем, FirstName и LastName). Таким образом, я могу сослаться на UserControl через cName члена и вместо того, чтобы быть пораженным всеми UserControl членами, я получаю только те из ICustomerName.

Все хорошо, но проблема в том, что если я сделаю это таким образом, я не смогу увидеть SomeCustomerNameUserControl в Designer. Кто-нибудь знает, как я могу это сделать, но все еще вижу UserControl на поверхности дизайна формы?

РЕДАКТИРОВАТЬ: Один из способов сделать это, который не слишком сложен, состоит в том, чтобы поместить элементы управления в базовую форму. По умолчанию (в C #) элементы управления являются частными. Затем я создаю свойство для каждого элемента управления, выставляя его через интерфейс.

Однако мне было бы интересно найти другой способ сделать это, даже если он более сложный. Кажется, есть какой-то способ сделать это с IDesignerHost, но я не могу найти подходящих примеров.

Ответы [ 8 ]

6 голосов
/ 05 мая 2009

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

Это то, что делает Windows Forms, когда, например, свойство базового класса ничего не значит для конкретного потомка. Например, Control имеет свойство Text, но свойство Text не имеет смысла, скажем, в TabControl. Поэтому TabControl переопределяет свойство Text и добавляет атрибуты к его переопределению, говоря: «Кстати, не показывайте мое свойство Text в таблице свойств или в Intellisense». Свойство все еще существует, но так как вы никогда не видите его, оно не мешает вам.

Если вы добавите атрибут [EditorBrowsable (EditorBrowsableState.Never)]] к члену (свойству или методу), то Intellisense больше не будет отображать этот элемент в своих списках завершения кода. Если я правильно понимаю ваш вопрос, это самое важное, чего вы пытаетесь достичь: усложнить для кода приложения случайное использование элемента.

Для свойств вы, вероятно, также захотите добавить [Browsable (false)] , чтобы скрыть свойство из таблицы свойств, и [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)] * , чтобы предотвратить дизайнер записывает значение свойства в файл .designer.cs.

Это затруднит случайное использование метода / свойства. Они все еще не гарантия, хотя. Если вам нужна гарантия, добавьте также атрибут [устарел] и выполните сборку с «Обрабатывать предупреждения как ошибки» - тогда вы позаботитесь о.

Если базовый член является виртуальным, вы, вероятно, захотите переопределить его, и пусть ваше переопределение просто вызовет base. Не выбрасывайте исключение, поскольку переопределенный член, вероятно, будет вызываться базовым классом во время обычного хода событий. С другой стороны, если базовый элемент не является виртуальным, тогда вы хотите использовать «новый» вместо «переопределить», и вы можете решить, должна ли ваша реализация вызывать базовый или просто выдавать исключение - никто не должен использовать в любом случае, ваш вновь введенный участник, так что это не должно иметь значения.

public class Widget : UserControl
{
    // The Text property is virtual in the base Control class.
    // Override and call base.
    [EditorBrowsable(EditorBrowsableState.Never)]
    [Browsable(false)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    [Obsolete("The Text property does not apply to the Widget class.")]
    public override string Text
    {
        get { return base.Text; }
        set { base.Text = value; }
    }

    // The CanFocus property is non-virtual in the base Control class.
    // Reintroduce with new, and throw if anyone dares to call it.
    [EditorBrowsable(EditorBrowsableState.Never)]
    [Browsable(false)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    [Obsolete("The CanFocus property does not apply to the Widget class.")]
    public new bool CanFocus
    {
        get { throw new NotSupportedException(); }
    }

    // The Hide method is non-virtual in the base Control class.
    // Note that Browsable and DesignerSerializationVisibility are
    // not needed for methods, only properties.
    [EditorBrowsable(EditorBrowsableState.Never)]
    [Obsolete("The Hide method does not apply to the Widget class.")]
    public new void Hide()
    {
        throw new NotSupportedException();
    }
}

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

6 голосов
/ 24 апреля 2009

Если SomeCustomerNameUserControl определено так:

class SomeCustomerNameUserControl : UserControl, ICustomerName
{
}

Вы все еще можете удалить этот элемент управления в конструкторе (который создает someCustomerNameUserControl1) и делать это всякий раз, когда вам нужно:

ICustomerName cName = someCustomerNameUserControl1;

Может быть, я что-то упускаю, но я думаю, что это так просто.

4 голосов
/ 09 мая 2009

'Я хочу, чтобы ICustomerName был опцией only для доступа к переменной UserControl. Идея состоит в том, что разработчику не нужно «просто помнить», чтобы его разыграть. '

Проблема, с которой вы столкнулись, состоит в том, что у вас есть два совершенно разных способа использования формы и элементов управления, которые на ней размещены. В Visual Studio нет встроенного трюка или winforms, которые решают эту задачу за вас. Это может быть возможно, но существует гораздо более чистый и объектно-ориентированный способ разделения двух методов взаимодействия с элементами управления.

Если вы хотите скрыть тот факт, что эти объекты наследуются от UserControl, и просто хотите трактовать их как IDoSomeThingYouShouldDealWith , вам необходимо отделить логику, которая касается вопросов представления (дизайнер + логика пользовательского интерфейса) от вашей бизнес-логики.

Ваш класс формы должен правильно относиться к элементам управления, таким как UserControls, закрепление, привязка и т. Д. И т. Д., Здесь нет ничего особенного. Вы должны поместить всю логику, необходимую для работы с ICustomerName.FirstName = и т. Д., В отдельный класс. Этот класс не заботится о шрифтах и ​​макете и не знает о них, он просто знает, что есть еще один экземпляр, который может представить имя клиента; или DateTime в качестве элемента управления «выбор даты рождения» и т. д.

Это действительно неудачный пример, но мне нужно идти прямо сейчас. Вы должны быть в состоянии раскрыть идею здесь более подробно :

public interface ICustomerName
    {
        void ShowName(string theName);
    }

    public partial class Form1 : Form, ICustomerName
    {
        public Form1()
        {
            InitializeComponent();
        }

        #region ICustomerName Members

        public void ShowName(string theName)
        {
            //Gets all controls that show customer names and sets the Text propert  
            //totheName
        }

        #endregion
    }

    //developers program logic into this class 
    public class Form1Controller
    {
        public Form1Controller(ICustomerName theForm) //only sees ICustomerName methods
        {
            //Look, i can't even see the Form object from here
            theForm.ShowName("Amazing Name");
        }
    }
3 голосов
/ 03 мая 2009

После добавления UserControl с помощью дизайнера вы можете установить для GenerateMember значение false в окне свойств, чтобы подавить создание элемента.

Затем вы можете использовать другой метод в конструкторе, чтобы назначить ссылку на cName, например ::

foreach(Control control in this.Controls)
{
    cName = control as ICustomerName;
    if (cName != null) break;
}

cName будет единственной ссылкой на UserControl.

1 голос
/ 08 мая 2009

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

public static class FormExtensions
{
    public static IDictionary<string, T> GetControlsOf<T>(this Form form) 
           where T: class
    {
        var result = new Dictionary<string, T>();
        foreach (var control in form.Controls)
        {
            if ((control as T) != null)
                result.Add((control as T).Tag, control as T);
        }
        return result;
    }
}

Тогда в своей форме вы можете назвать это там, где хотите:

this.GetControlsOf<ICustomerName>()["NameOfControlHere"];

В случае, если он возвращает более одного пользовательского элемента управления, вам нужно будет как-то обработать это, возможно, добавив свойство Tag в интерфейс для уникального отслеживания каждого пользовательского элемента управления или чего-то подобного, например

public partial class UserControl1 : UserControl, ICustomerName
{
     public string Tag { get { return this.Name; } }
}

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

Кроме того, следует отметить, что этот подход ТАКЖЕ позволит вам использовать это на ВСЕХ формах в вашем решении.

Единственное, что вам нужно сделать, это установить для GenerateMember значение false.

0 голосов
/ 10 мая 2009

Предположим, что MyUserControl определен так:

class MyUserControl : UserControl, IMyInterface
{
    // ...
}

Тогда в вашей форме должно быть что-то вроде этого:

public class MyForm : Form
{
    IMyInterface cName;

    public MyForm()
    {
        InitializeComponent();

        cName = new MyUserControl();
        Controls.Add((UserControl)cName);
    }
}

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

0 голосов
/ 08 мая 2009

Похоже, вы хотите реализовать шаблон посредника. Вместо того, чтобы иметь дело с каждым из пользовательских контроллеров bazillion напрямую, вы будете взаимодействовать с ними через посредника. Каждый посредник будет определять тонкий интерфейс, который вы хотите видеть из каждого элемента управления. Это уменьшит общую сложность, сделав ваш дизайн более четким и лаконичным. Например, вам не понадобятся 20 свойств и 50 методов, доступных на одном из ваших элементов управления. Вместо этого вы должны иметь дело с посредником для этого элемента управления, который определяет 2 свойства и 5 методов, которые вам действительно нужны. Все будет отображаться в конструкторе, но другие части вашего приложения не будут взаимодействовать с этими элементами управления - они будут взаимодействовать с посредниками.

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

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

0 голосов
/ 24 апреля 2009

вы можете сделать то же самое, что сказал Боб, но назначить все переменные-члены в конструкторе, и тогда вы получите это в одном месте.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...