web-dev-qa-db-ja.com

WinForm UI検証

Winformアプリ全体に入力検証を実装する必要があります。データを入力できるさまざまなフォームがありますが、フォームごとにコントロールを制御し、アイテムごとにisValidなどを作成したくないです。他の人はこれにどのように対処しましたか?

関連する投稿のほとんどがWeb Appsを扱っているか、または Enterprise Library Validation Application Block に言及していることがわかります。今、私はELVABを徹底的に研究していないことを認めていますが、それはseems私が必要とするものに対する過剰なもののようです。私の現在の考えは、さまざまな要件を持つクラスライブラリを作成し、パラメータとしてコントロールを渡すことです。私はすでにisValidZipCodeなどのRegEx関数のライブラリを持っているので、そこから始めることができます。

私が欲しいのは、onClickがそのフォームページ上のすべてのコントロールを循環し、必要な検証を実行する検証ボタンです。どうすればこれを達成できますか?

42

私のアプリケーションでは、入力されたディメンションを検証する必要があります。使用したシーケンスは次のとおりです。

  1. ユーザーが選択または入力すると、コントロールから離れます。
  2. コントロールはフォーカスを失い、IDとエントリテキストを送信するビューに通知します。
  3. ビューは、どのシェイププログラム(インターフェイスを実装するクラス)がフォームを作成したかを確認し、IDとエントリテキストを渡します
  4. 形状プログラムは応答を返します。
  5. 応答がOKの場合、ビューはシェイプクラスの正しいエントリを更新します。
  6. 応答がOKの場合、ビューはインターフェイスを介してフォームに、フォーカスを次のエントリに移動してもよいことを伝えます。
  7. 応答がOKでない場合、ビューは応答を確認し、フォームインターフェイスを使用してフォームに処理を指示します。これは通常、フォーカスが問題のあるエントリに戻り、ユーザーに何が起こったかを知らせるメッセージが表示されることを意味します。

このアプローチの利点は、特定のシェイププログラムの1つの場所に検証が集中化されることです。各コントロールを変更したり、フォーム上のさまざまなタイプのコントロールを実際に心配したりする必要はありません。ソフトウェアを設計したとき、テキストボックス、リストボックス、コンボボックスなどでUIがどのように機能するかを決定しました。また、重大度のレベルが異なると、処理方法も異なります。

ビューは、インターフェイスを介して何をするかをフォームに指示することを処理します。実際の実装方法は、インターフェイスの実装内のフォーム自体によって処理されます。ビューは、警告の場合はフォームが黄色で表示され、エラーの場合は赤で表示されているかどうかは関係ありません。それらの2つのレベルを処理するだけです。後で警告とエラーを表示するというより良いアイデアが得られたら、Viewロジックを変更したり、Shape Programで検証するよりも、フォーム自体に変更を加えることができます。

検証ロジックを保持するクラスを作成することを検討している場合、あなたはすでにそこに半分あります。これにより、新しいデザインの残りの部分が得られます。

8
RS Conley

検証はすでにWinFormsライブラリに組み込まれています。

Controlから派生した各オブジェクトには、ValidatingおよびValidatedという名前の2つのイベントがあります。また、 CausesValidation というプロパティがあります。これがtrueに設定されている場合(デフォルトではtrue)、コントロールは検証に参加します。そうでなければ、そうではありません。

検証は、フォーカスの一部として行われます。コントロールからフォーカスを外すと、その検証イベントが発生します。実際、フォーカスイベントは特定の順序で発生します。 [〜#〜] msdn [〜#〜] から:

キーボード(TAB、SHIFT + TABなど)を使用して、SelectメソッドまたはSelectNextControlメソッドを呼び出すか、ContainerControl .. ::。ActiveControlプロパティを現在のフォームに設定してフォーカスを変更すると、フォーカスイベントが発生します。次の順序:

  1. 入る
  2. GotFocus
  3. 去る
  4. 検証中
  5. 検証済み
  6. LostFocus

マウスを使用するか、Focusメソッドを呼び出してフォーカスを変更すると、次の順序でフォーカスイベントが発生します。

  1. 入る
  2. GotFocus
  3. LostFocus
  4. 去る
  5. 検証中
  6. 検証済み

CausesValidationプロパティがfalseに設定されている場合、ValidatingおよびValidatedイベントは抑制されます。

ValidatingイベントデリゲートでCancelEventArgsのCancelプロパティがtrueに設定されている場合、Validatingイベントの後に通常発生するすべてのイベントが抑制されます。

また、ContainerControlには ValidateChildren() というメソッドがあり、含まれるコントロールをループし、それらを検証します。

62
Matt Brunell

私はこのスレッドがかなり古いことを知っていますが、私が思いついた解決策を投稿すると思いました。

WinFormsでの検証の最大の問題は、コントロールが「フォーカスを失った」ときにのみ検証が実行されることです。そのため、ユーザーは実際にテキストボックス内をクリックしてから、検証ルーチンを実行するために別の場所をクリックする必要があります。入力されたデータが正しいことだけを懸念している場合は、これで問題ありません。ただし、ユーザーがテキストボックスをスキップして空のままにしないようにする場合、これはうまく機能しません。

私のソリューションでは、ユーザーがフォームの送信ボタンをクリックすると、フォーム上の各コントロール(または指定されたコンテナ)をチェックし、リフレクションを使用して、コントロールに検証メソッドが定義されているかどうかを判断します。そうである場合、検証メソッドが実行されます。検証のいずれかが失敗すると、ルーチンは失敗を返し、プロセスの停止を許可します。このソリューションは、特に検証するフォームがいくつかある場合に有効です。

1)コードのこのセクションをコピーしてプロジェクトに貼り付けます。 Reflectionを使用しているので、usingステートメントにSystem.Reflectionを追加する必要があります

class Validation
{
    public static bool hasValidationErrors(System.Windows.Forms.Control.ControlCollection controls)
    {
        bool hasError = false;

        // Now we need to loop through the controls and deterime if any of them have errors
        foreach (Control control in controls)
        {
            // check the control and see what it returns
            bool validControl = IsValid(control);
            // If it's not valid then set the flag and keep going.  We want to get through all
            // the validators so they will display on the screen if errorProviders were used.
            if (!validControl)
                hasError = true;

            // If its a container control then it may have children that need to be checked
            if (control.HasChildren)
            {
                if (hasValidationErrors(control.Controls))
                    hasError = true;
            }
        }
        return hasError;
    }

    // Here, let's determine if the control has a validating method attached to it
    // and if it does, let's execute it and return the result
    private static bool IsValid(object eventSource)
    {
        string name = "EventValidating";

        Type targetType = eventSource.GetType();

        do
        {
            FieldInfo[] fields = targetType.GetFields(
                 BindingFlags.Static |
                 BindingFlags.Instance |
                 BindingFlags.NonPublic);

            foreach (FieldInfo field in fields)
            {
                if (field.Name == name)
                {
                    EventHandlerList eventHandlers = ((EventHandlerList)(eventSource.GetType().GetProperty("Events",
                        (BindingFlags.FlattenHierarchy |
                        (BindingFlags.NonPublic | BindingFlags.Instance))).GetValue(eventSource, null)));

                    Delegate d = eventHandlers[field.GetValue(eventSource)];

                    if ((!(d == null)))
                    {
                        Delegate[] subscribers = d.GetInvocationList();

                        // ok we found the validation event,  let's get the event method and call it
                        foreach (Delegate d1 in subscribers)
                        {
                            // create the parameters
                            object sender = eventSource;
                            CancelEventArgs eventArgs = new CancelEventArgs();
                            eventArgs.Cancel = false;
                            object[] parameters = new object[2];
                            parameters[0] = sender;
                            parameters[1] = eventArgs;
                            // call the method
                            d1.DynamicInvoke(parameters);
                            // if the validation failed we need to return that failure
                            if (eventArgs.Cancel)
                                return false;
                            else
                                return true;
                        }
                    }
                }
            }

            targetType = targetType.BaseType;

        } while (targetType != null);

        return true;
    }

}

2)検証するコントロールで標準の検証イベントを使用します。 検証が失敗したときにe.Cancelを使用してください!

private void txtLastName_Validating(object sender, CancelEventArgs e)
    {
        if (txtLastName.Text.Trim() == String.Empty)
        {
            errorProvider1.SetError(txtLastName, "Last Name is Required");
            e.Cancel = true;
        }
        else
            errorProvider1.SetError(txtLastName, "");
    }

3)このステップをスキップしないでください!フォームのAutoValidateプロパティをEnableAllowFocusChangeに設定します。これにより、検証が失敗した場合でも、別のコントロールへのタブ移動が可能になります。

4)最後に、Submit ButtonメソッドでValidationメソッドを呼び出し、確認するコンテナを指定します。フォーム全体でも、パネルやグループなどのフォーム上のコンテナでもかまいません。

private void btnSubmit_Click(object sender, EventArgs e)
    {
        // the controls collection can be the whole form or just a panel or group
        if (Validation.hasValidationErrors(frmMain.Controls))
            return;

        // if we get here the validation passed
        this.close();
    }

ハッピーコーディング!

42
Bruce

フォームごとに制御して、アイテムごとにisValidなどを作成する必要はありません。

何らかのレベルとして、各コントロールに対してvalidとはどういう意味かを定義する必要があります。ただし、コントロールが何らかの値を持っているということだけを気にする場合は除きます。

とは言っても、 ErrorProviderコンポーネント を使用すると、かなりうまく機能します。

4
Joel Coehoorn

Noogen ValidationProvider で幸運に恵まれました。単純なケース(データ型のチェックと必須フィールド)に対してシンプルであり、より複雑なケースのカスタム検証を簡単に追加できます。

3
Jamie Ide

上記のアイデアとこれを一般的な検証イベントハンドラと組み合わせると、ビジネスクラスのすべての検証メソッドで適切な検証エラー「フレームワーク」が発生します。ブルースのコードをデンマークのアイデアで拡張しました。これはEntity FrameworkおよびDev Expressコンポーネントに対して行われましたが、これらの依存関係は簡単に削除できます。楽しい!

public class ValidationManager
{
    /// <summary>
    /// Call this method to validate all controls of the given control list 
    /// Validating event will be called on each one
    /// </summary>
    /// <param name="controls"></param>
    /// <returns></returns>
    public static bool HasValidationErrors(System.Windows.Forms.Control.ControlCollection controls)
    {
        bool hasError = false;

        // Now we need to loop through the controls and deterime if any of them have errors
        foreach (Control control in controls)
        {
            // check the control and see what it returns
            bool validControl = IsValid(control);
            // If it's not valid then set the flag and keep going.  We want to get through all
            // the validators so they will display on the screen if errorProviders were used.
            if (!validControl)
                hasError = true;

            // If its a container control then it may have children that need to be checked
            if (control.HasChildren)
            {
                if (HasValidationErrors(control.Controls))
                    hasError = true;
            }
        }
        return hasError;
    }

    /// <summary>
    /// Attach all youe Validating events to this event handler (if the controls requieres validation)
    /// A method with name Validate + PropertyName will be searched on the binded business entity, and if found called
    /// Throw an exception with the desired message if a validation error is detected in your method logic
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    public static void ValidationHandler(object sender, CancelEventArgs e)
    {
        BaseEdit control = sender as BaseEdit;

        if (control.DataBindings.Count > 0) //control is binded
        {
            string bindedFieldName = control.DataBindings[0].BindingMemberInfo.BindingField;

            object bindedObject = control.BindingManager.Current;

            if (bindedObject != null) //control is binded to an object instance
            {
                //find and call method with name = Validate + PropertyName

                MethodInfo validationMethod = (from method in bindedObject.GetType().GetMethods()
                                               where method.IsPublic &&
                                                     method.Name == String.Format("Validate{0}",bindedFieldName) &&
                                                     method.GetParameters().Count() == 0
                                               select method).FirstOrDefault();

                if (validationMethod != null) //has validation method
                {
                    try
                    {
                        validationMethod.Invoke(bindedObject, null);

                        control.ErrorText = String.Empty; //property value is valid
                    }
                    catch (Exception exp)
                    {
                        control.ErrorText = exp.InnerException.Message;
                        e.Cancel = true;
                    }
                }
            }
        }
    }

    // Here, let's determine if the control has a validating method attached to it
    // and if it does, let's execute it and return the result
    private static bool IsValid(object eventSource)
    {
        string name = "EventValidating";

        Type targetType = eventSource.GetType();

        do
        {
            FieldInfo[] fields = targetType.GetFields(
                 BindingFlags.Static |
                 BindingFlags.Instance |
                 BindingFlags.NonPublic);

            foreach (FieldInfo field in fields)
            {
                if (field.Name == name)
                {
                    EventHandlerList eventHandlers = ((EventHandlerList)(eventSource.GetType().GetProperty("Events",
                        (BindingFlags.FlattenHierarchy |
                        (BindingFlags.NonPublic | BindingFlags.Instance))).GetValue(eventSource, null)));

                    Delegate d = eventHandlers[field.GetValue(eventSource)];

                    if ((!(d == null)))
                    {
                        Delegate[] subscribers = d.GetInvocationList();

                        // ok we found the validation event,  let's get the event method and call it
                        foreach (Delegate d1 in subscribers)
                        {
                            // create the parameters
                            object sender = eventSource;
                            CancelEventArgs eventArgs = new CancelEventArgs();
                            eventArgs.Cancel = false;
                            object[] parameters = new object[2];
                            parameters[0] = sender;
                            parameters[1] = eventArgs;
                            // call the method
                            d1.DynamicInvoke(parameters);
                            // if the validation failed we need to return that failure
                            if (eventArgs.Cancel)
                                return false;
                            else
                                return true;
                        }
                    }
                }
            }

            targetType = targetType.BaseType;

        } while (targetType != null);

        return true;
    }

}

サンプル検証方法:

partial class ClientName
{
    public void ValidateFirstName()
    {
        if (String.IsNullOrWhiteSpace(this.FirstName))
            throw new Exception("First Name is required.");
    }

    public void ValidateLastName()
    {
        if (String.IsNullOrWhiteSpace(this.LastName))
            throw new Exception("Last Name is required.");
    }
}
2
Rey

すべてのフォームで、問題の特定のコントロールにisValidatingイベントを実装し、データが検証されない場合、フォームにerrorProviderがあり、そのSetError(...)メソッドを使用してエラーをコントロールに設定しますなぜそれが間違っているのかについての関連情報を疑問視しています。

編集>私はこれを行うときに一般的にmvcパターンを使用するため、モデルのそのコントロール/メンバーの特定の検証がモデルで行われるため、isValidatingは次のように見えることに注意してください:

private uicontrol_isValidating(...)
{
    if(!m_Model.MemberNameIsValid())
    {
        errorProvider.SetError(...);
    }
}
2
Steven Evers

どちらにしても。または、同様の検証を必要とするすべてまたはコントロールに関連付けられた単一の検証イベントを持つことができます。これにより、コードからループが削除されます。整数のみを持つことができる4つのテキストボックスがあるとします。できることは、それぞれに1つのイベントを設定することです。私はIDEを持っているわけではないので、以下のコードが思いつくことができます。

this.textbox1.Validated += <ValidatedEvent>
this.textbox2.Validated += <ValidatedEvent>
this.textbox3.Validated += <ValidatedEvent>
this.textbox4.Validated += <ValidatedEvent>

イベントで:

  1. 送信者をテキストボックスとしてキャストします。
  2. テキストボックスの値が数値かどうかを確認します。

など、イベントが並んでいます。

お役に立てれば。

2
danish

大まかなアイデア:


void btnValidate_Click(object sender, EventArgs e)
{
  foreach( Control c in this.Controls )
  {
    if( c is TextBox )
    {
      TextBox tbToValidate = (TextBox)c;
      Validate(tbToValidate.Text);
    }
  }
}

他のコントロールのループを避けたい場合は、テキストボックスをパネル内に貼り付けて、そこのコントロールのみをループすることができます。

1
Will Eddins

検証イベントを使用しないのはなぜですか?単一の検証イベントを使用して、そこでコントロールを検証できます。ループを使用する必要はなく、データが入力されると各コントロールが検証されます。

1
danish

コントロールの切り替えは機能しますが、エラーが発生しやすくなります。私はその手法を使用したプロジェクトに取り組んでおり(C#ではなくDelphiプロジェクトであることが認められました)、期待どおりに動作しましたが、コントロールが追加または変更された場合、更新するのは非常に困難でした。これは修正可能である可能性があります。よく分かりません。

とにかく、単一のイベントハンドラーを作成して、各コントロールにアタッチしました。その後、ハンドラーはRTTIを使用してコントロールのタイプを判別します。次に、大きなselectステートメントでコントロールのnameプロパティを使用して、実行する検証コードを見つけます。検証に失敗した場合、エラーメッセージがユーザーに送信され、コントロールにフォーカスが与えられました。物事をより複雑にするために、フォームをいくつかのタブに分割し、その子コントロールがフォーカスを取得するために適切なタブを表示する必要がありました。

それが私の経験です。

むしろ、パッシブビューデザインパターンを使用して、フォームからすべてのビジネスルールを削除し、それらをPresenterクラスにプッシュします。フォームの状態によっては、投資する意思よりも多くの作業が必要になる場合があります。

1
Kenneth Cochran