Use the custom verification component library to expand Windows Forms (2)

Source: Internet
Author: User
Content on this page
Review
Procedural form range Verification
Validatorcollection
Validatormanager
Another problem: Update basevalidator
Enumerate validatorcollection
Declarative form range verification: formvalidator
Verify by the tab key order
Our location
Thank you
Visual Basic. NET and C #
References

Review

In the previous article, we implemented a set of verification components. These components provide reusable and declarative verification from within the Visual Studio. Net windows Form Designer with the help of the inherent windows form verification infrastructure. The results provide verification for each control, that is, the verification that occurs when users navigate between controls. Unfortunately, when the user completes the data input, they cannot guarantee that they have navigation and subsequently verified all controls in the form. In the above case, a form-range verification solution is required to prevent input of unreliable data. In this phase, we will explore how the existing custom verification component library supports form range verification programmatically and then convert it to declarative alternative verification.

Procedural form range Verification

One technique for implementing form range verification is in Windows FormsOKWhen the button is clicked, check the validity of all related controls. Let's useAdd new employeeExample form (1) to describe this method.

Figure 1 Add new employee form and related verification Components

Each validators are exposedValidateMethod andIsvalidAttributes, all of which are inherited fromBasevalidator. You can use these members to determine the validity of the form, as shown below:

private void btnOK_Click(object sender, System.EventArgs e) {  // Validate all controls, including those whose Validating  // events may not have had the opportunity to fire  reqName.Validate();  reqDOB.Validate();  cstmDOB.Validate();  reqPhoneNumber.Validate();  rgxPhoneNumber.Validate();  reqTypingSpeed.Validate();  rngTypingSpeed.Validate();  reqCommences.Validate();  cmpCommences.Validate();  // Check whether the form is valid  if( (reqName.IsValid) &&      (reqDOB.IsValid) &&      (cstmDOB.IsValid) &&      (reqPhoneNumber.IsValid) &&      (rgxPhoneNumber.IsValid) &&      (reqTypingSpeed.IsValid) &&      (rngTypingSpeed.IsValid) &&      (reqCommences.IsValid) &&      (cmpCommences.IsValid) ) DialogResult = DialogResult.OK;  else MessageBox.Show("Form not valid.");}

We can give some interesting comments on the above Code. First, my mother can write better code than this. Second, it is not scalable, because as more verification programs are added to the form, this technology requires us to write more code.

Back to Top

Validatorcollection

However, the most important comment is thatValidateAndIsvalid. This pattern allows people to think of refactoring enumeration styles. It allows us to write better code than my mother and, more importantly, provides scalable alternative code, this will undoubtedly solve the two problems mentioned above. Unfortunately, althoughSystem. Windows. Forms. FormPassControlsProperty implements an enumeration control set, but it does not provide components with similar functions. But interestingly,Windows Forms designerIndeed, a collection of components generated by the designer is inserted into a Windows form, which is appropriately calledComponents:

public class AddNewEmployeeForm : System.Windows.Forms.Form {  ...  ///   /// Required designer variable.  ///   private System.ComponentModel.Container components = null;  ...}

ComponentsManage a list of components that use unmanaged resources and need to be disposed of after the host form is disposed.System. Windows. Forms. TimerThis component depends on the unmanaged Win32 system timer (beyond the scope of this article, but it can be found in Chapter 9 of Chris sells's book Windows Forms programming in C ). BecauseComponentsCollections are managed by the designer, and because our custom verification components do not depend on unmanaged resources, we cannot use these components for enumeration. Instead, we must create our own guaranteed and strongly typedBasevalidatorsSet. Manual creation of such sets, especially strong collections, may be a time-consuming and laborious task. In the above circumstances, we recommend that you useCollectiongen(Http://sellsbrothers.com/tools/#collectiongen), which is a Visual Studio. Net custom tool created by Chris sells 'Et Al, can do this heavy lifting for you.CollectiongenForBasevalidatorSet (calledValidatorcollection) Generate the following code:

[Serializable]public class ValidatorCollection :   ICollection, IList, IEnumerable, ICloneable {  // CollectionGen implementation  ...}

The possible implementation is more complete than what we need to solve specific problems, but the several hours it saves coding time allows me to appreciate some old ones.Knight Rider(Http://www.imdb.com/title/tt0083437/) plot.

Back to Top

Validatormanager

UnfortunatelyMichael KnightOrK. I .t. T.CannotValidatorcollectionMerge to the verification library, so we need to turn off the TV and do it on our own. At this point, we do have the enumeratedValidatorcollectionBut to ensure that it contains a list of all the verification programs that are running, we need to implement a mechanismValidatorcollectionAdd and delete so-called validators. We createdValidationmanagerTo meet this requirement:

public class ValidatorManager {  private static Hashtable _validators = new Hashtable();  public static void Register(BaseValidator validator, Form hostingForm) {    // Create form bucket if it doesn't exist    if( _validators[hostingForm] == null ) {      _validators[hostingForm] = new ValidatorCollection();    }    // Add this validator to the list of registered validators    ValidatorCollection validators =       (ValidatorCollection)_validators[hostingForm];    validators.Add(validator);  }  public static ValidatorCollection GetValidators(Form hostingForm) {    return (ValidatorCollection)_validators[hostingForm];  }  public static void DeRegister(BaseValidator validator,     Form hostingForm) {    // Remove this validator from the list of registered validators    ValidatorCollection validators =       (ValidatorCollection)_validators[hostingForm];    validators.Remove(validator);    // Remove form bucket if all validators on the form are de-registered    if( validators.Count == 0 ) _validators.Remove(hostingForm);  }}

Basically,ValidatormanagerUse _ValidatorsHash Tables are managed by one or moreValidatorcollectionList of instances. Each instance represents a set of validators hosted on a specific form. EachValidatorcollectionAre associated with a specific form and contain one or moreBasevalidators. Association is inBasevalidatorDirectionValidationmanagerWhen you register and deregister yourself, becauseRegisterMethod andDeregisterAll methods must beBasevalidatorAnd reference of the form that carries it. For a specific formValidatorcollectionYou canGetvalidatorsPassed a form reference and retrieved. The entire implementation is static (shared) to ensure memory access and simplify client code andValidatormanagerInstance management.

Back to Top

Another problem: Update basevalidator

RegisterAndDeregisterYou need to call it somewhere to make it all valid, and this place isBasevalidatorBecause this logic is common to all validators. Because basevalidator and its host form coexistRegisterAndDeregisterIs synchronized with the lifetime of the host form. Specifically, this is done by processingLoadAndClosedEvent implementation:

public abstract class BaseValidator : Component {  ...  private void Form_Load(object sender, EventArgs e) {    // Register with ValidatorManager    ValidatorManager.Register(this, (Form)sender);  }    private void Form_Closed(object sender, EventArgs e) {    // DeRegister from ValidatorCollection    ValidatorManager.DeRegister(this, (Form)sender);  }  ...}

The next step is to hook these event handlersLoadAndClosedEvent. The form we need isBasevalidatorOfControltovalidateHost form, and becauseControltovalidateIsControl, We can call itsFindformMethod to retrieve the host form. Unfortunately, we cannotBasevalidatorIs called in the constructorFindformBecause of itsControltovalidateAt that time, a form may not be allocated. This isWindows Form DesignerUseInitializecomponentTo store the results of code that builds a form and assigns controls to the parent container:

private void InitializeComponent() {  ...  // Create control instance  this.txtDOB = new System.Windows.Forms.TextBox();  ...  // Initialize control  //   // txtDOB  //   this.txtDOB.Location = new System.Drawing.Point(101, 37);  this.txtDOB.Name = "txtDOB";  this.txtDOB.Size = new System.Drawing.Size(167, 20);  this.txtDOB.TabIndex = 3;  this.txtDOB.Text = "";  ...  //   // cstmDOB  //   this.cstmDOB.ControlToValidate = this.txtDOB;  this.cstmDOB.ErrorMessage = "Employee must be 18 years old";  this.cstmDOB.Icon =     ((System.Drawing.Icon)(resources.GetObject("cstmDOB.Icon")));  this.cstmDOB.Validating +=     new CustomValidator.ValidatingEventHandler(this.cstmDOB_Validating);  //   // reqDOB  //   this.reqDOB.ControlToValidate = this.txtDOB;  this.reqDOB.ErrorMessage = "Date of Birth is required";  this.reqDOB.Icon =     ((System.Drawing.Icon)(resources.GetObject("reqDOB.Icon")));  this.reqDOB.InitialValue = "";  ...  //   // AddNewEmployeeForm  //   ...  // Add control to form and set control's Parent to this form  this.Controls.Add(this.txtDOB);  ...}

As you can see, the creation time of the control instance is much earlier than the time it is allocated to the form, and the latter is after the associated verification program, which makesFindformIs useless. In this case, you can turnSystem. componentmodel. isupportinitializeThrough the two defined methods (BegininitAndEndinit.Windows Forms designerUse reflection to determine whether the component is implementedIsupportinitializeIf yesBegininitAndEndinitAre insertedInitializecomponentBefore and after the form initialization. Because it can guaranteeEndinitInBasevalidatorOfControltovalidateAssign a parent control and call it fromFindformReturns a form.LoadAndClosedWhere the event is registered. The following code illustrates the implementation method:

public abstract class BaseValidator : Component, ISupportInitialize {  ...  #region ISupportInitialize  public void BeginInit() {}  public void EndInit() {    // Hook up ControlToValidate's parent form's Load and Closed events     // ...    Form host = _controlToValidate.FindForm();    if( (_controlToValidate != null) && (!DesignMode) &&         (host != null) ) {      host.Load += new EventHandler(Form_Load);      host.Closed += new EventHandler(Form_Closed);    }  }  #endregion  ...}

UpdatedInitializecomponentAs follows:

private void InitializeComponent() {  ...  // Call BaseValidator implementation's BeginInit implementation  ((System.ComponentModel.ISupportInitialize)(this.reqDOB)).BeginInit();  ...  // Control, component and form initialization  ...  // Call BaseValidator implementation's EndInit implementation  ((System.ComponentModel.ISupportInitialize)(this.reqDOB)).EndInit();}

You may want to know why I didn't deployIsupportinitialize. endinitAndDispose. BecauseValidatormanagerManage one or more hash operations performed by the parent formValidatorcollectionSo I want to ensure thatValidatorcollectionWhen the associated form is closedValidatormanagerInstead of waiting for garbage collection.

Back to Top

Enumerate validatorcollection

By creatingValidatorcollection,ValidatormanagerAnd updateBasevalidatorTo enable the requiredBasevalidatorThe registration mechanism required for enumeration. Figure 2 shows the internal representation of these parts in combination.

Figure 2 internal representation of validatormanager, validatorcollection, and basevalidator

To make full use of the updated design, we only needOKButtonClickSimple update of the event handler:

private void btnOK_Click(object sender, System.EventArgs e) {  // Better form wide validation  ValidatorCollection validators = ValidatorManager.GetValidators(this);  // Make sure all validate so UI visually reflects all validation issues  foreach( BaseValidator validator in validators ) {    validator.Validate();  }  foreach( BaseValidator validator in validators ) {    if( validator.IsValid == false ) {      MessageBox.Show("Form is invalid");      return;    }  }  DialogResult = DialogResult.OK; }

Since we don't have to write more and more code as we add more verification programs to the form, the code we get is much more refined than the code we originally compiled and supports scalability. This time, our code is not what my mother can write.

Back to Top

Declarative form range verification: formvalidator

If the goal is to write as few code as possible, we can further simplify the solution by restructuring it into a reusable and better model. ASP. NET is essentially usedSystem. Web. UI. Page(All asp. NET code hidden pages are derived from this type. Specifically,PageThe following verification-oriented members are implemented:

public class Page : TemplateControl, IHttpHandler {  ...  public virtual void Validate();  public bool IsValid { get; }  public ValidatorCollection Validators { get; }  ...}

We already haveValidatorcollection(In this way, the name is used to maintain consistency .)ValidateAndIsvalidThe result is equivalent to the verification logic implemented based on the form range enumeration. Unfortunately, althoughSystem. Windows. Forms. FormImplementedValidateBut it is associated with the Windows form native verification we use in the Custom library, rather than integration. Therefore, it makes sense to continue talking about one of the main topics in this article series. developers can drag and drop appropriate logic to reusable components on their forms as needed. The component used to verify the form can only be calledFormvalidator, It implementsValidateAndIsvalid, As shown below:

[ToolboxBitmap(typeof(FormValidator), "FormValidator.ico")]public class FormValidator : Component {  private Form _hostingForm = null;  ...   public Form HostingForm {...}  public bool IsValid {    get {      // Get validators for this form, if any      ValidatorCollection validators =         ValidatorManager.GetValidators(_hostingForm);      if( validators == null ) return true;      // Check validity      foreach(BaseValidator validator in validators) {        if( validator.IsValid == false ) return false;      }      return true;    }  }  public void Validate() {                // Get validators for this form, if any    ValidatorCollection validators =       ValidatorManager.GetValidators(_hostingForm);    if( validators == null ) return;    // Validate    Control firstInTabOrder = null;          foreach(BaseValidator validator in validators) {      validator.Validate();    }    }}

Besides implementationValidateAndIsvalidBesides,FormvalidatorYou have also implementedHostingformAttribute. Because ofParentAttribute orFindformThe host form is different in the method, and the component itself cannot determine its host form. Therefore, we need to adopt a design-time technique to achieve the same goal. This technique is displayed inHostingformAttribute. The magician will never disclose his tricks, but I am not a magician, and this is not my magic, so please study this technology freely and refer to the Chris sells 'book'sChapter 9. In the regenerateCustomvalidationProject andFormvalidatorAdd componentToolboxYou can simply drag the component to the form for use, as shown in 3.

Figure 3 use the formvalidator component

With the helpFormvalidator,OKButtonClickThe event handler is simplified to three lines of code:

private void btnOK_Click(object sender, System.EventArgs e) {  formValidator.Validate();  if( formValidator.IsValid ) DialogResult = DialogResult.OK;  else MessageBox.Show("Form not valid.");}

Figure 4 shows the running result.

Figure 4 running formvalidator

Although it is good to reduce the number of client code to three lines, it may be better to reduce the number to zero lines of code, especially when fully declarative form range verification is implemented. To achieve this goal,FormvalidatorYou need to implement the version corresponding to the above three lines of code, and at the appropriate time (when the formAcceptbuttonWhen clicked) run this version for us. FormAcceptbuttonAndCancelbuttonYou canProperty Browser, As shown in Figure 5.

Figure 5 specify the acceptbutton and cancelbutton OF THE FORM

This indicates that when you pressEnterKey, the specifiedAcceptbuttonQuiltClickWhen you pressESCKey, the specifiedCancelbuttonQuiltClick.FormvalidatorDetermineAcceptbuttonAnd then processClickEvent, which depends onInitializecomponentInternal settingsAcceptbutton. Therefore, we must implement it again.Isupportinitialize, As shown below:

public class FormValidator : Component, ISupportInitialize {  #region ISupportInitialize  public void BeginInit() {}  public void EndInit() {    if( (_hostingForm != null) ) {      Button acceptButton = (Button)_hostingForm.AcceptButton;      if( acceptButton != null ) {        acceptButton.Click += new EventHandler(AcceptButton_Click);      }    }  }  #endregion  private Form _hostingForm = null;  [Browsable(false)]  [DefaultValue(null)]  public Form HostingForm {...}  ...  private void AcceptButton_Click(object sender, System.EventArgs e) {    Validate();    if( IsValid ) _hostingForm.DialogResult = DialogResult.OK;    else MessageBox.Show("Form not valid.");  }  ...}
Back to Top

Verify by the tab key order

It is also useful for users to process the verification sequence in a visualized manner. Currently, formvalidator selects the first invalid control instead of the first control in a visualized order, which is the same as that specified by the tab key order. Figure 6 showsAdd new employeeThe correct Tab key order of the form.

Figure 6 specify the tab order

By pressing the tab key for verification, you can correct invalid fields from top to bottom on the form, which is more intuitive than the seemingly random method. To verify that the verification is performed in the tab order, you must update the verification as follows:Formvalidator:

[ToolboxBitmap(typeof(FormValidator), "FormValidator.ico")]public class FormValidator : Component {  ...   public Form HostingForm {...}  public bool IsValid {...}  public void Validate() {                // Validate all validators on this form, ensuring first invalid    // control (in tab order) is selected    Control firstInTabOrder = null;    ValidatorCollection validators =       ValidatorManager.GetValidators(_hostingForm);    foreach(BaseValidator validator in validators) {      // Validate control      validator.Validate();      // Record tab order if before current recorded tab order      if( !validator.IsValid ) {        if( (firstInTabOrder == null) ||             (firstInTabOrder.TabIndex >                validator.ControlToValidate.TabIndex) ) {          firstInTabOrder = validator.ControlToValidate;        }      }    }    // Select first invalid control in tab order, if any    if( firstInTabOrder != null ) firstInTabOrder.Focus();  }  }   

Figure 7 the result is displayed by focusing on the first invalid control in the order of the tab key.

Figure 7. Focus on the first invalid control (date of birth) in the tab order.

Download example

Our location

In this issue, based on the verification of each control established in the first phaseFormvalidatorThe format range is verified. According to the mode dialog box,FormvalidatorFully declarative form range verification is supported. Even so, we finally generated two extreme verification scopes:For each controlAndForm range. However, Windows Forms may contain tab controls with several tabs, each of which is loose or completely irrelevant, and each option card must be verified by itself. In this example, the Windows desktop Properties dialog box is used on each property tab.ApplyButton. In the above scheme, the unique verification of containers makes more sense. We will discuss this issue in the next and last phases of the topic article series. We will also extend the verification component library to enable it to display verification error summaries through basic implementations and scalable designs, allowing for further customization of digest Solutions

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.