C# 实现相同界面的多个自定义用户控件;有没有办法避免在每个代码中重复相同的代码?
对不起,我在StackOverflow中找不到这个问题的答案,尽管这一定是重复的。我知道多重继承是不可能的 我的应用程序有50类自定义用户控件,所有这些控件都实现了我的IParamConfig接口 IParamConfig定义了5个属性和8个方法 在这些自定义用户控件中,有些从文本框继承,有些从按钮继承,有些从复选框继承,有些从组合框继承,等等 我可以创建一个ParamConfig类来实现IParamConfig的方法和属性;但是我知道我不能让我的每个自定义用户控件类同时从ParamConfig和TextBox或其他用户控件类继承 因此,我不得不多次复制IParamConfig的方法和属性的实现 例如,我创建了一个抽象类AbtrParamConfigTextBox,它继承了TextBox并实现了IParamConfig的方法和属性;然后,我创建了许多类似文本框的自定义用户控件,这些控件继承自这个抽象类 类似地,我创建了一个抽象类AbtrParamConfigComboBox,它继承自ComboBox,还实现了IParamConfig的方法和属性 总的来说,我不得不将通用代码复制12次(图中红色的框) 如果我能让通用代码只出现一次,我会很高兴的 例如,如果我能让所有自定义用户控件从同一个类(ParamConfig)继承,我会很高兴 我尝试通过创建一个从UserControl继承的ParamConfig类,然后创建一个从ParamConfig继承的ParamConfigTextBox来实现这一点,该类包含一个文本框 但这导致了棘手的解决办法,因为我不再能够直接访问ParamConfigTextBox中TextBox的属性,因此我必须在ParamConfigTextBox中复制它们,并添加代码将属性从ParamConfigTextBox复制到其TextBox。丑陋的 问题: 我是否被困在必须将通用代码复制12次的困境中 我应该采用从UserControl继承的公共类方法吗 还是有更清洁的解决方案 编辑:用户界面的屏幕截图,如要求。 它有大约100个选项卡,每个选项卡都有用户控件来配置产品 编辑: 接口:C# 实现相同界面的多个自定义用户控件;有没有办法避免在每个代码中重复相同的代码?,c#,user-controls,multiple-inheritance,C#,User Controls,Multiple Inheritance,对不起,我在StackOverflow中找不到这个问题的答案,尽管这一定是重复的。我知道多重继承是不可能的 我的应用程序有50类自定义用户控件,所有这些控件都实现了我的IParamConfig接口 IParamConfig定义了5个属性和8个方法 在这些自定义用户控件中,有些从文本框继承,有些从按钮继承,有些从复选框继承,有些从组合框继承,等等 我可以创建一个ParamConfig类来实现IParamConfig的方法和属性;但是我知道我不能让我的每个自定义用户控件类同时从ParamConfig
// 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
您的接口似乎无法访问实现控件的任何属性(至少不能访问示例中的属性)。这意味着将它们封装在自己的类中并重用它应该相当容易。即使它将访问控件的某些属性,您也可以在单独的类中实现接口,并使用回调方法等技术来访问控件特定的部分(不能给您一个示例,因为对于您刚才在文章中为我们提供接口的方法) 然后可以在控件中使用此类的实例,从而将控件保存到实现中并重用代码。这实际上就是许多常见的设计模式(如桥接器、访问者或适配器)所做的:用组合替换继承 为了说明我的观点,我使用了您界面的简化版本:
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; }
}
}
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; }
}
}
我为属性编辑器保留了一些属性,即使它是一个帮助器类。稍后你会明白我为什么这么做。如果您想接近当前的实现,可以在控件上实现IParamConfig
,并转发helper类的功能:
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; }
}
}
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; }
}
}
诚然,这两个属性似乎有点太简单,无法卸载到helper类中,但您将把它们都放在那里,也包括更复杂的属性,因此总体而言,您将实现最佳的代码重用
此解决方案的缺点是,您仍然必须在所有控件中实现整个接口。该功能在您的helper类中,但是您仍然需要多次进行实现的准备工作。您可以通过创建一个更易于实现的提供程序接口来简化任务(这次是完整的接口,没有简化的示例):
您仍然可以在可视化设计器中导航
IParamConfig
的属性(还记得我保留了一些设计器属性吗?-现在您知道原因了:您仍然可以在编辑器中看到属性)。这是在控制级别实现此功能的最简单方法,但是需要更改访问IParamConfig
的代码,才能通过IParamConfigProvider
。最后,您可以选择更好的服务。最好能看到界面。否则回复就会模糊不清。为什么你不能直接访问文本框?你把它保密了吗?如果您将其添加为带有受保护访问修饰符的属性,则子类应该能够直接使用它。@Sefe我添加了用户界面的屏幕截图I的意思是IParamConfig
@chadnt类ParamConfigTextBox继承自UserControl,而非TextBox,因此它无权访问TextBox的属性。它包含一个文本框,但它不是一个文本框的孩子。哇!好的,我差不多明白了。我需要一段时间才能完全消化。我知道你要去,我很喜欢。玩过之后我会再打给你的,谢谢!太遗憾了,你没有使用XAML,你可以只使用附加属性。@Michael Puckett II:谢谢你让我意识到这一点。