|
|||||||||||||||||||||
|
|||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionOver the past few months I have been creating a variety of WebControls some of which have needed access to a
DataSource such as a DataSet in order to work properly. Adding a DataSource to your WebControl is relatively easy
however the tricky part is when you need to interact with that property in the Properties window e.g. in the same manner
as you would for a DropDownList control etc. Now if you are lucky and you get the query right in MSDN you will come across the following article
Implementing a Web Forms Data-Bound Control Designer. However
you will notice like I did that the sample just does not work as advertised. While attempting to debug it I posted a message on the
microsoft.public.dotnet.framework.aspnet.webcontrols
newsgroup and I received the following answer from Mike Moore "DataSource property and DataBinding thread"
which eventhough was not 100% what I wanted put me on the right track. However an interesting comment, see the supplied source for the
What I intend to do is explain each step of the process and describe what you actually need to do support data binding in a WebControl against what the MSDN sample says you have to do. For this demonstration I created a very simple control that does nothing other than expose properties that can be used to bind to a data source, a table within that data source and finally some fields that reside in that table in the same manner as a listbox. A Simple Data Bound Control and its DesignerThe source available for download contains the code for
Now in order to support data binding in the properties window we need to add a designer to the control. It is this designer that does
all the hard work while you are working with your control at design time. The source to the final implementation of
[DefaultProperty("DataSource"),
ToolboxData("<{0}:SimpleDataBoundControl runat="server"></{0}:SimpleDataBoundControl>"),
Designer(
typeof(ManyMonkeys.Web.ControlLibrary.Design.SimpleDataBoundControlDesigner))]
public class SimpleDataBoundControl : System.Web.UI.WebControls.WebControl ,
INamingContainer
{
...
}
The DataSource Property
public class SimpleDataBoundControlDesigner : ...
{
...
public string DataSource
{
get
{
DataBinding binding = DataBindings["DataSource"];
if (binding != null)
return binding.Expression;
return string.Empty;
}
set
{
if ((value == null) || (value.Length == 0))
base.DataBindings.Remove("DataSource");
else
{
DataBinding binding = DataBindings["DataSource"];
if (binding == null)
binding = new DataBinding("DataSource", typeof(IEnumerable),
value);
else
binding.Expression = value;
DataBindings.Add(binding);
}
OnBindingsCollectionChanged("DataSource");
}
}
}
We also need to add a type converter called
public class SimpleDataBoundControlDesigner : ...
{
...
protected override void PreFilterProperties(IDictionary properties)
{
base.PreFilterProperties(properties);
PropertyDescriptor prop = (PropertyDescriptor)properties["DataSource"];
if(prop!=null)
{
AttributeCollection runtimeAttributes = prop.Attributes;
// make a copy of the original attributes but make room for one extra
// attribute ie the TypeConverter attribute
Attribute[] attrs = new Attribute[runtimeAttributes.Count + 1];
runtimeAttributes.CopyTo(attrs, 0);
attrs[runtimeAttributes.Count] = new
TypeConverterAttribute(typeof(DataSourceConverter));
prop = TypeDescriptor.CreateProperty(this.GetType(), "DataSource",
typeof(string),attrs);
properties["DataSource"] = prop;
}
}
}
The final step in implementing the
public class SimpleDataBoundControl : ...
{
...
private object _dataSource=null;
[
Bindable(true),
Category("Data"),
DefaultValue(null),
Description("The datasource that is used to populate the list with items."),
// needs to be hidden otherwise we don't save the property for some reason
DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)
]
public object DataSource
{
get { return _dataSource; }
set
{
if ((value == null) || (value is IListSource) || (value is IEnumerable))
_dataSource = value;
else
throw new Exception("Invalid datasource.");
}
}
}
Now we should have a control for which we can select a data source from a list of available data sources. The IDataSourceProvider interface
As you can see there has been no need to implement the
A designer that implements the
Since it is required to implement the properties that use The DataMember Property
The First we need to create a property for
public class SimpleDataBoundControlDesigner : ...
{
...
public string DataMember
{
get
{
return ((SimpleDataBoundControl)this.Component).DataMember;
}
set
{
((SimpleDataBoundControl)this.Component).DataMember = value;
}
}
}
and then in
public class SimpleDataBoundControlDesigner : ...
{
...
protected override void PreFilterProperties(IDictionary properties)
{
...
prop = (PropertyDescriptor)properties["DataMember"];
if(prop!=null)
{
AttributeCollection runtimeAttributes = prop.Attributes;
// make a copy of the original attributes but make room for one extra
// attribute ie the TypeConverter attribute
Attribute[] attrs = new Attribute[runtimeAttributes.Count + 1];
runtimeAttributes.CopyTo(attrs, 0);
attrs[runtimeAttributes.Count] = new TypeConverterAttribute(
typeof(DataMemberConverter));
prop = TypeDescriptor.CreateProperty(this.GetType(), "DataMember",
typeof(string),attrs);
properties["DataMember"] = prop;
}
}
}
Now for the
public class SimpleDataBoundControlDesigner : ...
{
...
object IDataSourceProvider.GetSelectedDataSource()
{
object selectedDataSource = null;
string dataSource = null;
DataBinding binding = DataBindings["DataSource"];
if (binding != null)
{
dataSource = binding.Expression;
}
if (dataSource != null)
{
ISite componentSite = Component.Site;
if (componentSite != null)
{
IContainer container = (IContainer)componentSite.GetService(
typeof(IContainer));
if (container != null)
{
IComponent comp = container.Components[dataSource];
// Added the IListSource test as DataSet doesn't
// support IEnumerable
if ((comp is IEnumerable) || (comp is IListSource))
{
selectedDataSource = comp;
}
}
}
}
return selectedDataSource;
}
IEnumerable IDataSourceProvider.GetResolvedSelectedDataSource()
{
return null;
}
}
We have only implemented a simple implementation for Now we should have a control for which we can select a table, using a listbox, from a list of available tables for a selected data source. The DataTextField and DataValueField Properties
The
public class SimpleDataBoundControlDesigner : ... { ... public string DataTextField { get { return ((SimpleDataBoundControl)this.Component).DataTextField; } set { ((SimpleDataBoundControl)this.Component).DataTextField = value; } } public string DataValueField { get { return ((SimpleDataBoundControl)this.Component).DataValueField; } set { ((SimpleDataBoundControl)this.Component).DataValueField = value; } } protected override void PreFilterProperties(IDictionary properties) { ... prop = (PropertyDescriptor)properties["DataTextField"]; if(prop!=null) { AttributeCollection runtimeAttributes = prop.Attributes; Attribute[] attrs = new Attribute[runtimeAttributes.Count + 1]; // make a copy of the original attributes but make room for one extra // attribute ie the TypeConverter attribute runtimeAttributes.CopyTo(attrs, 0); attrs[runtimeAttributes.Count] = new TypeConverterAttribute( typeof(DataFieldConverter)); prop = TypeDescriptor.CreateProperty(this.GetType(), "DataTextField", typeof(string),attrs); properties["DataTextField"] = prop; } prop = (PropertyDescriptor)properties["DataValueField"]; if(prop!=null) { AttributeCollection runtimeAttributes = prop.Attributes; Attribute[] attrs = new Attribute[runtimeAttributes.Count + 1]; // make a copy of the original attributes but make room for one extra // attribute ie the TypeConverter attribute runtimeAttributes.CopyTo(attrs, 0); attrs[runtimeAttributes.Count] = new TypeConverterAttribute( typeof(DataFieldConverter)); prop = TypeDescriptor.CreateProperty(this.GetType(), "DataValueField", typeof(string),attrs); properties["DataValueField"] = prop; } } }
Now that all is needed is the
IEnumerable IDataSourceProvider.GetResolvedSelectedDataSource()
{
return (IEnumerable)((IDataSourceProvider)this).GetSelectedDataSource();
}
However as already mentioned a public class SimpleDataBoundControlDesigner : ... { ... IEnumerable IDataSourceProvider.GetResolvedSelectedDataSource() { object selectedDataSource = ((IDataSourceProvider)this).GetSelectedDataSource(); DataView dataView = null; if (selectedDataSource is DataSet) { // find the correct table or if non set the look up the first table DataSet dataSet = (DataSet)selectedDataSource; DataTable dataTable = null; if ((DataMember != null) && (DataMember.Length>0)) dataTable = dataSet.Tables[DataMember]; else dataTable=dataSet.Tables[0]; // we found a table so lets just get its default view if (dataTable!=null) { dataView = dataTable.DefaultView; } } else if (selectedDataSource is DataTable) { // just get the default view since we have just been given a table dataView = ((DataTable)selectedDataSource).DefaultView; } else if (selectedDataSource is IEnumerable) { // might as well just see if it will cast as this is // the MS sample's default return selectedDataSource as IEnumerable; } return dataView as IEnumerable; } } Now we should have a control for which we can choose a field from a selected table. The DesignTimeData Class
The public class SimpleDataBoundControlDesigner : ... { ... object IDataSourceProvider.GetSelectedDataSource() { DataBinding binding; binding = this.DataBindings["DataSource"]; if (binding != null) return DesignTimeData.GetSelectedDataSource(this.Component, binding.Expression); return null; } IEnumerable IDataSourceProvider.GetResolvedSelectedDataSource() { DataBinding binding; binding = this.DataBindings["DataSource"]; if (binding != null) return DesignTimeData.GetSelectedDataSource(this.Component, binding.Expression, this.DataMember); return null; } } CommentsPlease take the time to vote for this article and/or to comment about it on the boards below. All suggestions for improvements will be considered. History
| ||||||||||||||||||||