Go homepage(回首页) Upload pictures (上传图片) Write articles (发文字帖)
The author:(作者)归海一刀published in(发表于) 2014/1/30 1:01:26 Discuz!NT控件剖析,之,DataGrid(数据列表),[原创-,附源码]_[Asp.Net教程]
自从 9月未开始写关于"ICONIX方法"的系列文章以来,到今天已有两个多月了,当初因为兴趣点的转移才划一段落的Discuz!NT 系列文章,从今天开始又要开始续写了。这写这个系列以来,大家对我写的内容也是包贬不一,其实这也是众口难调所致,我会在接下来的几篇随笔中尽力顾及大家的感受和阅读口味。最后还是希望大家能支持我们
的这个开源项目。
好了,开始今天的话题!
先请大家看一下这个控件运行时的效果图:
效果图1: datagrid_1.JPG
效果图2: datagrid_2.JPG
需要说明的是写这个控件(继承自.net DataGrid 控件)的动机: 其实在产品早期,为了提高开发速度。我们最先使用的是Component Art 控件库中的DataGrid控件,相信园子里肯定有人用过这个商业控件库,甚至研究过它。从我个人感受看,我只能是“NB”来形容它了,首先是它的使用习惯和方法名称非常接近(甚至完全相同)于我们所熟悉的Microsoft .NET DataGrid 控件,另外就是它的功能比微软的DataGrid强大得不是一点半点,是那种“很好很强大”的东东。我在这里要向那些致力于控件开发的朋友推荐这个控件库,相信大家会从它的源码中学到很多有益的东西。 如果这里有些朋友还不知道它的话,建议大家看看 CS(这里可不是"反恐精英"呀),这个开源社区软件里的控件库就用的是Component Art。
言归正传吧,因为Component Art是商业控件库,如果使用的话会有许多问题(如授权等)。所以在Discuz!NT1.0 正式版推出前, 我们的相应代码基本上都又再次回到了 Microsoft .NET DataGrid上了,同时考虑到所开发的代码要适用于.net1.0-.net2.0,所以没有使用.net2.0中的DataView控件。 即然决定使用Microsoft .NET DataGrid,那么就要看看它到底适不适合我们这个项目了:) ,因为之前已完成了后台的编码工作,而剩下的就是在确保少修改代码,甚至减少代码量的基础上使用这个控件来摆脱Component Art。
在减少代码量的方面,这里对“原始”的DataGrid进行了不少的封装, 如下:
1.绑定数据部分,为了实现只给一条 SQL语句,就完成数据绑定的设计,我在继承自DataGrid的类中写了如下方法:
1//添加表类型对象 2 public void BindData(string sqlstring) 3 { 4 this.SqlText = sqlstring; 5 6 //DbHelper类我在之前的关于数据库链接一文中已介绍过,大家可以参考一下 7 DataTable dt = DbHelper.ExecuteDataset(CommandType.Text, sqlstring).Tables[0]; 8 9 //用于标识记录数10 this.VirtualItemCount = dt.Rows.Count;1112 //下面两行代码就不用多说了吧:)13 this.DataSource = dt;14 this.DataBind();15 }16 17 public void BindData()18 {19 if ((this.SqlText != null) && (this.SqlText != ""))20 {21 BindData(this.SqlText);22 }23 24 }2526 2.加载编辑列和删除列按钮的方法如下:
1public void LoadEditColumn() 2 { 3 EditCommandColumn ecc = new EditCommandColumn();//更新按钮列 4 ecc.SortExpression = "desc"; 5 ecc.ButtonType = ButtonColumnType.LinkButton;//链接按钮 6 ecc.EditText = "编辑"; 7 ecc.UpdateText = "更新"; 8 ecc.CancelText = "取消"; 9 ecc.ItemStyle.Width = 70;10 this.Columns.AddAt(0, ecc);//增加按钮列 11 }1213 public void LoadDeleteColumn()14 {15 ButtonColumn bc = new ButtonColumn();16 bc.SortExpression = "desc";17 bc.CommandName = "Delete";18 bc.Text = "删除";19 bc.ItemStyle.Width = 70;20 this.Columns.AddAt(1, bc);//增加按钮列 21 }2223 3.在点击编辑,取消和跳转指定分页上也进行了封装如下:
1 public void EditByItemIndex(int itemindex) 2 { 3 this.EditItemIndex = itemindex; 4 BindData(); 5 } 6 7 public void Cancel() 8 { 9 this.EditItemIndex = -1;10 BindData();11 }1213 public void LoadCurrentPageIndex(int value)14 {15 this.CurrentPageIndex = (value < 0) ? 0 : value;16 BindData();17 }1819 4.点击表头的排序任务通过下面的属性来完成:
1private string sort; 2 3 [Bindable(true), Category("Appearance"), DefaultValue("")] 4 public string Sort 5 { 6 get 7 { 8 return sort; 9 }10 set11 {12 sort = value;13 SortTable(sort, (DataTable)null);14 }15 }1617 而SortTable函数的声明如下:
1public void SortTable(string SortExpression, DataTable dt) 2 { 3 DataView dv = new DataView(); 4 if (dt != null && dt.Rows.Count > 0) 5 { 6 dv = new DataView(dt); 7 } 8 else 9 {10 if (this.SqlText != null && this.SqlText != "")11 {12 dv = new DataView(DbHelper.ExecuteDataset(CommandType.Text, this.SqlText).13 Tables[0]);14 }15 else16 {17 return;18 }19 }20 dv.Sort = SortExpression.Replace("21 this.DataGridSortType;22 //dv.Sort= SortExpression+" "+this.DataGridSortType;23 this.DataSource = dv;24 this.DataBind();25 }262728 public void SortTable(string SortExpression, string sqlstring)29 {30 DataView dv = new DataView();31 if (sqlstring != null && sqlstring != "")32 {33 dv = new DataView(DbHelper.ExecuteDataset(CommandType.Text, sqlstring).34 Tables[0]);35 }36 else37 {38 return;39 }4041 dv.Sort = SortExpression.Replace("42 this.DataGridSortType;43 this.DataSource = dv;44 this.DataBind();45 }4647 而里面的DataGridSortType属性, 即标识当前是升序还是降序排列当前表字段:
1 [Description("表头的名称。"), Category("Appearance"), DefaultValue("ASC")] 2 public string DataGridSortType 3 { 4 get 5 { 6 7 object obj = ViewState["DataGridSortType"]; 8 string ascordesc = obj == null ? "ASC" : (string)obj; 9 if (ascordesc == "ASC")10 {11 ViewState["DataGridSortType"] = "DESC";12 return "DESC";13 }14 else15 {16 ViewState["DataGridSortType"] = "ASC";17 return "ASC";18 }1920 }21 set22 {23 ViewState["DataGridSortType"] = value;24 }25 }2627 5.当按表头某字段进行排序时,在字段名称后添加“向下”或“向上”箭头icon。这里为了实现和使用方便,在继承自datagrid的基础上绑定(详见构造函数)并实现了SortGrid方法(下面代码比较简单,就不做说明了):
1protected void SortGrid(Object sender, DataGridSortCommandEventArgs e) 2 { 3 4 SortTable(e.SortExpression, (DataTable)null); 5 6 foreach (System.Web.UI.WebControls.DataGridColumn dc in this.Columns) 7 { 8 if (dc.SortExpression == e.SortExpression) 9 {10 if (dc.HeaderText.IndexOf("13 {14 dc.HeaderText = dc.HeaderText.Replace("15 gif height=13>", "");16 }17 else18 {19 dc.HeaderText = dc.HeaderText.Replace("20 gif height=13>", "");21 }22 }23 else24 {25 if (this.DataGridSortType == "ASC")26 {27 dc.HeaderText = dc.HeaderText + "28 height=13>";29 }30 else31 {32 dc.HeaderText = dc.HeaderText + "33 height=13>";34 }35 }36 }37 else38 {39 dc.HeaderText = dc.HeaderText.Replace("40 }41 }42 }43 4445 6.当鼠标在数据行间移动时的背景颜色变化的效果也设置在了控件中,相应的代码段如下:
1 public void DataGrid_ItemDataBound(object sender, System.Web.UI.WebControls.DataGridItemEventArgs e) 2 { 3 if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem) 4 { 5 e.Item.Attributes.Add("onmouseover", "this.className='mouseoverstyle'"); 6 e.Item.Attributes.Add("onmouseout", "this.className='mouseoutstyle'"); 7 e.Item.Style["cursor"] = "hand"; 8 } 9 10 通过上面的一番"折腾",使用原来很好用的datagrid在使用上进一步“代码瘦身”,相应的CS代码也相应的变成了下面的样子(数据表通过运行下载包中的SQL脚本创建即可):
1protected void Page_Load(object sender, EventArgs e) 2 { 3 if (!Page.IsPostBack) 4 { 5 BindData(); 6 } 7 } 8 9 public void BindData()10 {11 //是否允许自定义分页(继承自.net datagrid)12 DataGrid1.AllowCustomPaging = false;13 //定义列表名称14 DataGrid1.TableHeaderName = "过滤词列表";15 DataGrid1.BindData("SELECT * FROM [dnt_smilies]");16 }1718 protected void Sort_Grid(Object sender, DataGridSortCommandEventArgs e)19 {20 DataGrid1.Sort = e.SortExpression.ToString();21 }2223 24 protected void DataGrid_PageIndexChanged(object source, DataGridPageChangedEventArgs e)25 {26 DataGrid1.LoadCurrentPageIndex(e.NewPageIndex);27 }2829 protected void DataGrid_Edit(Object sender, DataGridCommandEventArgs E)30 {31 DataGrid1.EditByItemIndex(E.Item.ItemIndex);32 }3334 protected void DataGrid_Cancel(Object sender, DataGridCommandEventArgs E)35 {36 DataGrid1.Cancel();37 }3839
当然,如果您觉得还是以前的.net datagrid 使用方便,这个控件也是兼容的。
上面的改进只是为了少写代码,是一种“偷懒”的行径。而下面的代码就是在microsoft datagrid基础上的订制改进了。
1.因为分样的方式要与论坛前台的"分页样式"相类似,所以要在分页页码位置之后添加诸如: 当前页码,总页数,总记录数,跳转到指定页面文本框等,所以下面的代码被开发出来。
1public void DataGrid_ItemCreated(Object sender, DataGridItemEventArgs e) 2 { 3 ListItemType elemType = e.Item.ItemType; 4 5 if (elemType == ListItemType.Pager) 6 { 7 TableCell cell1 = (TableCell)e.Item.Controls[0]; 8 cell1.HorizontalAlign = HorizontalAlign.Left; 9 cell1.VerticalAlign = VerticalAlign.Bottom;10 cell1.CssClass = "datagridPager";1112 LiteralControl splittable = new LiteralControl("splittable");13 splittable.Text = "14 ";15 cell1.Controls.AddAt(0, splittable);1617 LiteralControl PageNumber = new LiteralControl("PageNumber");18 PageNumber.Text = " ";19 if (this.PageCount <= 1)20 {21 try22 {23 cell1.Controls.RemoveAt(1); //当页数为1时, 则不显示页码24 }25 catch { ; }26 }27 else28 {29 PageNumber.Text = " ";30 }31 PageNumber.Text += "共 " + this.PageCount + " 页, 当前第 " + 32 (this.CurrentPageIndex + 1) + " 页";3334 if (this.VirtualItemCount > 0)35 {36 PageNumber.Text += ", 共 " + this.VirtualItemCount + " 条记录";37 }383940 PageNumber.Text += " " + ((this.PageCount > 1) ? "跳转到:" : "");41 cell1.Controls.Add(PageNumber);424344 //当大于1时显示跳转按钮45 if (this.PageCount > 1)46 {47 //加载跳转文件框48 GoToPagerInputText.ID = "GoToPagerInputText";49 GoToPagerInputText.Attributes.Add("runat", "server");50 GoToPagerInputText.Attributes.Add("onkeydown", "if(event.keyCode==13) 51 {52 var gotoPageID=this.name.replace('InputText','Button'); 53 return(document.getElementById(gotoPageID)).focus();}54 ");5556 GoToPagerInputText.Size = 6;57 GoToPagerInputText.Value = (this.CurrentPageIndex == 0) ? "1" :58 (this.CurrentPageIndex + 1).ToString();59 cell1.Controls.Add(GoToPagerInputText);6061 PageNumber = new LiteralControl("PageNumber");62 PageNumber.Text = "页 ";63 cell1.Controls.Add(PageNumber);6465 //加载跳转按钮 66 GoToPagerButton.ID = "GoToPagerButton";67 GoToPagerButton.Text = " Go ";68 cell1.Controls.Add(GoToPagerButton);69 }7071 e.Item.Controls.Add(cell1);7273 2. 相信用过.net datagrid 的用户对下面代码的用法会比较熟悉: (控件名).PagerStyle.Mode=PagerMode.NumericPages;
其实在一开始使用这种分类样式时,还觉得不错,但时间一长,数据一多起来就会在“上一页”和“下一页”的显示位置上出现"..."这样的链接,我问过许多用户,他们中不少人一开始都搞不清“...”都底是什么东西。于是我就想把这个表示“上一页”或“下一页”的符号用真正的汉字进行替换。所以就在这个控件中出现了下面的代码段了(接上面代码段):
1 2 //上面的代码段 3 TableCell pager = (TableCell)e.Item.Controls[0]; 4 5 for (int i = 1; i < pager.Controls.Count; i += 2) 6 { 7 Object o = pager.Controls[i]; 8 9 if (o is LinkButton)10 {11 LinkButton h = (LinkButton)o;12 if (h.Text == "" && i == 1)//pager.Controls[i].ID == "_ctl0")13 {14 h.Text = "上一页";15 continue;16 }17 if (i > 1 && h.Text == "")18 {19 h.Text = "下一页";20 continue;21 }2223 //下面JS用于当跳转页面时显示“正在加载数据”层。24 h.Attributes.Add("onclick", "javascript:document.getElementById('Layer5').innerHTML25 ='26 />正在加载数据27 ';document.getElementById('success').style.display ='block';");28 }29 if (o is Label)30 {31 Label l = (Label)o;32 if (l.Text == "" && i == 1)//l.ID == "_ctl0") 33 {34 l.Text = "上一页";35 }3637 if (i > 1 && l.Text == "")38 {39 l.Text = "下一页";40 }41 }42 }43 4445 3.为了减少因为使用控件而生成过多冗长的viewstate代码,添加了如下的属性:
1[Bindable(true), Category("Appearance"), DefaultValue("")] 2 public bool SaveDSViewState 3 { 4 get 5 { 6 object obj = ViewState["SaveDSViewState"]; 7 if (obj == null) return false; 8 else 9 {10 if (obj.ToString().ToLower() == "true")11 return true;12 else13 return false;14 }15 }16 set17 {18 ViewState["SaveDSViewState"] = value;19 }20 }21 通过上面的属性设置来判断是否EnableViewState,如下:
1if (!this.SaveDSViewState)2 {3 this.Controls[0].EnableViewState = false;4 }5 4.为了让多行编辑记录并提交更方便,还添加了IsFixConlumnControls属性,当它的值为TRUE时,则当前分页下的所有记录都以文本框的形式进行显示,也就是在文章一开始的效果图2 中显示的效果(里面的下拉列表框和复选框除外)。而相应实现代码如下所示:
1 public void DataGrid_ItemDataBound(object sender, System.Web.UI.WebControls.DataGridItemEventArgs e) 2 { 3 4 5 if (this.IsFixConlumnControls) 6 { 7 if (e.Item.ItemType == ListItemType.Item || e.Item.ItemType == ListItemType.AlternatingItem) 8 { 9 for (int i = 0; i < e.Item.Cells.Count; i++)10 {11 if ((!e.Item.Cells[i].HasControls()))12 {13 //判断是否存在只读属性14 if (GetBoundColumnFieldReadOnly()[i].ToString().ToLower() == "false") 15 {16 Discuz.Control.TextBox t = new Discuz.Control.TextBox();17 t.ID = GetBoundColumnField()[i].ToString();18 t.Text = e.Item.Cells[i].Text.Trim().Replace(" ", "");1920 //设置宽度21 if (this.Columns[i].ItemStyle.Width.Value > 0)22 {23 t.Width = (int)this.Columns[i].ItemStyle.Width.Value;24 }25 else26 {27 t.Width = 100;28 }2930 e.Item.Cells[i].Controls.Add(t);31 }3233 }34 else35 {36 foreach (System.Web.UI.Control c in e.Item.Cells[i].Controls)37 {38 //加载discuz!nt下拉列表框控件39 if (c is Discuz.Control.DropDownList)40 {41 Discuz.Control.DropDownList __dropdownlist = (Discuz.Control.DropDownList)c;42 if (__dropdownlist.SqlText != "")43 {44 __dropdownlist.AddTableData(__dropdownlist.SqlText);45 }4647 try48 {49 __dropdownlist.SelectedValue = Convert.ToString(DataBinder.Eval(e.Item.50 DataItem, __dropdownlist.DataValueField));51 }52 catch53 { ;}54 }5556 //加载普通下拉控件57 if (c is System.Web.UI.WebControls.DropDownList)58 {59 System.Web.UI.WebControls.DropDownList __dropdownlist = (System.Web.UI.60 WebControls.DropDownList)c;61 try62 {63 __dropdownlist.SelectedValue = Convert.ToString(DataBinder.Eval(e.Item.64 DataItem, __dropdownlist.DataValueField));65 }66 catch67 { ;}68 }6970 }71 }7273 7475 同时为便于得到指定行的控件的属性值,还添加了下面两个方法:
1//得到指定行的控件字段的值2 public string GetControlValue(int controlnumber, string fieldname)3 {4 return DNTRequest.GetFormString(this.ClientID.Replace("_", ":") + ":_ctl" + (controlnumber + 3) + 5 ":" + fieldname);6 }7
1//得到指定行的CheckBox控件字段的值 2 public bool GetCheckBoxValue(int controlnumber, string fieldname) 3 { 4 string selectcontrolvalue = GetControlValue(controlnumber, fieldname); 5 if (selectcontrolvalue == "on") 6 { 7 return true; 8 } 9 else10 {11 return false;12 }13 }14 当然为了设置和使用的方便和提高开发效率,还有一些属性和方法要么被重写(如DataSource等),要么被添加了进来(如TableHeaderName等)。大家可以详细看一下包中的源码即可, 这里就不再多说了:)
说句心里话:
对于这个控件,从公司产品开发的角度来说,基本上满足需要了:)
但从我个人角度来看却不那么让人满意,原因就是看到了Component Art 里面的Datagrid源码(当然它里面的其它控件也同样优秀),让我感到“无地自容”。甚至相当长的一段时间里我都不想(或者说不敢)再写任何东西,因为写出来的代码和功能跟人家的东西一对比,就感到自己写的就是“垃圾”。这段时间大约持约了一个多月。相信园子里的朋友中有些人会有类以的经历吧!
但过后慢慢自信心就恢复了过来,因为如果放弃不写代码,水平就会停止下来,而以前所积攒的问题也永远得不到解决,所以还是逆着头皮“上路”了。现在回过头来看,一年前的这种心态真是很害人,我甚至想起以前温瑞安的一本武侠小说时中的人物,那位大侠自出师以来就是每战必败,江湖人称“逢打必败”冯无极(具体名字记不清了)。人家在那种情况下居然都能无所谓,而自己这些年来自认成熟了不少,但还有这种心理,现在回想起来实在是可笑。
好了,主要是东西就先交待到这里了。如果大家有什么问题或建议,欢迎与我交流。 我的邮件是daizhj@discuz.com, daizhj617595@126.com 下载链接:/Files/daizhj/datagrid_Controls_Test.rar
来源:cnblogs
赞