Showing posts with label controls. Show all posts
Showing posts with label controls. Show all posts

Wednesday, October 10, 2007

LINQ to Controls (Validation)

For my next trick... I'll tack in a validator object into controls in my form and then call that object in a LINQ query to tell me if the input is valid. This is not the best way to accomplish this specific task, but I'm trying to keep my examples fairly simple and this should be easy to grok.

First, I create a class that can call a validation delegate.


class InputValidator
{
public delegate bool ValidationDelegate( Control ctrl );
private ValidationDelegate validateCode;
private Control _ctrl;
public InputValidator( Control ctrl, ValidationDelegate validate )
{
_ctrl = ctrl;
validateCode = validate;
}
public bool IsValid()
{
return validateCode( _ctrl );
}
}


Then, in the form initialization section, I'd assign the code to do the validation to the Tag property. If I were really wanting to creating an elegant validation solution that went beyond the Validating event of Control, I'd probably create extension methods/properties and tack them onto Control rather than using the Tag property. But let's keep our scope small.


textBox1.Tag = new InputValidator( textBox1,
delegate(Control ctrl) { return ctrl.Text.Length >= 5; } );


As delegates, the validation code can either be written for each control using anonymous methods, or shared between controls by creating a method with the correct signature and passing in that named delegate.

So, for the query, we can query all the controls on the form, get the ones where the Tag object is of the proper type, then cast the tag object and call the validation code. In the following query, we are returning all controls that are invalid.


var InvalidControls = from ctrl in Controls.Cast<Control>()
where ctrl.Tag != null
&& ctrl.Tag.GetType() == typeof( InputValidator )
&& !( (InputValidator)textBox1.Tag ).IsValid()
select ctrl;


Once you have the query, just foreach over the IEnumerable<Control> list and perform coloring or whatever operations you want. You may want to just have the LINQ query returned from a function that can be called from many places.

LINQ to Controls

In a perfect world, we'd be able to do this on a form:


var disabled = Controls.Select( c => !c.Enabled );


However, the Controls collection isn't one that is natively supported by LINQ. MS developed the custom collection of ControlCollection rather than using a generic (since generics didn't exist at the time).

There's a way around the problem. Since the generics namespace adds the "Cast" extension method when it sees the "IEnumerable" interface, we can just cast the list objects as Control. Once you've done that, then you just do your normal LINQ queries.


var disabled = Controls.Cast<Control>().Select( c => !c.Enabled );


Or the more SQL-esque way:


var invalid = from cont in Controls.Cast<Control>()
where cont.Text == "" && cont.Visible
select cont;

Tuesday, October 9, 2007

LINQ to Textbox


var lines = textbox1.Lines.Select( l => l.Contains( "the" ) );


The point of this post is that LINQ is everywhere. Use it on whatever you want. The above shows the "Select" that LINQ adds to collections, arrays, etc when you include the System.Linq namespace. Below is the more SQL-like query syntax, with an added condition and a trim. It returns all the lines that have "the" in it.


var lines = from line in textbox1.Lines
where line.Contains( "the" ) && line.Length > 10
select line.Trim();



I wrote a simple application that has two large text boxes and a small text box. The example program only has 1 statement, which is the following.


txtResult.Lines = (from line in txtToSearch.Lines
where line.Contains(txtSearchString.Text)
select line).ToArray();


I get bug reports in large text files (which include historical bug data, call stack, loaded modules, etc) automatically emailed to me from our application. I can paste the bug report into the txtToSearch control, type in "up time" into the single line txtSearchString control, and the results are put into the multi-line txtResult control and it will pull out all the lines that have either system or program up times. LINQ is great.