我正在开发一个应用程序,在该应用程序中,将根据数据库中的数据在运行时创建表单。它目前使用reflection
来创建控件并将其添加到表单中。
有了这个,我可以在运行时轻松动态创建表单,但接下来我遇到了如何访问当前所选的问题,例如:TextBox.Text
与DropDownList.SelectedValue
。为了“修复”这个问题,我使用GetValue
方法创建了一个界面。有了这个,我创建了一个新类,并从各自的控件继承并实现了接口。
现在,我可以轻松地遍历表单控件,看看它们是否实现了接口,然后获取控件的值。
所有这一切的问题是:这是实现这一目标的最佳方法吗?
要注意:我完全期望可用于构建这些表单的控件达到15 +。
班级示例:
public interface IFormField
{
string GetId();
object GetValue();
}
public TextBox : System.Web.UI.WebControls.TextBox, IFormField
{
public string GetId()
{
return ID;
}
public object GetValue()
{
return Text;
}
}
答案 0 :(得分:1)
差不多。这是继承/多态有用的主要原因之一。它允许调用代码来处理一个通用集合,这个集合实际上可以有许多派生类型,并且就像处理它们一样处理它们。
如果您只有两种或三种类型,可能更容易跳过此类,但随着您可以操作的类型集的增长,这将成为最佳选择。另外,我想指出我不会非常处理WinForm类型,可能已经有一些支持这种类型的行为(你要重复),我不知道。
答案 1 :(得分:1)
虽然我喜欢接口,但正如“创建新类并从相应控件继承”所述。使用新接口需要修改基础类型。对于这种情况,这并不总是实用的。因此,虽然我不会声称这里的接口不合适,但我会提供其他想法。
第一种方法使用一个知道控件的伴随对象,以及如何从控件中获取值。这个类可以使用一个接口,但这里不需要它。它允许延迟提取器(以良好类型的方式),但也要求它是每个伴随实例的显式设置。
interface IWithValue {
string Value { get; }
}
class ControlCompanion<T>: IWithValue where T: Control {
IFunc<Control, string> readValue;
public T Control { get; private set; }
public string Value { get { return readValue(Control); } }
public ControlCompanion (T control, IFunc<T, string> readValue) {
Control = control;
this.readValue = readValue;
}
}
// this is typed narrowly here, but it could be typed wider to
// the actual ControlCompanion if needing additional information
// or actions wrt. the particular control
var valueAccessors = new List<IWithValue>();
var textBox = new TextBox();
valueAccessors.Add(new ControlCompanion(textBox, (c) => c.Text));
var comboBox = new ComboBox();
valueAccessors.Add(new ControlCompanion(comboBox, (c) => c.SelectedValue));
var allValues = valueAccessors.Select(v => v.Value);
另一种方法是创建一个知道如何提取值的函数。因为这些控件是“动态创建的”(例如Control类型),所以我们不能直接使用方法重载,因此必须接受更通用的类型并使用某种形式的反射或类型细化。
string GetValue(Control c) {
// using this form will allow invalid path detection
TextBox tb;
ComboBox cb;
if ((tb = c as TextBox) != null) {
return tb.Text;
} else if ((cb = c as ComboBox) != null) {
return GetValue(cb);
} else {
throw new Exception("Unsupported control");
}
}
// but we could use overloading once refined ..
string GetValue(ComboBox cb) {
return cb.SelectedValue;
}
当然,上述两种方法可以组合在一起 1 - 例如GetValue函数使用每类型提取器(类似于ControlCompanion但控件实例的独立),由地图/字典根据控件对象的实际类型进行查找。如果一个人甚至不想手动维护地图/字典,那么程序集反射可以自动加载这些每类型提取器 - 哦,可能性和可能的复杂性!
沿着相同的路线但比上述建议更通用的是使用Type Converters这是一个非常完整(如果不是很复杂的)设置来处理转换类型 - 即使这些类型无法修改或扩展。
有几种不同的可能性,虽然扩展控件和添加接口 通常可以工作(它需要控件可以注册为安全并由特定的精炼实现创建),但它仅限于案例其中所述类型可以适应这种变化。
1 好的,对于一般的“无开关”GetValue,这是一个粗略的想法。请注意,它将控件实例与“fetcher”分开。实际上,这样的反转甚至可以用来“获取同伴”以避免显式包装,如第一个例子所示。
interface IFetchValue {
string FetchValue(Control c);
}
abstract class Fetcher<T>: IFetchValue where T : Control {
abstract protected FetchControlValue(T c);
public string FetchValue (Control c) {
return FetchControlValue((T)c);
}
}
class TextBoxFetcher: Fetcher<TextBox> {
protected string FetchControlValue (TextBox tb) {
return tb.Value;
}
}
class ComboBoxFetcher: Fetcher<ComboBox> {
protected string FetchControlValue (ComboBox cb) {
return cb.SelectedValue;
}
}
// This could be initialized via reflection of all
// Fetcher<T>/IFetchValue types with a bit more work.
IDictionary<Type, IFetchValue> map = new Dictionary<Type, IFetchValue> {
{ typeof(TextBox), new TextBoxFetcher() },
{ typeof(ComboBox), new ComboBoxFetcher() },
};
string GetValue(Control c) {
IFetchValue fetcher;
// This should be smarter to also try parent types or
// check general assignability.
if (c != null && map.TryGetValue(c.GetType(), out fetcher)) {
return fetcher(c);
} else {
throw new Exception("Whoops!");
}
}
此外,您最喜欢的DI / IoC框架可能支持类似的解析功能,然后只需将此维护推送到配置中。再一次 - 许多方法,以及许多方法使它变得复杂。
答案 2 :(得分:1)
你可以这样做(只是陈述明显的...),但我个人不会。当你所做的只是添加一个方法作为接口实现的一部分时,扩展一些控件是一个相当漫长的方法来做到这一点。
我会使用一个辅助方法,它以Control
作为输入,并检查控件的类型(通过转换代替更具语言特定的选项),然后将控件的值作为对象返回
接口的目的是建立一个合同而不管实际的实现,所以你没有错误地使用它,你刚刚做了比你真正需要的更多的工作。
答案 3 :(得分:1)
如果满足您的需求并简化生活,您的方法是正确和正确的。
我想展示一种控制值检索的另一种方式,即在ASP.NET Web Forms本身中如何实现它的方式。如果您不想使用继承,并且使用标准输入控件或所有控件都使用ValidationPropertyAttribute
进行修饰(如果您希望在自定义中使用标准验证控件,则此方法非常有用)那些)。
要检索任何标准输入控件的值,我们需要使用BaseValidator.GetValidationProperty方法。此方法为验证属性返回PropertyDescriptor
实例,该属性保存控件的值(ListItem除外,但这种情况在代码片段中有所介绍)。
因此,值检索的完整代码将是:
public static string GetControlValue(Control c)
{
// This code is copied as-is from BaseValidator.GetControlValidationValue method
PropertyDescriptor prop = BaseValidator.GetValidationProperty(c);
if (prop == null) {
return null;
}
object value = prop.GetValue(c);
if (value is ListItem) {
return((ListItem) value).Value;
}
else if (value != null) {
return value.ToString();
}
else {
return string.Empty;
}
}