Reputation: 1413
Sorry, but I was not able to find an answer to this question in StackOverflow, even though this must be a duplicate. I do know that multiple inheritance is not possible.
My application has 50 classes of custom user controls, all of which implement my IParamConfig Interface.
The IParamConfig defines 5 properties and 8 methods.
Of those custom user controls, some inherit from TextBox, some from Button, some from CheckBox, some from ComboBox, etc.
I could create a ParamConfig class that implements the Methods and Properties of IParamConfig; but I know that I cannot have each of my custom user controls classes inherit from both the ParamConfig and TextBox or some other user control class.
Therefore, I am forced to duplicate the implementation of the Methods and Properties of IParamConfig multiple times.
For example, I created an abstract class AbtrParamConfigTextBox that inherits from TextBox and implements the Methods and Properties of IParamConfig; then, I created many TextBox-like custom user controls that inherit from this abstract class.
Similarly, I created an abstract class AbtrParamConfigComboBox that inherits from ComboBox and that also implements the Methods and Properties of IParamConfig.
In total, I had to duplicate the common code 12 times (boxes in red in the picture).
I'd love it if I could have the common code appear only once.
For example, I'd love it if I could have all the custom user controls inherit from the same class (ParamConfig ).
I tried doing that by creating a ParamConfig class that inherits from UserControl, and then creating a ParamConfigTextBox that inherits from ParamConfig, that includes a single TextBox.
But that resulted in awkward workarounds, because I no longer have direct access to the Properties of the TextBox in ParamConfigTextBox, so I have to duplicate them in ParamConfigTextBox, and add code to copy the properties from ParamConfigTextBox to its TextBox. Ugly.
Question:
Am I stuck in having to duplicate the common code 12 times?
Should I pursue the common class approach inheriting from UserControl?
Or is there a cleaner solution?
EDIT: A screenshot of the User Interface, as requested.
It has ~100 tabs, each full of user controls to configure the product.
EDIT:
Interface:
// 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
Properties:
#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
Upvotes: 2
Views: 851
Reputation: 14007
Your interface does not seem to access any properties of the implementing control (at least not the properties in your example). That means it should be fairly easy to encapsulate them in their own class and reuse it. Even if it would access some properties of your control, you could implement the interface in a separate class and use techniques such as callback methods to access the control specific part (can't give you an example, because for the methods you just gave us the interface in the post).
You can then use instances of this class in controls and thus save the controls the implementation and reuse code. That is actually what a lot of the common design patterns do (such as bridge, visitor or adapter): replace inheritance with composition.
To illustrate my point, I use a simplified version of your interface:
interface IParamConfig
{
string BmsParamName
{
get;
set;
}
float ConversionFactor
{
get;
set;
}
}
You would implement this in a separate helper class:
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; }
}
}
I left some of the attributes for the property editor, even though it is a helper class. You'll see later why I did it. If you want to stay close to your current implementation, you can implement IParamConfig
on your control and forward the functionality of the helper class:
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; }
}
}
Admittedly, these two properties seem to be a bit too simple to offload into a helper class, but you are going to have them all in there, also the more complex ones, so overall you will achieve the optimal code reusage.
The downside of this solution is that you still have to implement the entire interface in all your controls. The functionality is in your helper class, but you still have to do the footwork of implementation quite a few times. You can simplify your task by creating a provider interface, which is much easier to implement (this time it's the full interface, no simplified example):
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; }
}
}
You can still navigate the proprties of your IParamConfig
in your visual designer (remember that I kept some of the designer attributes? - now you know why: you'll still see the properties in the editor). That is the easiest way to implement this at control level, but the code that accesses IParamConfig
would need to be changed to go through IParamConfigProvider
. At the end it's your choice what serves you better.
Upvotes: 1