Inputvalidering i Windows Forms Applikationer Rolf Therkildsen (6. nov 2008) Krav til inputvalidering Brugeren må aldrig blive forhindret i at flytte til en anden kontrol. Brugeren skal informeres om fejl via ErrorProvider kontrollen. Generelt Lige et eksempel vi kan snakke om (to tekstbokse, en knap er en errorprovider): En textbox har et Validating event, med dette kan vi samme med en errroprovider checke input: private void txtname_validating(object sender, CancelEventArgs e) ValidateName(); private bool ValidateName() if (!Customer.isNameValid(txtName.Text)) errorprovider1.seterror(txtname, "Please type a name."); return false; errorprovider1.seterror(txtname, ""); return true; Der er to problemer med dette. Eventet bliver ikke kaldt hvis ikke brugeren har været i feltet, og selvom der er fejl, bliver koden bag OK-knappen stadig kørt. For at løse dette bliver man, som det første koden bag OK-knappen, nød til at teste at alt er ok. private void button1_click(object sender, System.EventArgs e) bool validname = ValidateName(); bool validage = ValidateAge(); if (validname && validage) MessageBox.Show("Ok"); MessageBox.Show("Error");
Dette er fjollet. I stedet for dette så lad os spørge errorprovideren i stedet. Desværre ved den ikke om der er nogen fejl på siden. Men det gør vi noget ved. En kontrol kalder Evaluate-eventet når den midste focus. Så hvis vi sætter fokus til alle kontroller og derefter sætter fokus et andet sted sker der en evaluering. ErrrorProvideren kan spørges om en kontrol har fejlet. Så hvis man løber alle kontroller igennem ved man også om der er fejl på formen. Dette har jeg samlet i classe, som nedarver fra ErrorProvider. Download fra http://www.rolft.dk/programs/inputvalidation/inputvalidation.zip. Man kan nu gøre således: private void button1_click(object sender, EventArgs e) if (extendederrorprovider1.isvalid()) MessageBox.Show("Ok"); MessageBox.Show("Error"); En mindre krølle er kontroller, som selv viser fejlmeddelr. F.esk. DataGridView:
Disse fortæller jo ikke ErrorProviderren om deres eventuelle fejl. Dette løses ved at implementerer Validation eventet på controllen alligevel og kalde SetHiddenError, som er en funktion som jeg har tilføjet til ErrorProvideren. Funktionen gemmer bare fejlen i intern liste. private void dgvcustomers_validating(object sender, CancelEventArgs e) foreach (DataGridViewRow row in dgvcustomers.rows) foreach (DataGridViewCell cell in row.cells) if (cell.errortext.length == 0) extendederrorprovider1.sethiddenerror(dgvcustomers, "Some values in the grid has errors."); return; extendederrorprovider1.sethiddenerror(dgvcustomers, ""); Inputvalidering i DataGridView Hvis man har en regel for, hvad brugeren må indtaste i en celle, så checkes dette bedst via CellValidating eventet på grid et og brug cellens ErrorText til at marker, hvis der er fejl. F.eks.: private void grid_cellvalidating(object sender, DataGridViewCellValidatingEventArgs e) DataGridView grid = (DataGridView) sender; if (e.columnindex==1) if (!Customer.isNameValid((string)e.FormattedValue)) grid.rows[e.rowindex].cells[e.columnindex].errortext = "Wrong name"; grid.rows[e.rowindex].cells[e.columnindex].errortext = ""; Hvis et check indeholder vædier fra flere kolonner, så brug RowValidating i stedet. Hvis ovenstående skal virke, kræver det dog at det databærende objekt kan indeholde den fejlagtige værdi. Indtaster man f.eks. et bogstav ind i id-kolonnen, som er af typen int, får man en DataGridView Default Error
Dialog op med et staktrace. Samtidig kan man ikke forlade feltet, før man har tastet en rigtig værdi. Dette er ikke en acceptabel opførsel. Dette løses via de to CellParsing og CellFormatting events. I CellParsing eventet prøver man at lave konverteringen. Hvis dette går galt, gemmer man fejlinputtet i et 2D array (her kaldet inputwitherror), sætter ErrorText på cellen og gemmer den tidligere værdi i det underliggende databærende objekt: private void grid_cellparsing(object sender, DataGridViewCellParsingEventArgs e) try //Try to convert e.value = Convert.ChangeType(e.Value, e.desiredtype); Rows[e.RowIndex].Cells[e.ColumnIndex].ErrorText = ""; inputwitherror.delete(e.columnindex, e.rowindex); catch (FormatException exp) //Save the input for later display inputwitherror.put(e.columnindex, e.rowindex, e.value); //Put the existing value into the underlaying data container e.value = Rows[e.RowIndex].Cells[e.ColumnIndex].Value; Rows[e.RowIndex].Cells[e.ColumnIndex].ErrorText = exp.message; e.parsingapplied = true; I CellFormatting kan vi nu sørge for, at fejlinputtet stadig bliver præsenteret i stedet for, hvad der nu ligger i det underliggende databærende objekt. private void grid_cellformatting(object sender, DataGridViewCellFormattingEventArgs e) if (inputwitherror.has(e.columnindex, e.rowindex)) e.formattingapplied = true; e.value = inputwitherror.get(e.columnindex, e.rowindex);
Jeg har lavet en klasse som nedarver fra DataGrivView som implementerer ovenstående. Dette kan findes på http://www.rolft.dk/programs/inputvalidation/inputvalidation.zip. Det går dog stadig galt, hvis det databærende objekt smider en exception (andre end FormatException). F.eks.: public string Name get return name; set if (!isnamevalid(value)) throw new ArgumentException("Name is invalid", Name); name = value; Men det må du selv overveje. Begræns input til tekstbokse En gang imellem er det nemmere at sikre, at brugeren ikke taster noget forkert, end at skulle tjekke senere. Ønsker man f.eks. et positivtheltal fra brugeren, er der ikke nogen grund til, at han kan taste bogstaver ind i tekstboksen. Dette kan gøres ved at abonere på tekstboksens KeyDown event. F.eks.: void NumberTextBox_KeyDown(object sender, KeyEventArgs e) //Supress all key strokes e.suppresskeypress = true; if ((e.keycode >= Keys.NumPad0 && e.keycode <= Keys.NumPad9) (e.keycode >= Keys.D0 && e.keycode <= Keys.D9)) //Except 0 to 9 e.suppresskeypress = false; if (e.keycode == Keys.Back e.keycode == Keys.Delete) //Except delete e.suppresskeypress = false; if (e.keycode == Keys.Left e.keycode == Keys.Right e.keycode == Keys.End e.keycode == Keys.Home)
//Except moving keys e.suppresskeypress = false; Jeg har lavte et eksempe på dette kaldet en NumberTextBox. Den nedarver fra TextBox men begrænser input til hel- eller decimaltal.