Communicating between custom controls: Building parent/child controls

After release of custom controls feature in SP1 beta, few asked me how to build dynamic custom controls: For example, state and city controls like below where city gets loaded based on state’s value. Another variation of it is one or more controls show up (city for example) based on another control’s selection. In these cases, the controls are independent custom controls and the question is how to achieve this dependency. I will explain few ways to do it below.

 

In this simple state/city example, it can be actually built without custom controls using FieldControl and “When” rules in workitemtype xml. Custom controls are needed a) if the data is so huge or comes from another datasource, or b) if the data should be stored somewhere else other than in work item fields. Basically I would suggest first considering the built-in functionality (global lists work great for often changing or larger data), and then consider custom controls – it reduces complexity and gives more rules support for free. If you must use custom controls, you can read on.

Loading data in controls when other controls change:

Using above example, two custom fields CityName and StateName are associated with two custom controls and when state is selected by user, the city control needs to be notified of that event. From city control, the reference for state control is not provided; instead the communication needs to happen through the workitem fields. One way to achieve this is to force Invalidate for city control by changing value of CityName field whenever state is selected. For example, state control could set “invalid” as value for cityname field whenever state changes. This causes InvalidateDatesource call to city control and it can look at StateName field to load appropriate data. Important: The city control needs to clear the CityName field (or set to a valid value) so that it gets invalidate on another state change – and to avoid infinite recursion, BeforeUpdateDatasource and AfterUpdateDatasource events should be raised before & after resetting that value. Those events make sure Invalidate is not called on the control changing value itself.

Another elegant solution to avoid “setting invalid value trick” is to use FieldChanged event, but much care to be taken to manage lifetime of objects. Basically the custom control gets reference to WorkItem object and it can hook to its FieldChanged event when WorkItemDatasource property is set.

m_workItem.FieldChanged += new WorkItemFieldChangeEventHandler(WorkItem_FieldChanged);

Inside the event for city control, load city values only when state is changed as below example:

        void WorkItem_FieldChanged(object sender, WorkItemEventArgs e)

        {

  Debug.Assert(this.IsDisposed == false);

        if (!this.IsDisposed && e.Field != null

                && e.Field.ReferenceName.Equals("Company.StateName", StringComparison.OrdinalIgnoreCase))

            {

                string stateName = e.Field.Value.ToString();

                // Clear city control and load cities for given state here

            }

        }

Important: The event should be unhooked in the dispose of the control & whenever WorkItemDataSource changes – otherwise the control will keep receiving events even after the form is closed, and control will not be released from memory until workitem object is released causing potential memory leak. See here for more info.

Making controls visible when other controls change:

Using above approach, city control could be made to appear only when some value is selected in state control. It can be achieved by setting “this.visible = false” in City control initially, and then setting visibility to true when a state value is selected. Or the control paints differently based on parent control’s selection to simulate dynamic form and workflow. In this case the controls should have initial size etc so that the workitem form allocates enough space for all controls that could dynamically appear. Remember that the label will not show/hide dynamically so either have no label or have your own label in the usercontrol itself.

Obviously this could be limiting if you truly want to change form layout drastically based on user selection. There isn’t a way to dynamically add/remove controls in workitemform. In that case, one option is to build a large control that takes up certain area and within that control adding/removing children controls and associating whole control with one or no field name.

 

<%//This posting is provided "AS IS" with no warranties of any kind and confers no rights %>