模版控件能让用户几乎不用花费任何时间就创建出复杂的用户界面. asp.net有很多控件都使用了模版技术(datagrid就是一个例子). 而这些控件都工作得很好, 通常, 模版可以被保存为ascx文件以增加复用性. 很有可能, 事前你是不知道你的控件是怎么布局的, 而且你需要动态的添加一些模版以应付不同的事件.
使用模版的另一个优势,就是它们能动态的添加到你的控件里面去. 这样的话, 你可以事先设计好模版, 然后通过简单的几行代码就添加到你的控件中.
下面这篇文章就要告诉你如何如何一步步的添加一个动态的itemtemplate和edititemtemplate到datagrid中. 另外, 还会告诉你怎么获取和更新用户对edititemtemplate所做的改变. 例子将会是很简单的. 然后, 我很快就会在tripleasp上面正式发布一个改进后的tableeditor版本. 这个版本将更好的说明如何使用动态模版.
itempalte的实现
为了能动态的添加itemtemplate和edititemtemplate, 我们需要创建2个类来实现itemplate的接口(interface). 第一个类是genericitem. 这个类的主要工作就是: 取数据源的列名, 创建一个文本控件(literal contral), 为这个文本控件赋值, 最后把这个文本控件加到父控件(在这里父控件就是datagrid了).
到目前为止还是很顺利. 在继续下面的讨论之前, 我们来看看代码和完成的步骤.
using system;
using system.web;
using system.data; using system.web.ui;
using system.web.ui.webcontrols;
namespace tripleasp.itemtemplates
{
/// <summary>
/// summary description for genericitem.
/// </summary>
public class genericitem : itemplate
{
private string column;
//private bool validate;
public genericitem(string column)
{
this.column = column;
}
public void instantiatein(control container)
{
literal l = new literal();
l.databinding += new eventhandler(this.binddata);
container.controls.add(l);
}
public void binddata(object sender, eventargs e)
{
literal l = (literal) sender;
datagriditem container = (datagriditem) l.namingcontainer;
l.text = ((datarowview) container.dataitem)[column].tostring();
}
}
}
正如你看到的, genericitem类实现了itemplate的接口(interface). 因为我们是实现接口, 所以必须包括instantiatein这个方法. 这个方法是用来定义所有子控件和模版所属的控件对象的. 在这个方法里面, 我们创建了一个新的literal控件来保存datagrid的单元值. 接着, 我们添加了databinding事件处理函数. 这个事件处理函数实际上就是在datagrid绑定数据的时候, 把单元值放到literal控件的text属性中. 最后, 把这个literal控件加入到控件的容器集合中. 很简单吧?
动态edititemtemplate
动态edititemtemplate类validateedititem跟genericitem很类似, 但是有3个地方不同.
第一个不同的地方是, 我们添加的是textbox控件而不是literal控件. 这样的话, 在编辑模式下, 用户可以做任何修改.
第二个不同的地方, 你会发现我们会显式地命名控件. 这会使我们能够获取更新事件中的任何数据变化.
最后一个不同, 你会看到一个跟textbox相联系的requiredfieldvalidator控件. 这是可选的. 但是, 这的确让你知道有些事是可以这样做的.
下面就是validateedititem的代码:
using system;
using system.data;
using system.web.ui;
using system.web.ui.webcontrols;
using system.web;
namespace tripleasp.itemtemplates
{
/// <summary>
/// summary description for validateedititem.
/// </summary>
public class validateedititem : itemplate
{
private string column;
public validateedititem(string column)
{
this.column = column;
}
public void instantiatein(control container)
{
textbox tb = new textbox();
tb.databinding += new eventhandler(this.binddata);
container.controls.add(tb);
tb.id = column;
requiredfieldvalidator rfv = new requiredfieldvalidator();
rfv.text = "please answer";
rfv.controltovalidate = tb.id;
rfv.display = validatordisplay.dynamic;
rfv.id = "validate" + tb.id;
container.controls.add(rfv);
}
public void binddata(object sender, eventargs e)
{
textbox tb = (textbox) sender;
datagriditem container = (datagriditem)tb.namingcontainer;
tb.text = ((datarowview) container.dataitem)[column].tostring();
}
}
}
动态模版的实现
现在我们已经有两个实现了itempalte接口的类了. 一切准备好了! 我们现在要做的就是把它们加入到我们的datagrid里面.
我们把binddata和dynamiccolumns两个方法放在一起. binddata主要是创建sql查询语句, 往datagrid添加列(动态列), 然后把数据表绑定到datagrid.
void binddata()
{
string sql = "select * from publishers where state is not null";
datagrid1.columns.add(dynamiccolumns("pub_id",false));
datagrid1.columns.add(dynamiccolumns("pub_name",true));
datagrid1.columns.add(dynamiccolumns("city",true));
datagrid1.columns.add(dynamiccolumns("state",true));
datagrid1.columns.add(dynamiccolumns("country",true));
datagrid1.datakeyfield = "pub_id";
datagrid1.datasource = getdatatable(sql);
datagrid1.databind();
}
dynamiccolumns有两个参数: column(字符类型)和iseditable(布尔类型). column变量当然就是我们要加入templatecolumn的列名. iseditable变量是用作测试的, 如果我们希望这个列是允许编辑的话.
protected templatecolumn dynamiccolumns(string column, bool iseditable)
{
templatecolumn genericcolumn = new templatecolumn();
genericcolumn.headertext = column;
genericcolumn.itemtemplate = new genericitem(column);
if(iseditable)
{
genericcolumn.edititemtemplate = new validateedititem(column);
}
return genericcolumn;
}
正如你所看到的, 首先我们实例化一个templatecolumn(genericcolumn), 根据我们要添加的列的名字设置headertext属性(当然,你可以设置为任何东西都可以). 接着, 我们通过添加新的genericitem的参考(reference), 把itemtemplate添加到genericcolumn, 并把名称传入. 最后, 我们必须检查iseditable, 以便看看我们需不需要允许编辑这个列. 如果为真, 我们要往validateedititem添加新的参考, 而且把列名也传过去.
datagrid事件
我们的编辑和取消事件是很标准的. 你有可能已经看过它们100遍了. 在我们的编辑事件里面, 我们简单地取出被选中的行的编号, 然后重新绑定数据.
protected void edit_click(object sender, datagridcommandeventargs e)
{
datagrid1.edititemindex = e.item.itemindex;
binddata();
}
我们的取消事件是把当前所选行号设为-1. 这样就等于告诉datagrid, 不在是编辑模式了. 然后, 我们重新绑定数据.
protected void cancel_click(object sender, datagridcommandeventargs e)
{
datagrid1.edititemindex = -1;
binddata();
}
更新事件会跟你以前看到的有一点点不同. 然而, 它却会让你想起你在asp的日子.
protected void update_click(object sender, datagridcommandeventargs e)
{
//gets the uniqueid that is attached to the front of each textbox
//dyamically added to our datagrid's edititemtempate
string uid = e.item.uniqueid + ":";
string pub_id = (string)datagrid1.datakeys[e.item.itemindex];
string pub_name = (request.form[uid + "pub_name"].tostring());
string city = (request.form[uid + "city"].tostring());
string state = (request.form[uid + "state"].tostring());
string country = (request.form[uid + "country"].tostring());
//simple method to update db
updaterecord(pub_id,pub_name,city,state,country);
datagrid1.edititemindex = -1;
binddata();
}
这样的话, edititemtemplate就硬编码到页面中去了. 你可能已经看过一些取表单提交数据的例子, 其中的方法, 或者是通过控件位置取值, 或者是控件名称取值. 但是, 如果你是在运行时创建控件, 那么, 在postback的时候, asp.net是无法取得这些值的. 为此, 我们只能通过request.form的方法来得到这些值.
在你开始在validateedititem类里面仔细寻找被小心命名的textbox的时候, 你必须记住, asp.net已经为控件的名字冲突做了预防措施. 一般来说, 这包括增加每个datagrid父控件的名称, datagrid本身的名称, 和一个代表每个textbox的序号的字符串放在textbox的id前面. 我们可以大量的使用这样的方法. 但是这并不保证我们的代码绝对的模块化和可复用. 相反, 我们检查datagridcommandeventargs.item.uniqueid 并在尾部加上":". 有了这个uniqueid, 我们就可以安全地取得textbox里面的编辑数据, 并更新到数据库.
结论
动态添加模版到你的模版控件会在开始的时候增加一点点的工作量. 但是, 一旦你建立了一系列的优秀的模版类, 你会发现, 实现itemplate会非常的快速和容易. 它运行你建立强大的控件来满足你数据操作的需要. 如果你需要更好的例子, 请看我即将发布在tripleasp的tableeditor控件.