0

我正在开发一个应用程序,该应用程序将在运行时根据数据库中的数据创建一个表单。它当前用于reflection创建控件并将其添加到表单中。

有了这个我可以很容易地在运行时动态创建一个表单,但接下来我遇到了如何访问当前选择的问题,例如:TextBox.TextDropDownList.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;
    }
}
4

4 回答 4

1

你可以这样做(只是为了说明显而易见的......),但我个人不会这样做。当您所做的只是添加单个方法作为接口实现的一部分时,扩展许多控件是一种相当冗长的方法。

我会使用一个辅助方法,它将 aControl作为其输入,并检查控件的类型(通过强制转换来代替更特定于语言的选项),然后将控件的值作为对象返回。

接口的目的是建立一个与实际实现无关的契约,所以你没有错误地使用它,你只是做了比你真正需要的更多的工作。

于 2013-07-19T22:26:16.933 回答
1

如果它满足您的需求并简化生活,那么您的方法是好的和正确的。

我想展示另一种控件值检索的方法,即它是如何在 ASP.NET Web 窗体本身中完成的。如果您不想为继承而烦恼,并且如果您使用标准输入控件或所有控件都装饰有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;
    } 
}
于 2013-07-21T09:40:59.870 回答
1

差不多。这是继承/多态性有用的主要原因之一。它允许调用代码来处理一个泛型集合,该集合实际上可能有许多派生类型,并像处理它们一样处理它们。

如果你只有两个或三个类型,跳过这个可能更简单,但是随着你可以操作的类型集的增长,这会迅速成为最好的选择。另外,我想指出的是,我不太会处理 WinForm 类型,可能已经对我不知道的这种类型的行为(你会复制)提供一些支持。

于 2013-07-19T22:10:01.333 回答
1

虽然我喜欢接口,但正如“创建一个新类并从相应的控件继承”所指出的那样……使用新接口需要修改基础类型。对于这种情况,这并不总是可行的。所以,虽然我不会声称接口在这里不合适,但我会提供替代的想法。

第一种方法使用一个伴随对象,它知道控件以及如何从控件中获取值。此类可以使用接口,但此处不需要。它允许延迟 fetcher(以良好类型的方式),但也要求它为每个伴随实例显式设置。

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,但独立于控件实例),由基于控件对象的实际类型的映射/字典查找. 如果一个人甚至不想手动维护地图/字典,程序集反射可以自动加载这些每种类型的提取器 - 哦,可能性和可能的​​复杂性!

与上述建议相同但比上述建议更通用的是使用类型转换器,这是一个非常完整(如果不复杂)的设置来处理转换类型 - 即使这些类型无法修改或扩展。

有几种不同的可能性,虽然扩展控件和添加接口通常确实有效(它要求控件可以注册为安全并由特定的改进实现创建),但仅限于所述类型可以适应此类更改的情况。


1好的,这里是一个通用的“无开关”GetValue 的粗略想法。请注意,它将控件实例与“提取器”分开。事实上,这种反转甚至可以用于“获取同伴”以避免像第一个示例中那样显式包装。

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 框架可能支持类似的解析功能,然后只需将此维护推送到配置中。再一次 - 很多方法,很多方法使它变得复杂。

于 2013-07-19T22:23:23.450 回答