2017-01-28 7 views
2

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

Мое приложение имеет 50 классов пользовательских элементов управления, все из которых реализуют мой интерфейс IParamConfig.

IParamConfig определяет 5 свойств и 8 методов.

Из этих пользовательских элементов управления пользователя, некоторые наследуют от TextBox, некоторые из Баттона, некоторые из CheckBox, некоторые из ComboBox и т.д.

я мог бы создать класс ParamConfig, который реализует методы и свойства IParamConfig; но я знаю, что я не могу наследовать каждый из моих настраиваемых классов пользовательских элементов управления как из ParamConfig, так и из TextBox или другого пользовательского класса управления.

Поэтому я вынужден неоднократно дублировать реализацию методов и свойств IParamConfig.

Например, я создал абстрактный класс AbtrParamConfigTextBox, который наследует от TextBox и реализует методы и свойства IParamConfig; тогда я создал множество пользовательских элементов управления TextBox, которые наследуются от этого абстрактного класса.

Аналогичным образом, я создал абстрактный класс AbtrParamConfigComboBox, который наследуется от ComboBox, а также реализует методы и свойства IParamConfig.

Custom user controls inherit from various standard user controls

В целом, я должен был дублировать общий код 12 раз (коробки в красный цвет на картинке).

Мне понравилось бы, если бы я мог использовать общий код только один раз.

Например, мне бы это понравилось, если бы у меня были все пользовательские элементы управления, наследуемые от одного и того же класса (ParamConfig).

All custom user controls inherit from a single class

enter image description here

Я попытался сделать это, создав класс ParamConfig, который наследуется от UserControl, а затем создать ParamConfigTextBox, который наследуется от ParamConfig, который включает в себя один TextBox.

Single Parent class

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

Вопрос:

Am Я застрял в том, чтобы дублировать общий код в 12 раз?

Должен ли я использовать общий подход класса, наследующий от UserControl?

Или есть более чистое решение?

EDIT: Снимок экрана пользовательского интерфейса, как просили. enter image description here У него есть ~ 100 вкладок, каждый из которых содержит пользовательские элементы управления для настройки продукта.

EDIT:

Интерфейс:

// Functions 
    string BmsParamName { get; } // Name of the parameter in the BMS's memory associated with this control 
    void InitControlOnLoad(); // Initialize the control after load 
    void ClearCtrl(); // Clear this control 
    uint BitsUsedMask(); // Return the mask for the bits used in memory 
    void ShowValue16(ushort paramWord, bool isBadData); // Update the value or state displayed by this control 
    void ShowValue32(uint paramValue32, bool isBadData); // Update the value or state displayed by this control 
    uint NoOfBmsMemWords(); // Return the number of BMS memory words that this set uses 
    void ShowParamArray(uint[] paramValues, uint bmsMemOfst, bool isBadData); // Update the value or state displayed by this control 
    ushort GetEntry16(ushort configWord); // Receive the full word of configuration data and return it after replacing those bits that this configuration control is responsible for 
    uint GetEntry32(); // Return the value in this control as a 16 bit unsigned integer 
    ushort[] GetEntryArray(); // Get the set of configuration words for this configuration control 

Свойство:

#region Properties (Protected properties, available to inherited classes) 

    // Name of BMS variable 
    protected string bmsParamName = ""; 
    [DefaultValueAttribute(0), Category("_Vinci"), 
    Description("Name of the variable in the BMS's memory associated with this control")] 
    public string BmsParamName 
    { 
     get { return bmsParamName; } 
     set { bmsParamName = value; } 
    } 

    // Conversion factor 
    protected float conversionFactor = 1F; 
    [DefaultValueAttribute(1), 
    Description("To convert the BMS value to the value shown in this setting, divide by this factor"), 
    Category("_Vinci")] 
    public float ConversionFactor 
    { 
     get { return conversionFactor; } 
     set { conversionFactor = value; } 
    } 

    // Width of the data 
    protected VinciForm.DataWidth dataWidth = VinciForm.DataWidth.Data16; 
    [DefaultValueAttribute(VinciForm.DataWidth.Data16), 
    Description("Whether the data in the BMS fill an entire 32 bit word, a 16 bit Word, just the top 8 bits (MSB), the lower 8 bits (LSB), or specific bits"), 
    Category("_Vinci")] 
    public VinciForm.DataWidth DataWidth 
    { 
     get { return dataWidth; } 
     set 
     { 
      dataWidth = value; 
      firstBitNo = 0u; 
      switch (dataWidth) 
      { 
       case VinciForm.DataWidth.Data32: 
        noOfBits = 32u; 
        break; 

       case VinciForm.DataWidth.Data16: 
        noOfBits = 16u; 
        break; 

       case VinciForm.DataWidth.Data8LSB: 
        noOfBits = 8u; 
        break; 

       case VinciForm.DataWidth.Data8MSB: 
        firstBitNo = 8u; 
        noOfBits = 8u; 
        break; 

       case VinciForm.DataWidth.DataBits: 
        noOfBits = 16u; 
        break; 
      } 
     } 
    } 

    // Number of the least significant bit used 
    protected uint firstBitNo = 0u; 
    [DefaultValueAttribute(0), Category("_Vinci"), 
    Description("Number of the least significant bit used")] 
    public uint FirstBitNo 
    { 
     get { return firstBitNo; } 
     set { firstBitNo = value; } 
    } 

    // Number of bits used 
    protected uint noOfBits = 16u; 
    [DefaultValueAttribute(3), Category("_Vinci"), 
    Description("Number of bits used")] 
    public uint NoOfBits 
    { 
     get { return noOfBits; } 
     set { noOfBits = value; } 
    } 

    // Display format: unsigned, signed or hex 
    protected VinciForm.DisplayFormat displayFrmtCode = VinciForm.DisplayFormat.UnsignedFormat; 
    [DefaultValueAttribute(VinciForm.DataWidth.Data16), 
    Description("Display format: unsigned, signed or hex"), 
    Category("_Vinci")] 
    public VinciForm.DisplayFormat DisplayFormat 
    { 
     get { return displayFrmtCode; } 
     set { displayFrmtCode = value; } 
    } 

    // Format string 
    protected string displayFrmtStr = ""; 
    [DefaultValueAttribute(""), 
    Description("Display format string (e.g.: F4 for 4 decimal places); leave blank for automatic"), 
    Category("_Vinci")] 
    public virtual string DisplayFrmtStr 
    { 
     get { return displayFrmtStr; } 
     set { displayFrmtStr = value; } 
    } 

    #endregion 
+1

Было бы хорошо видеть интерфейс. В противном случае ответы будут неопределенными. – Sefe

+0

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

+0

@Sefe Я добавил скриншот пользовательского интерфейса –

ответ

1

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

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

Чтобы проиллюстрировать свою точку зрения, я использую упрощенную версию интерфейса:

interface IParamConfig 
{ 
    string BmsParamName 
    { 
     get; 
     set; 
    } 

    float ConversionFactor 
    { 
     get; 
     set; 
    } 
} 

Вы бы осуществить это в отдельный вспомогательный класс:

public class ParamConfig : IParamConfig 
{ 
    private string bmsParamName = ""; 
    [DefaultValueAttribute(0), 
    Description("Name of the variable in the BMS's memory associated with this control")] 
    public string BmsParamName 
    { 
     get { return bmsParamName; } 
     set { bmsParamName = value; } 
    } 

    // Conversion factor 
    private float conversionFactor = 1F; 
    [DefaultValueAttribute(1), 
    Description("To convert the BMS value to the value shown in this setting, divide by this factor")] 
    public float ConversionFactor 
    { 
     get { return conversionFactor; } 
     set { conversionFactor = value; } 
    } 
} 

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

public class ParamConfigTextBox : TextBox, IParamConfig 
{ 
    private readonly ParamConfig paramConfig = new ParamConfig(); 

    [DefaultValueAttribute(0), Category("_Vinci"), 
    Description("Name of the variable in the BMS's memory associated with this control")] 
    public string BmsParamName 
    { 
     get { return paramConfig.BmsParamName; } 
     set { paramConfig.BmsParamName = value; } 
    } 

    // Conversion factor 
    [DefaultValueAttribute(1), 
    Description("To convert the BMS value to the value shown in this setting, divide by this factor"), 
    Category("_Vinci")] 
    public float ConversionFactor 
    { 
     get { return paramConfig.ConversionFactor; } 
     set { paramConfig.ConversionFactor = value; } 
    } 
} 

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

Недостатком этого решения является то, что вам по-прежнему необходимо реализовать весь интерфейс во всех ваших элементах управления. Функциональность находится в вашем классе-помощнике, но вам все равно придется выполнять работу по реализации довольно много раз. Вы можете упростить задачу, создав интерфейс провайдера, который намного проще реализовать (на этот раз это полный интерфейс, не упрощенный пример):

interface IParamConfigProvider 
{ 
    IParamConfig ParamConfig 
    { 
     get; 
    } 
} 

public class ParamConfigTextBox : TextBox, IParamConfigProvider 
{ 
    private readonly paramConfig = new ParamConfig(); 

    [Category("_Vinci"), 
    Description("The param config properties")] 
    public ParamConfig ParamConfig 
    { 
     get { return paramConfig; } 
    } 

    IParamConfig IParamConfigProvider.ParamConfig 
    { 
     get { return ParamConfig; } 
    } 
} 

Вы все еще можете перемещаться proprties вашего IParamConfig в вашем визуальном конструкторе (помните, что я сохранил некоторые атрибуты дизайнера? Теперь вы знаете, почему: вы все равно увидите свойства в редакторе). Это самый простой способ реализовать это на уровне управления, но код, который обращается к IParamConfig, необходимо будет изменить, чтобы пройти через IParamConfigProvider. В конце концов, это ваш выбор, что вам лучше.

+0

Вау! Хорошо, я почти все понял. Мне понадобится время, чтобы полностью переварить его. Я вижу, ты идешь, и мне это нравится. Я вернусь к тебе после того, как сыграю с ним, спасибо! –

+1

Слишком плохо, что вы не используете XAML, вы можете просто использовать Attached Properties. –

+0

@ Майкл Пакетт II: Спасибо, что предупредил меня об этом. –