Implementing a Data-Bound Control Designer


If you've used one of the ASP.NET data-bound controls in Visual Studio .NET, you've seen how design-time features greatly enhance the usability of a data-bound control in a visual design environment. In this section, we'll implement the DataBoundTableDesigner designer, which demonstrates the key design-time features that a designer can provide to a data-bound control.

Figure 16-2 shows the DataBoundTable control used in a Web Forms page in Visual Studio .NET. The designer shadows the DataSource and DataMember properties. Shadowing refers to the process of replacing properties or events with corresponding design-time implementations . The design-time DataSource and DataMember properties display drop-down lists that respectively contain the data sources and data members that the control can bind to on the design surface. The designer dynamically generates the values in the list. For example, the drop-down list for the DataSource property in Figure 16-2 contains "dataSet1" and "dataTable1" . The string "dataSet1" corresponds to the dataSet1 component in the component tray, and "dataTable1" corresponds to the first DataTable in dataSet1 . DataSet and DataTable implement the IListSource interface, which is a type that the DataBoundTable allows for its DataSource property.

Figure 16-2. The DataBoundTable control used in a Web Forms page. The DataBoundTableDesigner is associated with the control.

graphics/f16hn02.jpg

Another feature of the data-bound control designer is that it creates a sample data source at design time, which differs from the run-time data source. If you examine the Web Forms page that generated Figure 16-2, you will see that the data source ( dataTable1 ) for the control does not contain any data, yet the control displays data on the design surface. The sample data source is created by the designer and is useful when the real data source is populated with data at run time only. The designer-generated .aspx page and the corresponding code-behind file are included in the sample files for this book.

Listing 16-4 contains the code for the DataBoundTableDesigner class. The designer performs the following tasks , which we'll explain after the code listing:

  • Defines design-time DataMember and DataSource properties, which are associated with design-time type converters that will display drop-down lists of values in the property browser.

  • Overrides the PreFilterProperties method to replace the properties of the DataBoundTable control instance with corresponding design-time properties.

  • Overrides the GetDesignTimeHtml method to create a sample data source to bind to the control and generates HTML for the control by using that data source.

  • Implements the IDataSourceProvider interface.

    Listing 16-4 DataBoundTableDesigner.cs
     usingSystem; usingSystem.Collections; usingSystem.ComponentModel; usingSystem.ComponentModel.Design; usingSystem.Data; usingSystem.Diagnostics; usingSystem.Web.UI; usingSystem.Web.UI.Design; usingSystem.Web.UI.WebControls; usingMSPress.ServerControls; usingAttributeCollection=System.ComponentModel.AttributeCollection; namespaceMSPress.ServerControls.Design{ publicclassDataBoundTableDesigner:ControlDesigner, IDataSourceProvider{ privateDataTable_dummyDataTable; privateDataTable_designTimeDataTable; publicstringDataMember{ get{ return((DataBoundTable)Component).DataMember; } set{ ((DataBoundTable)Component).DataMember=value; OnDataSourceChanged(); } } 
     publicstringDataSource{ get{ DataBindingbinding=DataBindings["DataSource"]; if(binding!=null){ returnbinding.Expression; } returnString.Empty; } set{ if((value==null)(value.Length==0)){ DataBindings.Remove("DataSource"); } else{ DataBindingbinding=DataBindings["DataSource"]; if(binding==null){ binding=newDataBinding("DataSource", typeof(object),value); } else{ binding.Expression=value; } DataBindings.Add(binding); } OnDataSourceChanged(); OnBindingsCollectionChanged("DataSource"); } } publicoverrideboolDesignTimeHtmlRequiresLoadComplete{ get{ //Ifthereisadatasource,lookitupinthe //containerandrequirethedocumenttobeloaded //completely. return(DataSource.Length!=0); } } privateIEnumerableGetDesignTimeDataSource(intminimumRows){ IEnumerableselectedDataSource= ((IDataSourceProvider) this).GetResolvedSelectedDataSource(); DataTabledataTable=_designTimeDataTable; //Ifpossible,usethedatatablecorrespondingtothe //selecteddatasource. 
     if(dataTable==null){ if(selectedDataSource!=null){ _designTimeDataTable= DesignTimeData.CreateSampleDataTable(selectedDataSource); dataTable=_designTimeDataTable; } if(dataTable==null){ //Fallbackonadummydatasourceifitisn't //possibletocreateasampledatatable. if(_dummyDataTable==null){ _dummyDataTable= DesignTimeData.CreateDummyDataTable(); } dataTable=_dummyDataTable; } } IEnumerableliveDataSource= DesignTimeData.GetDesignTimeDataSource(dataTable, minimumRows); returnliveDataSource; } publicoverridestringGetDesignTimeHtml(){ DataBoundTabledbt=(DataBoundTable)Component; stringdesignTimeHTML=null; IEnumerabledesignTimeDataSource= GetDesignTimeDataSource(5); try{ dbt.DataSource=designTimeDataSource; dbt.DataBind(); designTimeHTML=base.GetDesignTimeHtml(); } catch(Exceptione){ designTimeHTML=GetErrorDesignTimeHtml(e); } finally{ dbt.DataSource=null; } returndesignTimeHTML; } 
     protectedoverridestringGetErrorDesignTimeHtml(Exceptione){ returnCreatePlaceHolderDesignTimeHtml("TherewasanerrorrenderingtheDataBoundTable."); } publicoverridevoidInitialize(IComponentcomponent){ if(!(componentisDataBoundTable)){ thrownewArgumentException("ComponentmustbeaDataBoundTable","component"); } base.Initialize(component); } protectedinternalvirtualvoidOnDataSourceChanged(){ _designTimeDataTable=null; } protectedoverride voidPreFilterProperties(IDictionaryproperties){ base.PreFilterProperties(properties); PropertyDescriptorprop; prop=(PropertyDescriptor)properties["DataSource"]; Debug.Assert(prop!=null); //YoucannotcreatethedesignerDataSourceproperty //basedontherun-timepropertybecausetheirtypesdo //notmatch.Therefore,youmustcopyalltheattributes //fromtherun-timeproperty. AttributeCollectionruntimeAttributes=prop.Attributes; Attribute[]attrs= newAttribute[runtimeAttributes.Count+1]; runtimeAttributes.CopyTo(attrs,0); attrs[runtimeAttributes.Count]= newTypeConverterAttribute(typeof(DataSourceConverter)); prop=TypeDescriptor.CreateProperty(this.GetType(), "DataSource",typeof(string),attrs); properties["DataSource"]=prop; prop=(PropertyDescriptor)properties["DataMember"]; Debug.Assert(prop!=null); prop=TypeDescriptor.CreateProperty(this.GetType(),prop, newAttribute[]{ newTypeConverterAttribute(typeof(DataMemberConverter)) }); 
     properties["DataMember"]=prop; } #regionImplementationofIDataSourceProvider objectIDataSourceProvider.GetSelectedDataSource(){ objectselectedDataSource=null; DataBindingbinding=DataBindings["DataSource"]; if(binding!=null){ selectedDataSource= DesignTimeData.GetSelectedDataSource(Component, binding.Expression); } returnselectedDataSource; } IEnumerableIDataSourceProvider.GetResolvedSelectedDataSource(){ IEnumerableselectedDataSource=null; DataBindingbinding=DataBindings["DataSource"]; if(binding!=null){ selectedDataSource= DesignTimeData.GetSelectedDataSource(Component, binding.Expression,DataMember); } returnselectedDataSource; } #endregion } } 

Let's look at how DataBoundTableDesigner defines the design-time DataMember and DataSource properties. The design-time DataMember property delegates to the corresponding run-time property. The setter of DataMember calls the OnDataSourceChanged method to inform the designer that the data source has changed. The design-time DataSource property is a String ; however, the run-time property is an Object . This enables the property setter to persist the data source name selected by the page developer, such as "dataSet1" , as a data-binding expression. The setter of the design-time DataSource property adds the data-binding expression corresponding to the data source selection to the design-time DataBindings property. The setter also invokes the OnDataSourceChanged and OnBindingsCollectionChanged methods to inform the designer that a data-binding expression has changed. As a result, the code persister generates the data-binding expression of the form "<%# dataSet1 %>" as the value of the DataSource property in the .aspx page. At run time, when the page is parsed, the data-binding expression causes the page parser to create an event handler (for the control's DataBinding event) that assigns the dataSet1 object to the DataSource property. This event handler is invoked when the page developer's code invokes the DataBind method of the control.

Now let's examine how DataBoundTableDesigner causes the property browser to display a drop-down list of dynamically generated values for the DataMember and DataSource properties. Any logic to shadow properties must be implemented in the PreFilterProperties method. DataBoundTable ­Designer overrides this method to replace the DataMember and DataSource run-time properties with the corresponding design-time properties. In the PreFilterProperties method, DataBoundTableDesigner associates the System.Web.UI.Design.DataMemberConverter and System.Web.UI.Design.DataSourceConverter type converters with the design-time DataMember and DataSource properties. Each of these type converters implements the logic to examine the objects on the design surface and to dynamically generate a list of valid values for the respective property.

DataBoundTableDesigner overrides the GetDesignTimeHtml method to use a sample data source to generate HTML for the DataBoundTable control at design time. GetDesignTimeHtml invokes the helper GetDesignTimeDataSource method to perform the task of creating the sample table. The GetDesignTimeDataSource method in turn utilizes the methods of the System.Web.UI.Design.DesignTimeData helper class, which encapsulates the logic to create a design-time data source that mimics the schema of the selected data source but contains sample data. In its implementation of the GetDesignTimeHtml , DataBoundTableDesigner assigns the sample data source to the DataSource property of the DataBoundTable instance and invokes the DataBind method on the DataBoundTable instance. This causes the control to generate its control hierarchy by using the sample data source the same way as it does at run time by using a real data source. DataBoundTableDesigner then generates the HTML to render by invoking the GetDesignTimeHtml method of the base class.

A designer for a data-bound control should implement the System.Web.UI.Design.IDataSourceProvider interface. IDataSourceProvider represents the standard contract for any design-time component to retrieve the data source selected by the page developer for a particular data-bound control. In the DataBoundTableDesigner example, the DataMemberConverter class dynamically generates a list of values for the DataMember property by utilizing a method of the IDataSourceProvider interface. The IDataSourceProvider interface has two methods ” GetSelectedDataSource and GetResolvedSelectedDataSource . GetDataSource uses the string value of the design-time DataSource property and returns the object that represents the data source. When the data source is an IListSource instance, GetResolvedSelectedDataSource returns the actual IEnumerable type to bind to. DataBoundTableDesigner implements the methods of the IDataSourceProvider interface by using the overloaded Get ­SelectedDataSource methods of the DesignTimeData helper class.



Developing Microsoft ASP. NET Server Controls and Components
Developing Microsoft ASP.NET Server Controls and Components (Pro-Developer)
ISBN: 0735615829
EAN: 2147483647
Year: 2005
Pages: 183

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net