How do you bind your combo boxes?

How do you bind your combo boxes?

Old forum URL: forums.lhotka.net/forums/t/8653.aspx


tiago posted on Friday, March 12, 2010

Part I

In the begining I did what I suppose is the usual.

My business objects exposed a StatusID property and a StatusNVL decoded the ID into a proper name.

In the .Designer.cs

this.StatusComboBox.DataBindings.Add(new System.Windows.Forms.Binding("SelectedValue", this.ObjBindingSource, "StatusID", true));
this.StatusComboBox.DataSource = this.StatusNVLBindingSource;
this.StatusComboBox.DisplayMember = "Value";
this.StatusComboBox.ValueMember = "Key";

and also

this.StatusNVLBindingSource.DataSource = typeof(Store.BO.StatusNVL);
this.BindingSourceRefresh.SetReadValuesOnChange(this.StatusNVLBindingSource, true);

and in the code file

public ObjEdit(Obj obj)
{
    InitializeComponent();
    // store object reference
    ThisObj = obj; // obj is the business object
    StatusNVLBindingSource.DataSource = StatusNVL.GetNVL();
    ...
}

private void BindUI(bool rebinding)
{
    ThisObj.BeginEdit();
    ObjBindingSource.DataSource = ThisObj;
    StatusComboBox.SelectedValue = ThisObj.StatusID;
    ...
}

and that was it.

This solution is just fine. Your users drop down a list and click on one of the items of the drop down. Just like the combo box example you can see on PTracker.

Now let's add auto complete. And add also an ErrorProvider so you can tell your users what's wrong.

In the .Designer.cs file add

this.StatusComboBox.AutoCompleteMode = System.Windows.Forms.AutoCompleteMode.SuggestAppend;
this.StatusComboBox.AutoCompleteSource = System.Windows.Forms.AutoCompleteSource.ListItems;

Now your troubles begin... as your users insist on doing the wrong things:
- they type whatever they want and that doesn't match any name in the NameValueList
- they leave the combo box empty and that doesn't match any name in the NVL for sure

Worst: the ErrorProvider doesn't show any error at all. Of course you can handle a couple of combobox events and trigger some error to show up with the nice red mark. Or you can create an extra Binding that handles Format and Parse events. But then again, wasn't this all about keeping it simple and writing less code?

Part II

So I went the other road. Although storing a private _statusID fields, my business object no longer exposes the status ID but just a Status property that is the decoded status name.

So the .Designer.cs file looks like this

this.StatusComboBox.AutoCompleteMode = System.Windows.Forms.AutoCompleteMode.SuggestAppend;
this.StatusComboBox.AutoCompleteSource = System.Windows.Forms.AutoCompleteSource.ListItems;
// notice "Text" instead of "SelectedValue" and "Status" instead of "StatusID"
this.StatusComboBox.DataBindings.Add(new System.Windows.Forms.Binding("Text", this.ObjBindingSource, "Status", true));
this.StatusComboBox.DataSource = this.StatusNVLBindingSource;
this.StatusComboBox.DisplayMember = "Value";
this.StatusComboBox.ValueMember = "Key";

and also

this.StatusNVLBindingSource.DataSource = typeof(Store.BO.StatusNVL);
this.BindingSourceRefresh.SetReadValuesOnChange(this.StatusNVLBindingSource, true);

and the code file looks like this

public ObjEdit(Obj obj)
{
    InitializeComponent();
    // store object reference
    ThisObj = obj; // obj is the business object
    StatusNVLBindingSource.DataSource = StatusNVL.GetNVL();
    ...
}

private void BindUI(bool rebinding)
{
    ThisObj.BeginEdit();
    ObjBindingSource.DataSource = ThisObj;
    // notice Text instead of SelectedValue and Status instead of StatusID
    StatusComboBox.Text = ThisObj.Status;
    ...
}

But what about the red thing of the error provider? The question is all about validation. And that's a BO thing. So let's write a NameValueList validator that checks if the Status name converts to an existing StatusID. If it doesn't, just return false with the appropriate message and your ErrorProvider will show the red bullet.

using System;
using Csla;
using Csla.Core;
using Csla.Validation;

namespace Store.BO
{
    /// <summary>
    /// Implements NameValueList business rules.
    /// </summary>
    public static class NameValueListRules
    {

        /// <summary>
        /// Rule ensuring a name exists on a NameValueList.
        /// </summary>
        /// <param name="target">Object containing the data to validate</param>
        /// <param name="e">Arguments parameter specifying the name of the
        /// property and the Value name to validates.</param>
        /// <returns><see langword="false" /> if the rule is broken</returns>
        /// <remarks>
        /// This implementation uses late binding, and will only work
        /// against a NameValueList where Key is of type int and Value is of type string.
        /// The property to validate must be the Value member of the NameValueList.
        /// </remarks>
        public static bool NameValidation(object target, RuleArgs e)
        {
            var args = (DecoratedRuleArgs)e;
            var nameValueList = (NameValueListBase<Int32, String>)args["NameValueList"];

            var value = (string)Utilities.CallByName(target, e.PropertyName, CallType.Get);

            if (nameValueList.Key(value) > 0)
                return true;

            e.Description = String.Format(Properties.Resources.ValidNameRequired, RuleArgs.GetPropertyName(e));
            return false;
     
        }

        /// <summary>
        /// Custom <see cref="RuleArgs" /> object required by the
        /// <see cref="NameValidation" /> rule method.
        /// </summary>
        public class NameRuleArgs : DecoratedRuleArgs
        {
            /// <summary>
            /// Get the NameValueList to be used on the validation..
            /// </summary>
            public NameValueListBase<Int32, String> NameValueList
            {
                get { return (NameValueListBase<Int32, String>) this["NameValueList"]; }
            }

            /// <summary>
            /// Create a new object.
            /// </summary>
            /// <param name="propertyName">Name of the property to validate.</param>
            /// <param name="nameValueList">NameValueList to be used on the validation.</param>
            public NameRuleArgs(string propertyName, NameValueListBase<Int32, String> nameValueList)
                : base(propertyName)
            {
                this["NameValueList"] = nameValueList;
            }

            /// <summary>
            /// Create a new object.
            /// </summary>
            /// <param name="propertyInfo">PropertyInfo for the property to validate.</param>
            /// <param name="nameValueList">NameValueList to be used on the validation.</param>
            public NameRuleArgs(IPropertyInfo propertyInfo, NameValueListBase<Int32, String> nameValueList)
                : base(propertyInfo)
            {
                this["NameValueList"] = nameValueList;
            }

            /// <summary>
            /// Create a new object.
            /// </summary>
            /// <param name="propertyName">Name of the property to validate.</param>
            /// <param name="friendlyName">A friendly name for the property, which
            /// will be used in place of the property name when
            /// creating the broken rule description string.</param>
            /// <param name="nameValueList">NameValueList to be used on the validation.</param>
            public NameRuleArgs(string propertyName, string friendlyName, NameValueListBase<Int32, String> nameValueList)
                : base(propertyName, friendlyName)
            {
                this["NameValueList"] = nameValueList;
            }
        }
    }
}

Using this validator goes like this:

protected override void AddBusinessRules()
{
    ValidationRules.AddRule<Obj>(NameValueListRules.NameValidation,
        new NameValueListRules.NameRuleArgs(StatusIDProperty, StatusNVL.GetNVL()));
}

And the part where the BO converts the Status property into a _statusID private field (that is stored in the database)?

It goes like this:

a) in the old CSLA 3.0.5 syntax

private int _statusID;
public string Status
{
    get
    {
        CanReadProperty("Status", true);
        return StatusNVL.GetNVL().Value(_statusID);
    }
    set
    {
        CanWriteProperty("Status", true);
        var convertedValue = StatusNVL.GetNVL().Key(value);
        if (_statusID != convertedValue)
        {
            _statusID = convertedValue;
            PropertyHasChanged("Status");
        }
    }
}

b) using the latest syntax

private static readonly PropertyInfo<int> StatusIDProperty = RegisterProperty(new PropertyInfo<int>("Status", "status", -1));
public string Status
{
    get { return StatusNVL.GetNVL().Value(GetProperty(StatusIDProperty)); }
    set { SetPropertyConvert(StatusIDProperty, StatusNVL.GetNVL().Key(value)); }
}

Note - You can use the included code as you want -.copy, sell, resell, lend or even rent.

Copyright (c) Marimer LLC