Eric's Blog

Day to day experience in .NET
Welcome to Blogs @ IRM Sign in | Join | Help
 Search

Disclaimer

The content of this site is my own personal opinion and does not in any way represent my employer, it's subsideries or affiliates. These postings are provided "AS IS" with no warranties, and confer no rights.

This Blog

The Beauty of yield return

Today I debugged a bug that started to show up in a component that I developed quite a while ago, but haven't been exploited before. The component is a Windows Forms extension control that performs basic validation; much like the validators in ASP.NET.
It is possible to data bind the component and when you do it tries to automatically set up some validations depending on the data source. To be able to do this it enumerates the Bindings collection of the CurrencyManager which it gets from the data source.
 
//Set up errorManager
if ((this.parentControl != null) && ((this.dataSource != null) && (this.parentControl.BindingContext != null)))
{
    this.errorManager = (System.Windows.Forms.CurrencyManager)this.parentControl.BindingContext[this.dataSource, this.dataMember];
}
foreach (Binding binding in this.errorManager.Bindings)
{
    item = EnsureControlItem(binding.Control);
    item.AutoSetup(binding);
}
 
Now the bug that showed up was when this extension control was used on two user controls, each showing different parts of the underlying data source. This caused the validation control two hook up events once for each instance (two totally) and therefore validated to ok in one instance and not ok in the other. This by design, because the validation properties are set per control and therefore no validation was set up in the user control that did not display that data. The result of this was that if the validation first failed in UserControl1 and then succeeded in UserControl2, the last won and cleared the errors that was set up in the first failed validation.
 
The solution to the problem would be to only hook up the validation for the instance of the validation control that has the same parentControl as the control for the binding. This is where yield return enters the stage:
 
private IEnumerable<Binding> GetBindingsWithinSameContainer()
{
    foreach (Binding binding in this.errorManager.Bindings)
    {
        if (GetParent(binding.Control) == this.parentControl)
            yield return binding;
    }
}
 
/// <summary>
/// Gets the form or user control that hosts the control.
/// </summary>
/// <remarks>
/// This method will continue to check the Parent property until it finds a form or a user control, which is different
/// than the <see cref="Control.Parent"/> because it will continue up the hierarchy if the control is on a tab for example.
/// </remarks>
private static ContainerControl GetParent(Control control)
{
    if (control == null || control.Parent == null)
        return null;
 
    if (control.Parent is Form || control.Parent is UserControl)
        return control.Parent as ContainerControl;
    else
        return GetParent(control.Parent);
}
 
In the code above I first check if the control associated with the Binding object is on the same form or user control as the validation control, and only in this case I call yield return. This makes it easy for me to use my existing foreach code in the other places with just a small change:
 
foreach (Binding binding in GetBindingsWithinSameContainer())
{
    item = EnsureControlItem(binding.Control);
    item.AutoSetup(binding);
}
 
Published den 26 mars 2006 12:59 by ericqu
Filed under:

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS

Comments

No Comments

Leave a Comment

(required) 
(optional)
(required) 
Submit
Powered by Community Server, by Telligent Systems