1. 引言

我们经常会在网页上看到使用星形图案表示对某个软件或某篇文章的评价,通常以五个星形作为最高标准,指定的等级对应使用实心填充,如图1-1所示,在学习ASP.NET自定义控件的第一天,我们将开发这样的自定义控件。

2. 分析

可以看到这样的一个自定义控件包含两部分:显示的文本和包含两种图案(实心和空心星形)的图片,为了呈现出这样的结果,最方便的就是将这两部分放到包含一行两列的表格中。接下来要考虑的就是如何根据评分显示若干实心和空心星形。

看到这个图案的第一个想法可能就是根据评分首先显示实心图形,再判断还需要显示几个空心星形(用5减评分),这样的话需要做多次循环才能实现,但是在HTML里有更好的实现方法。

想一下在使用CSS设置背景图片时可以设置background-repeat属性,该属性标识背景图片按哪个方向重复,可以利用这个特性首先显示一个外层的div,该div始终显示5个空心的星形图形,在该层中嵌套另一个div,内层div显示实心星形图案。

background-repeat、background-image、background-color、background-position构成了设置背景样式的属性族

既然可以按X方向重复星形背景,那么显示指定个数的星形也就有答案了—我们可以根据得分设置指定层(div)的宽度即可,而且为了方便,将两个星形图案放置到一个图片文件里,再应用background-position显示指定位置星形即可。

根据以上分析,此自定义控件中需要具有两个属性:

属性描述
Comment 评分项目注释,字符串类型
Score 得分,根据该属性显示相应的实心图形,数字类型

3. 实现

1. 首先创建解决方案,包含ControlLibrary类库(定义了自定义控件类)和Web网站。

2. 在类库中创建Image目录,并放置使用的星形图片stars.gif,为了能够在网站中引用程序集中的资源文件,需要将图片的Build Action(生成动作)属性设置为Embedded Resource(嵌入资源),并且在AssemblyInfo.cs中声明要使用的资源文件,如下所示:

[assembly: WebResource("ControlLibrary.Image.stars.gif","image/jpg")]

使用了WebResource(实际上是WebResourceAttribute类,为了使用该类需要引入System.Web.UI命名空间)定义了使用的资源,该类的构造函数使用了两个参数,第一个参数是Web资源的名称,第二个参数是MIME类型。Web资源的名称必须遵循特定规则:命名空间名称+目录名称+文件名称,中间用半角点字符分隔。MIME类型对于不同的文件有不同的表示,详细的MIME列表可参考相关资料。

资源的名称可以通过.NET Reflector之类的工具浏览。载入某个程序集后,如果嵌有资源文件,可以通过Resources目录浏览嵌入的资源

3. 在类库中创建自定义控件类Star.cs,并引入必须的命名空间:

using System;
using System.ComponentModel;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace ControlLibrary
{
    public class Star : WebControl
    {
    }
}

4. 接下来定义Score和Comment属性,将这两个属性存储在ViewState中,同时如果用户没有设计得分的情况下将得分设置为0,也就是将得分的默认值设置为0,这里使用到了DefaultValutAttribute类,该类用于设置属性的默认值。

[DefaultValue(0)]
public int Score
{
    get
    {
        object obj = ViewState["Score"];
        return obj == null ? 0 : Convert.ToInt32(obj);
    }
    set
    {                
        ViewState["Score"] = value;
    }
}

public string Comment
{
    get
    {
        object obj = ViewState["Comment"];
        return obj == null ? string.Empty : Convert.ToString(obj);
    }
    set
    {
        ViewState["Comment"] = value;
    }
}
由于HTTP请求完成之后会断开连接,把属性保存到私有变量中不会满足我们的要求(提交回发时会重新生成自定义控件类,有可能丢失属性值),所以保存到ViewState中,回发时能够正确恢复属性值

5. 重写CreateChildControls方法,该类在Control类上定义,此方法用于创建控件层次,以便为回发和呈现做准备。在该方法中调用了自定义CreateControlHierarchy方法以创建控件层次:

protected override void CreateChildControls()
{
    base.CreateChildControls();

    CreateControlHierarchy();
}

6. 在CreateControlHierarchy方法中创建一个一行两列的表格,并分别调用CreateComment方法和CreateStarts方法创建注释和星形图按:

protected virtual void CreateControlHierarchy()
{
    Table table = new Table();
    TableRow row = new TableRow();
    table.Rows.Add(row);
    TableCell comment = new TableCell();
    CreateComment(comment);
    row.Cells.Add(comment);

    TableCell stars = new TableCell();

    CreateStars(stars);

    row.Cells.Add(stars);

    this.Controls.Add(table);
}

7. 实现CreateComment方法,该方法接收一个TableCell参数当做显示文本的容器,直接设置单元格的文本为注释字符串:

private void CreateComment(TableCell cell)
{
    cell.Text = Comment;
}

8. 实现CreateStarts方法,该方法亦接收一个TableCell当做参数,首先取得图片资源文件的路径,并在单元格里创建嵌套的层(在服务器端控件中Panel类与div相对应)并根据得分设设置其相应属性:

private void CreateStars(TableCell cell)
{
    string starPath = Page.ClientScript.GetWebResourceUrl(this.GetType(), "ControlLibrary.Image.stars.gif");

    Panel panBg = new Panel();
    panBg.Style.Add(HtmlTextWriterStyle.Width, "80px");
    panBg.Style.Add(HtmlTextWriterStyle.Height, "16px");
    panBg.Style.Add(HtmlTextWriterStyle.TextAlign, "left");
    panBg.Style.Add(HtmlTextWriterStyle.Overflow, "hidden");
    panBg.Style.Add(HtmlTextWriterStyle.BackgroundImage, starPath);
    panBg.Style.Add("background-position", "0px -32px");
    panBg.Style.Add("background-repeat", "repeat-x");

    cell.Controls.Add(panBg);

    Panel panCur = new Panel();
    string width=Score*16+"px";
    panCur.Style.Add(HtmlTextWriterStyle.Width, width);
    panCur.Style.Add(HtmlTextWriterStyle.Height, "16px");            
    panCur.Style.Add(HtmlTextWriterStyle.BackgroundImage, starPath);
    panCur.Style.Add("background-position", "0px 0px");            
    panCur.Style.Add("background-repeat", "repeat-x");

    panBg.Controls.Add(panCur);
}
9. 重写父类的Render方法,在该方法中将服务器控件的内容传递给HtmlTextWriter对象以在客户端呈现内容,在该方法中调用了PrepareControlForRender方法:
protected override void Render(HtmlTextWriter writer)
{
    PrepareControlForReader();

    base.Render(writer);
}

10. 实现PrepareControlForRender方法,该方法用于在呈现前进行其他样式的设置,在本控件中,只是简单的设置了表格的CellSpacing和CellPadding属性:

private void PrepareControlForReader()
{
    if (this.Controls.Count < 1)
        return;

    Table table = (Table)this.Controls[0];            

    table.CellSpacing = 0;
    table.CellPadding = 0;
}

11. 在网站中添加ControlLibrary类库的引用,并在Default.aspx页面中加入自定义控件的声明:

<%@ Register Assembly="ControlLibrary" Namespace="ControlLibrary" TagPrefix="cc" %>

12. 在页面相应位置定义自定义控件:

<%@ Register Assembly=?ControlLibrary? Namespace=?ControlLibrary? TagPrefix=?cc? %>

13. 预览效果。

在多个页面使用自定义控件在每一个页均需要加入声明,有一个更好的替代方法是在web.config文件中声明自定义控件,在配置节中加入
<pages>
    <controls>
        <add tagPrefix="cc" assembly="ControlLibrary" namespace="ControlLibrary"/>
    </controls>
</pages>

4. 总结

本次任务里我们通过继承WebControl类创建了一个很简单的星级自定义控件,随着学习的加深您将逐渐意识到该控件实际上包含了自定义控件开发的基本步骤——创建自控件、设置样式、进行呈现,通过把相应的动封装为不同的方法使代码看起来更清晰,并且掌握了如何在自定义控件中使用资源文件,这在自定义控件使用大量样式或者脚本时尤其有用。最后讲解了在开发完控件后如何在页面中使用。


ASP.NET自定义控件系列文章

前言

第一天 简单的星级控件

第二天 带有自定义样式的星级控件

第三天 使用控件状态的星级控件

第四天 折叠面板自定义控件

第五天 可以评分的星级控件

第六天 可以绑定数据源的星级控件

第七天 开发具有丰富特性的列表控件

第八天 显示多个条目星级评分的数据绑定控件

第九天 自定义GridView

第十天 实现分页功能的DataList


全部源码下载

本系列文章PDF版本下载