#一、前言#
很多第三方插件实现了这个功能,但不是我想要的那种像Excel表头一点就展开筛选面板的效果(很多用的是文本框输入filter查询条件),干脆自己动手丰衣足食吧。当初觉得太复杂,拖了很久,直到真的实现了,回头看看挺不可思议的。
#二、概述#
ASP.NET页面上我们显示数据表格一般用GridView控件,有时GridView数据行数很多,我想用一个类似Excel里的筛选功能来检索数据,如下图:
下图是我实现的效果,是不是很像Excel里的筛选功能。
#三、基本思路#
GridView列可以通过TemplateField实现用户自定义列模板,我们用ItemTemplate比较多,但是它也有HeaderTemplate,如果在里面放一个dropdownlist来实现下拉框效果,就可以把当前这个字段所有的值按distinct方式查询,结果集绑回dropdownlist控件,为dropdownlist定义一个响应事件,完成选择后触发该事件重新查询数据集,dropdownlist中的选项作为sql查询条件,动态组装出新的sql语句,得到新数据集重新绑定回GridView。
#四、核心控件 MultipleDropdownList#
##4.1 为啥需要MultipleDropdownList ##
首先我们需要一个复选下拉框控件,网上有很多大牛写好的,但是没有能直接用在本例的,多选结束后,需要一个动作来提交选项,使用button是很自然的选择,于是至少需要两个按钮:确定和取消,然后我又增加了一个取消筛选按钮,可以方便地将当前列中所有选项恢复到默认状态(全选状态)。
##4.2 MultipleDropdownList 的来历##
目前大家看到的截图中的控件是我搜索出一个大牛的作品然后改造了一下,增加了三个按钮,修改了响应事件的处理方法,需要注意的是,这个控件只能在gridview做筛选功能时使用,如果只是想普通地使用复选下拉框,直接用网上的原版就可以了。
原文地址:http://blog.csdn.net/bclz_vs/article/details/7317266
##4.3 MultipleDropdownList 部分细节说明##
MultipleDropdownList是CompositeControl类型控件,代码不粘贴了,下载链接在文章最后,仅就一些需要说明的地方解释一下。
###4.3.1 MultipleDropdownList 类文件###
private CheckBoxList _checkBoxes;
private Button _btnsOK; //确定按钮
//private Button _btnsCL; //取消按钮
private Button _btnsRevoke; //取消筛选按钮
private TextBox _textBox;
private static ListItem _selectAllItem = new ListItem("全选", "___selectAll___");
整个复合控件是由一个CheckBoxList两个button一个textbox组合而成,textbox就是在页面上呈现的控件收起样式,当展开时,向下延伸出的div里就是checkboxlist和几个button组成成的筛选面板,其实还有一个取消按钮,这个按钮后来我改成了纯html按钮,不再使用服务器控件,取消按钮不需要回发信息给服务器。
###4.3.2 重写DataSource###
public Object DataSource
{
get
{
if (ViewState["_dataSource"] != null)
{
DataTable stoveTable = (DataTable)ViewState["_dataSource"];
DataRow dataRow = stoveTable.NewRow();
dataRow[0] = "全选";
stoveTable.Rows.InsertAt(dataRow, 0);
ViewState["_dataSource"] = stoveTable;
}
return ViewState["_dataSource"];
}
set { ViewState["_dataSource"] = value; }
}
这样写的目的就是为了在选项列表自动加上“全选”项,不需要用户自己写代码实现给下拉框增加“全选”项。不过这样写DataSource只能接收datatable类型的数据,之前大牛写的还可以接收list类型,有得有失吧。
###4.3.3 OnConfirm事件###
为复合控件增加OnConfirm事件,在点击确定按钮时触发这个事件,自定义控件的事件冒泡我参考了这位大牛的文章。
原文:http://www.cnblogs.com/yanyangtian/archive/2008/08/25/1275741.html
在冒泡事件OnBubbleEvent内判断命令是Confirm还是Revoke,这两个命令分别来自确定按钮和取消筛选按钮,如果是取消筛选,则清空所选也就是默认目标列恢复到全选状态。可以理解为,这里的主要工作就是根据点击的按钮正确的计算和维持checkboxlist里选项的勾选状态。
###4.3.4 GetDivWidth方法###
GetDivWidth()的主要作用是计算展开div的宽度,遍历计算所有选项的字符长度,找到最长的选项的字符,在其长度的基础上加上滚动条的宽度和两边留白的距离,这样才能让展开的div面板好看,且选项不会因为宽度不够而换行挤在一起,这里偷懒用for循环遍历,效率理论上不忍直视,实际用起来还行。
###4.3.5 几个override的方法###
CreateChildControls() 创建组合控件中的子控件对象
RenderContents(HtmlTextWriter writer) 编写将在客户端呈现的内容
Render方法是将服务器控件内容发送到提供的HtmlTextWriter 对象,此对象编写将在客户端呈现的内容,或者调用编写内容的方法例如本例RenderContents
OnInit方法,在所有控件都已初始化且已应用所有外观设置后引发。使用该事件来读取或初始化控件属性。不需要你手动调用,在执行完PreInit后便会自动执行。Page_Load其实是响应页面的Load事件,它在OnInit后面执行! 每个页面都有OnInit 方法,(你没有重写不代表它不存在),而Page_Load是可以没有的,你可以删除页面中的Page_Load
###4.3.6 js文件和css文件###
js文件内的主要内容就是实现div的展开和复选框的勾选功能,作为嵌入资源即可。
#五、实现方法(前台页面需要做的事)#
##第一步:在aspx页面添加引用##
<%@ Register Assembly="DevControl" Namespace="DevControl" TagPrefix="Dev" %>
##第二步:添加js和css引用##
<link href="../css/multipledropdownList/MultipleDropdownList.css" type="text/css" rel="stylesheet" />
<script src="../css/multipledropdownList/jquery-1.6.1.js" type="text/javascript"></script>
后来我把js和css都一起封装到dll里面了,如果你直接添加dll应用,就不需要再添加js和css;如果你直接用源代码编译,那还是需要引用。
##第三步:添加js引用代码段##
<script type="text/javascript" language="javascript"> Sys.WebForms.PageRequestManager.getInstance().add_beginRequest(BeginRequestHandler); Sys.WebForms.PageRequestManager.getInstance().add_endRequest(EndRequestHandler);
function BeginRequestHandler(sender, args) {
var $mutipleDropdownLists = $('span.__MultipleDropdownList__');
$mutipleDropdownLists.each(function () {
multipleDropdownList(this);
});
var $BtnCancel = $('span.__BtnCancel__');
$BtnCancel.each(function () {
hiddenDiv(this);
});
}
function EndRequestHandler(sender, args) {
var $mutipleDropdownLists = $('span.__MultipleDropdownList__');
$mutipleDropdownLists.each(function () {
multipleDropdownList(this);
});
var $BtnCancel = $('span.__BtnCancel__');
$BtnCancel.each(function () {
hiddenDiv(this);
});
}
和第二步一样如果直接用dll就不需要了。
##第四步:将控件放入GridView的HeaderTemplate##
<asp:GridView ID="GridView1" runat="server" Width="100%" OnRowCommand="GridView1_RowCommand" AutoGenerateColumns="False" OnRowDataBound="GridView1_RowDataBound" OnRowDeleting="GridView1_RowDeleting" OnPreRender="GridView1_PreRender" OnDataBound="GridView1_DataBound">
<Columns> <asp:TemplateField HeaderText="材料编码">
<HeaderTemplate>
<Dev:MultipleDropdownList ID="DropDownListHeadPartNo" runat="server" OnConfirmClick="DropDownListHead_SelectedIndexChanged"
Width="100%" />
</HeaderTemplate>
<ItemTemplate>
<asp:Label ID="Label1" runat="server" Text='<%# Bind("材料编码") %>'>
</asp:Label>
</ItemTemplate>
<ItemStyle HorizontalAlign="Center" />
</asp:TemplateField>
。。。(若干列)
</Columns>
</asp:GridView>
注意:代码中的若干列是省略掉的代码,gridview中定义了若干列,重复的代码不在这里占用篇幅 |
#六、实现方法(后台代码需要做的事)#
##第一步:定义需要用到的常量(全选标志)##
const string SelectAllListItemText = "SelectAll"; //全选标志位
##第二步:第一次sql查询处理事件##
第一次查询datatable并显示到gridview上时需要为每一个参加筛选的字段准备一个viewstate,并赋值SelectAllListItemText。说白了就是给每个筛选列一个初始值,默认初始状态是全选。
用viewstate当缓存是最快的,用别的也可以,性能问题大家按需考虑,我的项目用viewstate没啥大问题 |
//执行查询动作的事件
protected void btnQueryStore_Click(object sender, EventArgs e)
{
if (ddlGType.SelectedValue == "-请选择材料类别-")
{
ScriptManager.RegisterStartupScript(this, this.GetType(), "key", "alert('请选择材料类别')", true);
}
else
{
ViewState["ddlpartno"] = SelectAllListItemText;
ViewState["ddlname"] = SelectAllListItemText;
ViewState["ddlsize1"] = SelectAllListItemText;
ViewState["ddlsize2"] = SelectAllListItemText;
ViewState["ddlsch1"] = SelectAllListItemText;
ViewState["ddlsch2"] = SelectAllListItemText;
ViewState["ddlend"] = SelectAllListItemText;
ViewState["ddlclass"] = SelectAllListItemText;
ViewState["ddlstulen"] = SelectAllListItemText;
ViewState["ddlmaterial"] = SelectAllListItemText;
ViewState["ddlstd"] = SelectAllListItemText;
ViewState["ddltagnumber"] = SelectAllListItemText;
ViewState["ddlaera"] = SelectAllListItemText;
DataTable dt = 根据业务逻辑查询数据集,返回datatable
GridView1.DataSource = dt;
GridView1.DataBind();
ViewState["dt"] = dt;
}
}
注意:此时根据业务逻辑查询数据集时是不带筛选过滤条件的 |
##第三步: GridView的RowDataBound事件##
第一次将datatable绑定到gridview时,筛选列里的选项要都绑好,没选项怎么筛呢
protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
{
if (e.Row.RowType == DataControlRowType.Header)
{
MultipleDropdownList ddlpartNo = (MultipleDropdownList)e.Row.FindControl("DropDownListHeadPartNo"); //材料编码
。。。。。。若干列相同的处理
MultipleDropdownList ddlaera = (MultipleDropdownList)e.Row.FindControl("DropDownListHeadAera"); //主项号
string projectId = HiddenField_ProjectId.Value;
//绑定材料编码
ddlpartNo.DataSource = MakeNewStoreColumn.BindGVHeadPartNo(projectId,ddlGType.SelectedValue);
ddlpartNo.DataTextField = "材料编码";
ddlpartNo.DataValueField = "材料编码";
ddlpartNo.SelectedValuesJoined = ViewState["ddlpartno"].ToString();
ddlpartNo.DataBind();
ddlpartNo.Text = "材料编码";
//绑定材料名称
ddlpartName.DataSource = MakeNewStoreColumn.BindGVHeaditemNo(projectId, ddlGType.SelectedValue);
ddlpartName.DataTextField = "材料名称";
ddlpartName.DataValueField = "材料名称";
ddlpartName.SelectedValuesJoined = ViewState["ddlname"].ToString();
ddlpartName.DataBind();
ddlpartName.Text = "材料名称";
。。。。。。若干列相同的处理
//绑定主项号
ddlaera.DataSource = MakeNewStoreColumn.BindGVHeadAera(projectId, ddlGType.SelectedValue);
ddlaera.DataTextField = "主项号";
ddlaera.DataValueField = "主项号";
ddlaera.SelectedValuesJoined = ViewState["ddlaera"].ToString();
ddlaera.DataBind();
ddlaera.Text = "主项号";
}
}
##第四步: 实现BindGVHead方法##
该方法就是去查询目标字段的值有哪些选项,一个distinct查询,然后返回一个datatable,绑到当前的MultipleDropdownList上。
有几个可筛选的列,就有几个这样的方法,每个方法用来查各自列的不重复值数据集 |
static public DataTable BindGVHeadPartNo(string projectId,string GType)
{
DataTable dt = new DataTable();
string sql = "SELECT DISTINCT 语句 " +
"ORDER BY 语句";
SqlParameter[] prams = new SqlParameter[2];
prams[0] = new SqlParameter("@projectId",SqlDbType.BigInt);
prams[0].Value = projectId; //业务参数
prams[1] = new SqlParameter("@GType",SqlDbType.VarChar,50);
prams[1].Value = GType; //业务参数
dt = 执行查询动作返回datatable (sql, prams).Tables[0];
return dt;
}
##第五步: 实现DropDownListHead_SelectedIndexChanged方法##
实现DropDownListHead_SelectedIndexChanged方法,这是核心部分
protected void DropDownListHead_SelectedIndexChanged(object sender, EventArgs e)
{
MultipleDropdownList ddlist = (MultipleDropdownList)sender; //获取所有字段head的状态
/*******************************
* 每个if块
* 第一步:判断IsSelectAll属性,如果是返回"-all-",如果不是进入else,开始第二步
* 第二步:判断SelectedValuesJoined属性是否为""(空值),如果是返回"",如果不是返回SelectedValuesJoined
*/
if (ddlist.ID == "DropDownListHeadPartNo")
{
if (ddlist.SelectedValuesJoined == null)
{
ViewState["ddlpartno"] = SelectAllListItemText;
}
else if (ddlist.SelectedValuesJoined == String.Empty)
{
ViewState["ddlpartno"] = "なにぬ"; //说明没有选任何项,此时给一个奇怪的字符(这里用日文)作为查询条件
}
else
{
ViewState["ddlpartno"] = ddlist.SelectedValuesJoined;
}
}
。。。。。。若干列相同的处理
string AndCondition = String.Empty; //Sql字符串缓存,一个字段查询条件的or部分总和
ArrayList sqlList = new ArrayList();
sqlList.Clear();
//组装sql后半部分(查询条件)
//[材料编码]。。。。若干字段
AndCondition = GetAndConditionString("材料编码", ViewState["ddlpartno"].ToString()); //【材料编码】查询条件的or部分总和
if (AndCondition != String.Empty)
{ sqlList.Add(AndCondition); }
。。。。。。若干列相同处理
//sql语句,组装AND条件部分
HiddenField_Filter.Value = String.Empty;
if (sqlList.Count == 0) //如果没有查询条件
{ HiddenField_Filter.Value = String.Empty; }
else //如果有查询条件
{
string[] sqlOrPhrase = new string[sqlList.Count];
sqlList.CopyTo(sqlOrPhrase);
for (int i = 0; i < sqlOrPhrase.Length; i++)
{
HiddenField_Filter.Value = HiddenField_Filter.Value + " AND (" + sqlOrPhrase[i] + ")";
}
}
StringBuilder sqls = new StringBuilder();
//这里是拼装sql语句的关键地方:
//该sql语句就是你之前查datatable的语句,只不过加上了某些字段的筛选项作为过滤条件要把这个条件拼接成新的sql语句
sqls.Append("原来的select语句");
sqls.Append("WHERE子句 "); //注意最后要跟一个空格
sqls.Append(HiddenField_Filter.Value); //这个是用GetAndConditionString方法加for循环拼接出来的sql语句过滤条件部分
sqls.Append(" ORDER BY 子句");
ViewState["dt"] = 执行这个sqls查询,返回新的datatable
GridView1.DataSource = (DataTable)ViewState["dt"];
GridView1.DataBind();
}
##第六步:实现GetAndConditionString方法##
实现GetAndConditionString方法,功能是拼接筛选项组装而成的sql语句过滤条件,第一个参数是列名,第二个参数是其列对应的viewstate的值,这也是核心部分。
private string GetAndConditionString(string ColumnName, string ViewStateString)
{
/* 说明:
* 每个if块
* 第一步:判断ViewState,如果是String.Empty,返回一个不可能查询出结果的条件,比如我给了五个俄文字母(这是一个临时方案,以后要让确认按钮变灰),开始第二步
* 第二步:判断ViewState,如果是"-all-",如果是返回"",如果不是返回SelectedValuesJoined
*/
string[] OrCondition; //查询条件缓存 or条件
string iRel = String.Empty; //查询条件缓存 and条件
string talbeName = "材料编码表";
if (ViewStateString == SelectAllListItemText) //全选
{
iRel = String.Empty; //材料编码的and条件是一个String.Empty
}
else //不是全选
{
if (ViewStateString != String.Empty) //SelectedValuesJoined不等于Null
{
OrCondition = ViewStateString.Split(';'); //分拆
//组装前N-1和or条件
for (int i = 0; i < OrCondition.Length - 1; i++)
{
iRel += "MaterialControl." + talbeName + ".[" + ColumnName + "]='" + OrCondition[i] + "' OR "; //记得String结尾要有空格
}
//组装最后一个or条件
iRel += "MaterialControl." + talbeName + ".[" + ColumnName + "]='" + OrCondition[OrCondition.Length - 1] + "' "; //记得String结尾要有空格
}
else //全选Item(点击取消筛选 或者 全选后点击确定)
{
iRel = String.Empty;
}
}
return iRel;
}
##第七步:最后增加一个处理空行的功能##
如果选项条件查询出来的结果是空集,要让gridview显示一个空行,不然整个gridview会因为没有数据行而消失,筛选操作就无法继续。
//空行的处理
protected void GridView1_DataBound(object sender, EventArgs e)
{
if (GridView1.Rows.Count == 0)
{
DataTable plus = new DataTable();
plus.Columns.Add("材料编码");
plus.Columns.Add("材料名称");
。。。。。。若干列
plus.Rows.Add(plus.NewRow());
GridView1.DataSource = plus;
GridView1.DataBind();
}
}
#七、代码下载#
http://download.csdn.net/detail/xiangcns/9291563
以后有时间做一个完整的demo,目前就把复选控件先放上来吧,照着说明步骤即可实现效果。