The next control is something like the GridView control,
where you can delete a newsletter from the list, as well as create new
newsletters. This control inherits from CompositeDataBoundControl, and supports
the more advanced binding through a data source control or another enumerable
data source.
Because the underlying data source comes from the provider,
this control doesn't make use of the DataSource or DataSourceID properties at
all. Instead, it makes use of the provider internally, and so when being bound
to, it uses the provider data source instead, as we shall soon see.
There is a scheme with these controls that you need to
understand. With these types of tabular data controls, there is a usually a
header, a series of items, and the footer below it. The header, in this case,
is non-existent because it isn't needed. The items will be the list of each of
the newsletters, as well as a link to delete the newsletter. In the footer,
there are two textboxes for the newsletter name and description, and a link to
add this newly found newsletter.
The CompositeDataBoundControl class uses a different
CreateChildControls overloaded method for creating the inner data-bound
controls. The following is the definition for this overload in the newsletter
administration control:
Listing 10
protected override int CreateChildControls(System.Collections.IEnumerable
dataSource, bool dataBinding)
{
int itemCount = 0;
if (dataBinding)
dataSource = Newsletter.GetAllNewsletters();
Table table = new Table();
this.Controls.Add(table);
this.Items.Clear();
foreach (object o in dataSource)
this.Items.Add(this.CreateItem(table, o, itemCount++, dataBinding));
this.CreateFooter(table);
return itemCount;
}
This method takes an IEnumerable data source that is
resolved from the manually bound data source (bound through the DataSource
property) or a data source control bound through the DataSourceID property.
This isn't a concern for two reasons; the CompositeDataBoundControl class takes
care of the differences through the backend architecture, and the second I will
talk about in a moment.
For now, the dataBinding parameter is the key to determine
whether we are in data binding mode, meaning that a data source is bound. The
data source is provided from the Newsletter.GetAllNewsletters() provider
method, and assigned to the data. The next time around, provided it isn't
bound to, is the ViewState being bound, which means the data source is an empty
object array.
When using the provider, the DataBinding property is the key
because DataSource will be not be used no matter what. Unfortunately, the
DataSource and DataSourceID properties do not help in this situation, because
the data source is provided within the control instead of on the outside.
Listing 11
new private object DataSource
{
get
{
return null;
}
set
{
throw new NotImplementedException();
}
}
new private string DataSourceID
{
get
{
return string.Empty;
}
set
{
throw new NotImplementedException();
}
}
The table is the structure that has the header, footer, and
items, and the CreateChildControls method above regulates when that all
happens. The CreateItem method is responsible for the creation of the actual
items.
Listing 12
protected virtual DataboundItem CreateItem(Table table, object dataItem, int
index, bool dataBinding)
{
DataboundItem item = new DataboundItem(dataItem, index);
table.Rows.Add(item);
TableCell newsletterCell = new TableCell();
item.Cells.Add(newsletterCell);
TableCell buttonCell = new TableCell();
item.Cells.Add(buttonCell);
this.OnItemCreated(new DataEventArgs < DataboundItem > (item));
if (dataBinding)
{
newsletterCell.Text = (string)dataItem;
this.OnItemDatabound(new DataEventArgs < DataboundItem > (item));
}
LinkButton button = new LinkButton();
button.Text = this.DeleteButtonText;
button.CommandName = "delete";
button.CommandArgument = this.Items.Count.ToString();
buttonCell.Controls.Add(button);
return item;
}
Notice that there is added logic that runs when data
binding. When in data binding mode, the cell is assigned the text value of the
newsletter passed from the string array of newsletters. Any other fields that
would be assigned something would also be assigned here, but since there is one
field, only one field is needed. Lastly, when binding, the ItemDataBound event
fires for the row.
The control overrides the OnBubbleEvent to capture any
events that were raised, and process them accordingly. This method could be
used to raise a RowCommand event, similar to the GridView's event that fires
for any outstanding commands, if so desired. We will get to the HandleEvent
method in a moment.
Listing 13
protected override bool OnBubbleEvent(object source, EventArgs args)
{
CommandEventArgs commandArgs = args as CommandEventArgs;
if (commandArgs != null)
return this.HandleEvent(commandArgs);
return false;
}
This control implements IPostBackEventHandler; any button
control that makes use of the GetPostBackClientHyperlink method raises the
click event to this method. It is here that the command name/argument are
passed in as the event argument, parsed, and passed to the HandleEvent method.
Listing 14
public void RaisePostBackEvent(string eventArgument)
{
if (eventArgument.Contains("$"))
{
string[]parts = eventArgument.Split('$');
CommandEventArgs args = new CommandEventArgs(parts[0], parts[1]);
this.HandleEvent(args);
}
}
The HandleEvent method received the command argument
raised. If inserting, the name/description is retrieved and the newsletter is
added through the provider. If deleting, the index of the row is used to
receive the name of the newsletter from the cell in which it is rendered. So
if row zero's button is clicked, row zero's newsletter name stored in the cell
is retrieved.
Listing 15
private bool HandleEvent(CommandEventArgs args)
{
if (args.CommandName == "insert")
{
Page.Validate("NewsletterAdministration_Add");
if (Page.IsValid)
{
string name = _newsletterName.Text;
string description = _newsletterDescription.Text;
Newsletter.AddNewsletter(name, description);
}
}
else if (args.CommandName == "delete")
Newsletter.RemoveNewsletter(this.Items[int.Parse
(args.CommandArgument.ToString())].Cells[0].Text);
else
return false;
base.RequiresDataBinding = true;
this.DataBind();
return true;
}
After each is run, the control is rebound. RequiresDataBinding
ensures that the data binding occurs.