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

かなり。これが、継承/ポリモーフィズムが役立つ主な理由の 1 つです。コードを呼び出すことで、実際には多くの派生型を持つ可能性のあるジェネリック コレクションを処理し、それらすべてを同じものであるかのように処理できます。

2 つまたは 3 つのタイプしかない場合は、これをスキップする方が簡単かもしれませんが、操作できるタイプのセットが増えるにつれて、これが急速に最適なオプションになります。また、私は WinForm 型をあまり扱っていないことを指摘したいと思います。私が知らないこのタイプの動作 (あなたが複製している) のサポートが既にあるかもしれません。

于 2013-07-19T22:10:01.333 に答える
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

「新しいクラスを作成し、それぞれのコントロールから継承する」で述べたように、私はインターフェイスが好きですが、新しいインターフェイスを使用するには、基になる型を変更する必要があります。これは、このケースでは必ずしも実用的ではありません。したがって、ここでインターフェイスが適切でないと主張するつもりはありませんが、別のアイデアを提供します。

この最初のアプローチでは、コントロールについて認識しているコンパニオン オブジェクトと、コントロールから値を取得する方法を使用します。このクラスはインターフェイスを使用できますが、ここでは必須ではありません。フェッチャーの遅延を (適切に型付けされた方法で) 許可しますが、コンパニオン インスタンスごとに明示的に設定する必要もあります。

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);

もう 1 つの方法は、値を抽出する方法を知っている関数を作成することです。これらのコントロールは「動的に作成される」ため (例: 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;
}

もちろん、上記の 2 つのアプローチは組み合わせることができます1 - たとえば、コントロール オブジェクトの実際のタイプに基づいてマップ/ディクショナリによってルックアップされるタイプごとのエクストラクタ (ControlCompanion に似ていますが、コントロール インスタンスとは無関係) を使用する GetValue 関数. マップ/ディクショナリを手動で維持したくない場合でも、アセンブリ リフレクションはこれらの型ごとのエクストラクタを自動的にロードできます。

同じ方向ですが、上記の提案よりも一般的なのは、タイプの変換を処理するための完全な (複雑ではないにしても) セットアップであるタイプ コンバーターを使用することです。これらのタイプを変更または拡張できない場合でも同様です。

いくつかの異なる可能性があり、コントロールの拡張とインターフェイスの追加通常は機能しますが (コントロールを安全なものとして登録し、特定の洗練された実装によって作成できる必要があります)、上記の型がそのような変更に対応できる場合に限られます。


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 に答える