概述
前两篇分别介绍了Ext.NET-基础 和 Ext.NET-布局,从本篇开始我们尽量做一些实际工作中用到的例子。
在Ext.NET官方示例中,关于GridPanel的例子是最多的(近百个),篇幅所限,我们这里只介绍一些常用的功能,包括页面布局、新建、编辑、查询、删除、排序、分组、统计等功能。
示例代码下载地址>>>>>
页面总体布局
首先,再来熟悉下前一篇Ext.NET-布局篇中提到的布局技术。
新建WebForm页面,在ASPX文件中加入如下代码:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs"
Inherits="WebFormDemo.GridDemo.Default" %>
Grid示例
以上代码在页面中加入了一个铺满整个浏览器窗口的GridPanel控件gridMain
;顶端为工具栏,我们打算放一些动作按钮:如新建、删除等;底部未分页工具栏,用于GridPanel的分页;以上代码运行效果如下
加入列
上面的代码组织了页面的整体布局,而目前的GridPanel里还没有任何实质性的内容,接下来我们为GridPanel添加需要显示的列。
在和
之间加入如下代码
以上代码为GridPanel定义了一个ColumnModel,并定义了ColumnModel所包含的列,需要说明的是
行号,将为GridPanel自动生成行号;
中的Flex="1"
指定了此列将占满剩余宽度,多个时自动计算各自宽度,与Ext.NET-布局篇中介绍的Flex一致。
Ext.NET与ExtJS中的列类型对照
Ext.NET不仅封装了ExtJS提供的列类型,并加入了一些自己的扩展,如下表
Ext.NET类型 | ExtJs类型 | 说明 |
---|---|---|
ActionColumn | Ext.grid.column.Action | |
BooleanColumn | Ext.grid.column.Boolean | |
CheckColumn | Ext.grid.column.Check | |
Column | Ext.grid.column.Column | |
CommandColumn | - | Ext.Net扩展 |
ComponentColumn | - | Ext.Net扩展 |
DateColumn | Ext.grid.column.Date | |
HyperlinkColumn | - | Ext.Net扩展,3.x新加,参见此处 |
ImageCommandColumn | - | Ext.Net扩展 |
NumberColumn | Ext.grid.column.Number | |
ProgressBarColumn | - | Ext.Net扩展 |
RatingColumn | - | Ext.Net扩展 |
RowNumbererColumn | Ext.grid.column.RowNumberer | |
SummaryColumn | - | Ext.Net扩展,已过时 |
TagColumn | - | Ext.Net扩展 |
TemplateColumn | Ext.grid.column.Template | |
TreeColumn | Ext.tree.Column | |
WidgetColumn | Ext.grid.column.Widget | Ext.NET3.x新加,参见此处 |
上表是Ext.NET与ExtJS列类型对照,每种类型的列提供了一些特殊功能,篇幅所限,不一一介绍,可参见链接指向的说明或示例。
数据库及CRUD代码
目前为止,页面布局完成了一大半,实际的工作中,我们的数据一般是存储在数据库中,所以,为了接下来的例子更贴近实际,先来创建数据库,本例中使用MS SQL Server 2012(Developer Edition)。
数据库访问相关的技术不是本文的主要目的,为了使示例代码尽量简单,这里我们使用MS提供的SqlHelper类,详情参见Data Access Application Block for .NET,下载地址.
创建数据库
创建名为ExtNetDemo的数据库加入测试数据,并执行如下脚本创建Person表:
CREATE TABLE [dbo].[Person]
(
[Id] UNIQUEIDENTIFIER NOT NULL PRIMARY KEY,
[Name] NVARCHAR(50) NULL,
[Age] INT NULL,
[Gender] BIT NULL,
[Birthdate] DATETIME NULL,
[Ethnic] NVARCHAR(50) NULL,
[Origo] NVARCHAR(200) NULL,
[Remarks] NVARCHAR(1000) NULL
)
INSERT INTO [dbo].[Person] ([Id], [Name], [Age], [Gender], [Birthdate], [Ethnic], [Origo], [Remarks])
VALUES (N'f0d1d8bf-31f4-49c1-ac69-898138356b75', N'任盈盈', 18, 0, N'2015-08-08 00:00:00', N'汉族', N'陕西西安', N'备注')
INSERT INTO [dbo].[Person] ([Id], [Name], [Age], [Gender], [Birthdate], [Ethnic], [Origo], [Remarks])
VALUES (N'b1fef104-195d-422c-b729-f0c81149b6f0', N'令狐冲', 26, 1, N'2015-05-05 00:00:00', N'回族', N'河北沧州', N'备注说明')
INSERT INTO [dbo].[Person] ([Id], [Name], [Age], [Gender], [Birthdate], [Ethnic], [Origo], [Remarks])
VALUES (N'02ddad1a-91f0-4ec3-8d2b-3ed01c6fda6e', N'风清扬', 35, 1, N'1960-02-05 00:00:00', N'汉族', N'陕西渭南', N'大神')
加入实体类
我们继续使用Ext.NET-基础篇中使用的Person类,修改后的代码如下:
using System;
namespace WebFormDemo
{
public class Person
{
public Guid Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public bool Gender { get; set; }
public DateTime Birthdate { get; set; }
public string Ethnic { get; set; }
public string Origo { get; set; }
public string Remarks { get; set; }
public string Desc { get; set; }
}
}
加入CRUD代码
CRUD代码很枯燥,也不是本文重点。建议用成熟的ORM库或代码生成工具来完成,这里为了使例子简单而尽量不引入更多的库。
在ASPX.CS文件中加入如下代码
#region DAL
private readonly string m_connectionString = ConfigurationManager.ConnectionStrings["default"].ConnectionString;
private IEnumerable GetPersonList(string where, string orderBy,
IEnumerable paras, int pageIndex, int pageSize)
{
where = String.IsNullOrEmpty(where) ? "1=1" : where;
orderBy = String.IsNullOrEmpty(orderBy) ? "Id desc" : orderBy;
pageIndex = pageIndex > 0 ? pageIndex : 1;
pageSize = pageSize > 0 ? pageSize : 20;
int _rowStart = (pageIndex - 1) * pageSize + 1;
int _rowEnd = pageIndex * pageSize;
List result = new List();
String sql =
String.Format("SELECT * FROM ( SELECT ROW_NUMBER() OVER ( ORDER BY {0} ) AS RowNumber,* FROM [" +
"Person ] WHERE {1}) T WHERE T.RowNumber BETWEEN {2} AND {3} ",
orderBy, where, _rowStart, _rowEnd);
var ds = SqlHelper.ExecuteDataset(m_connectionString, System.Data.CommandType.Text, sql, paras.ToArray());
if (ds != null && ds.Tables.Count > 0 &&
ds.Tables[0].Columns.Count > 0 && ds.Tables[0].Rows.Count > 0)
{
foreach (DataRow row in ds.Tables[0].Rows)
{
Person p = new Person();
p.Age = row.Field("Age");
p.Birthdate = row.Field("Birthdate");
p.Ethnic = row.Field("Ethnic");
p.Gender = row.Field("Gender");
p.Id = row.Field("Id");
p.Name = row.Field("Name");
p.Origo = row.Field("Origo");
p.Remarks = row.Field("Remarks");
result.Add(p);
}
}
return result;
}
public int GetRecordCount(string where, IEnumerable paras)
{
int _result = 0;
where = String.IsNullOrEmpty(where) ? "1=1" : where;
string _sql = "SELECT COUNT(1) FROM [Person] ";
if (!String.IsNullOrWhiteSpace(where))
{
_sql += " WHERE " + where;
}
Object _obj = SqlHelper.ExecuteScalar(m_connectionString, CommandType.Text, _sql, paras.ToArray());
if (!Object.Equals(null, _obj) && !Object.Equals(DBNull.Value, _obj))
{
Int32.TryParse(_obj.ToString(), out _result);
}
return _result;
}
public bool Insert(Person p)
{
if (p == null)
{
return false;
}
p.Id = Guid.NewGuid();
StringBuilder _sb = new StringBuilder();
_sb.Append("INSERT INTO [Person] ");
_sb.Append("([Id],[Name],[Age],[Gender],[Birthdate],[Ethnic],[Origo],[Remarks])");
_sb.Append(" VALUES ");
_sb.Append("(@Id,@Name,@Age,@Gender,@Birthdate,@Ethnic,@Origo,@Remarks)");
SqlParameter[] _paras = {
new SqlParameter("@Id", p.Id),
new SqlParameter("@Name", p.Name),
new SqlParameter("@Age", p.Age),
new SqlParameter("@Gender", p.Gender),
new SqlParameter("@Birthdate", p.Birthdate),
new SqlParameter("@Ethnic", p.Ethnic),
new SqlParameter("@Origo", p.Origo),
new SqlParameter("@Remarks", p.Remarks)
};
int _rowsAffected = 0;
_rowsAffected = SqlHelper.ExecuteNonQuery(m_connectionString,
CommandType.Text, _sb.ToString(), _paras);
return _rowsAffected > 0;
}
public bool Update(Person p)
{
if (p == null)
{
return false;
}
StringBuilder _sb = new StringBuilder();
_sb.Append("UPDATE [Person] SET ");
_sb.Append("[Id]=@Id,[Name]=@Name,[Age]=Age,[Gender]=@Gender,");
_sb.Append("[Birthdate]=@Birthdate,[Ethnic]=@Ethnic,[Origo]=@Origo,[Remarks]=@Remarks ");
_sb.Append(" WHERE [Id]=@Id");
SqlParameter[] _paras = {
new SqlParameter("@Id", p.Id),
new SqlParameter("@Name", p.Name),
new SqlParameter("@Age", p.Age),
new SqlParameter("@Gender", p.Gender),
new SqlParameter("@Birthdate", p.Birthdate),
new SqlParameter("@Ethnic", p.Ethnic),
new SqlParameter("@Origo", p.Origo),
new SqlParameter("@Remarks", p.Remarks)
};
int _rowsAffected = 0;
_rowsAffected = SqlHelper.ExecuteNonQuery(m_connectionString,
CommandType.Text, _sb.ToString(), _paras);
return _rowsAffected > 0;
}
public bool Delete(IEnumerable ids)
{
if (ids == null || ids.Count() < 1)
{
return false;
}
string sql = "DELETE FROM [Person] WHERE ";
string where = String.Empty;
foreach (var key in ids)
{
if (String.IsNullOrEmpty(where))
{
where = String.Format(" [Id]='{0}' ", key);
}
else
{
where += String.Format(" OR [Id]='{0}' ", key);
}
}
sql += where;
int _rowsAffected = 0;
_rowsAffected = SqlHelper.ExecuteNonQuery(m_connectionString, CommandType.Text, sql);
return _rowsAffected > 0;
}
#endregion
加入Store
GridPanel必须与Store结合使用才能显示数据,这通常令Ext.NET新手感到不习惯,可以理解为Store为GridPanel提供了数据源,可参见ExtJs API中关于 GridPanel Store的说明。
Store通常也用在TreePanel、ComboBox中,当然,Store也可独立存在做其它使用。Store对多个实体对象进行封装,并在客户端缓存;Store通过Proxy加载数据,并提供了对实体对象集合的排序、查询等功能。
关于Store的详细说明参见 Ext.data.Store。
为GridPanel加入Store可以使用两种方式:
- GridPanel的StoreID属性指定一个外部定义的Store的ID;
- 在GridPanel中定义,如
。此处定义真实的Store
定义Store
本例中我们使用后者,在
中加入如下代码:
如上,Store中的Model定义了一个实体类型,与我们在服务器端的Person
类相似,不过此处的Model存在于客户端浏览器中。
中的IDProperty="Id"
指明了Model中的主键,默认值为"id"
;
中定义了实体的字段;- 其中
Type="Boolean"
等指明了字段的数据类型,可选项有Auto
、Boolean
、Date
、Float
、Int
、Object
和String
,默认为Auto
。
详细说明请参考:
- Ext.data.Store
- Ext.data.Model
- Ext.data.field.Field
为Column指定DataIndex
上面我们为GridPanel定义好了Store,那么,如何使GridPanel中显示的列与Store.Model中定义的Field对应上呢?也就是说,如何为GridPanel指定每列要显示的内容呢?
答案是为Columns
中的每一列设置DataIndex
属性,修改前面指定的
中的每列Column的DataIndex属性,如下
其中的每个DataIndex值,与上面定义的ModelField
中的Name
对应关系,注意大小写需要一致;
为Store绑定服务器端数据库数据
以上,服务器端CRUD代码已经就绪,GridPanel的显示已经就绪,接下来我们来将数据库中的数据显示到GridPanel中来。
修改ASPX文件中Store的代码,如下
...代码同上
为Store加入了OReadData服务器端事件,并加入了
。
OnReadData="Main_ReadData"
:这个没什么可说的,就是个传统的ASP.NET事件,签名有点特殊而已;
:为Store指明了Proxy,Proxy负责为Store加载数据,其中PageProxy是Ext.NET从Ext.data.proxy.Server扩展,Ext.NET官方API文档中也没有更多说明,关于Proxy的说明,参见Ext.data.proxy.Proxy。
接着我们需要实现服务器端Main_ReadData
事件(按条件过滤查询后面再介绍),服务器端ASPX.CS文件中加入如下代码
protected void Main_ReadData(object sender, StoreReadDataEventArgs e)
{
Store store = sender as Store;
string where = "1=1";
string orderBy = "Id desc";
e.Total = GetRecordCount(where, new List());
store.DataSource = GetPersonList(where, orderBy,
new List(), e.Page, e.Limit);
store.DataBind();
}
这样,当浏览器中打开页面时自动调用了Main_ReadData
方法,客户端的Store中也就自动加载了数据并使其显示在GridPanel中,效果如下:
可以使用Sencha官方提供的Chrome扩展App Inspector for Sencha(墙外链接)查看Store中的数据,如下
此时Ext.NET生成的ExtJS代码如下(查看页面源代码可见):
Ext.net.ResourceMgr.init({
id: "ctl01",
theme: "crisp",
icons: ["Add", "Delete"]
});
Ext.onReady(function() {
Ext.create("Ext.container.Viewport", {
renderTo: Ext.getBody(),
items: [{
store: {
model: Ext.ClassManager.isCreated(Ext.id()) ? Ext.id() : Ext.define(Ext.id(), {
extend: "Ext.data.Model",
fields: [{
name: "Id"
},
{
name: "Name",
type: "string"
},
{
name: "Age",
type: "int"
},
{
name: "Gender",
type: "boolean"
},
{
name: "Birthdate",
type: "date",
dateFormat: "c"
},
{
name: "Ethnic"
},
{
name: "Origo"
},
{
name: "Remarks"
}],
idProperty: "Id"
}),
storeId: "storeMain",
autoLoad: true,
proxy: {
type: "page"
}
},
id: "gridMain",
xtype: "grid",
dockedItems: [{
dock: "top",
xtype: "toolbar",
items: [{
id: "btnAdd",
iconCls: "#Add"
},
{
xtype: "tbseparator"
},
{
id: "banAdd",
iconCls: "#Delete"
}]
},
{
dock: "bottom",
xtype: "pagingtoolbar",
displayInfo: true,
store: "storeMain"
}],
columns: {
items: [{
xtype: "rownumberer"
},
{
dataIndex: "Name",
text: "姓名"
},
{
xtype: "numbercolumn",
dataIndex: "Age",
text: "年龄"
},
{
xtype: "booleancolumn",
dataIndex: "Gender",
text: "性别",
falseText: "女",
trueText: "男"
},
{
xtype: "datecolumn",
dataIndex: "Birthdate",
text: "生日",
format: "Y-m-d"
},
{
dataIndex: "Ethnic",
text: "民族"
},
{
flex: 1,
dataIndex: "Origo",
text: "籍贯"
},
{
hidden: true,
flex: 1,
dataIndex: "Remarks",
text: "备注"
}]
}
}],
layout: "fit"
});
});
查询和排序
上面我们使GridPanel显示数据库中的数据,但实际应用中我们一般都需要为客户提供按条件查询的功能,下面介绍Ext.NET/ExtJS中GridPanel的查询。。
最常见的查询条件有:
- 字符串:一般作为模糊查询;
- 数字:区间查询;
- 日期:区间查询;
前面讲到Store提供了对实体对象集合的排序、查询等功能,因为Store存在于客户端,也就是Store提供了客户端的查询、排序功能,先来看看客户端的查询、排序功能。
客户端查询&排序
客户端的排序功能其实前面的已经实现了,由Store本身提供,定义好Store排序功能其实已经实现了。浏览器中单击GridPanel的列头就可以在正序和倒序之间切换。
实现客户端的查询也很简单,修改GridPanel中的ColumnModel并加入GridFilters,代码如下
...略去部分代码
Ext.NET3.X定义方式与Ext.NET 2.X系列不一样,以前的定义方式参见Ext.NET2.5查询示例。
在列的右侧单击下拉箭头,输入或选择查询条件就可以进行客户端查询,如下图:
如图查询生日为2015-05-10之前的数据,客户端查询出来的结果如上图。
关于客户端的查询、排序就介绍到这里,更多可参见GridPanel with Local Filtering, Sorting and Paging。
服务器端查询&排序
客户端的查询排序有其方便之处,但假如使用的是服务器端分页的话,客户端的查询/排序仅对当前页有效。
接着,再来看看服务器端如何查询和排序,与客户端的查询排序大致相同,不过需要服务器端写一些代码,并在客户端的Store上加几个属性就可以。
修改ASPX中的Store,代码如下
...略去部分代码
添加了RemoteFilter="true" RemoteSort="true"
指明这个Store需要服务器端查询和排序。
接着我们再来修改服务器端ASPX.CS文件中Main_ReadData
方法代码,如下
protected void Main_ReadData(object sender, StoreReadDataEventArgs e)
{
#region 查询条件
StringBuilder sbFilter = new StringBuilder();
List paras = new List();
sbFilter.Append(" 1=1 ");
string filtersStr = e.Parameters["filter"];
if (!String.IsNullOrEmpty(filtersStr))
{
FilterConditions fs = new FilterConditions(filtersStr);
foreach (FilterCondition fc in fs.Conditions)
{
string field = fc.Field;
string comparison = fc.Comparison == Comparison.Eq ? "=" :
fc.Comparison == Comparison.Gt ? ">" :
fc.Comparison == Comparison.Lt ? "<" : "";
switch (fc.Type)
{
case FilterType.String:
sbFilter.AppendFormat(" AND {0} LIKE @{0} ", field);
paras.Add(new SqlParameter("@" + field,
"%" + fc.Value() + "%"));
break;
case FilterType.Boolean:
sbFilter.AppendFormat("AND {0} = @{0}", field);
paras.Add(new SqlParameter("@" + field,
fc.Value()));
break;
case FilterType.Date:
DateTime theDate = fc.Value();
sbFilter.AppendFormat(" AND ([{0}] {1} '{2}') ",
field, comparison, theDate);
break;
case FilterType.Number:
if (field == "Age")
{
int age = fc.Value();
sbFilter.AppendFormat(" AND ([Age] {0} {1}) ",
comparison, age);
}
break;
case FilterType.List:
string whereList = String.Empty;
for (int i = 0; i < fc.List.Count; i++)
{
whereList += String.Format(" {0} ([{1}] LIKE @{1}{2}) ",
i > 0 ? "OR" : "", field, i);
paras.Add(new SqlParameter(
String.Format("@{0}{1}", field, i),
String.Format("%{0}%", fc.List[i])
));
}
if(!String.IsNullOrEmpty(whereList))
{
sbFilter.AppendFormat(" AND ({0}) ", whereList);
}
break;
default:
break;
}
}
}
#endregion
#region 排序
StringBuilder sbSort = new StringBuilder();
foreach (DataSorter sort in e.Sort)
{
if (sbSort.Length > 0)
{
sbSort.Append(",");
}
sbSort.AppendFormat(" {0} {1} ", sort.Property,
sort.Direction == Ext.Net.SortDirection.ASC ? "ASC" : "DESC");
}
if (sbSort.Length <= 0)
{
sbSort.Append(" Id DESC ");
}
#endregion
Store store = sender as Store;
//总行数
e.Total = GetRecordCount(sbFilter.ToString(), paras);
//给Store绑定数据
store.DataSource = GetPersonList(sbFilter.ToString(), sbSort.ToString(),
paras, e.Page, e.Limit);
store.DataBind();
}
如上第8行string filtersStr = e.Parameters["filter"]
获取了客户端浏览器传递过来以JSON方式描述的的查询条件,这个参数怎么来的?是由上面提到Store中的Proxy来处理并提交给服务器端的,此处的
是Ext.NET由Ext.data.proxy.Server扩展来的,此处的filter
参数详见关于filterParam说明。
如上代码中用到的e.Sort
、e.Total
也是由Proxy来处理的。
现在运行如下图所示,查询条件为:年龄>18岁&&性别=男;
用App Inspector for Sencha(墙外链接)查看Store中的数据,如下
清空查询条件
当GridPanel中的列较多时,当输入了一堆查询条件,要显示全部数据时需要逐一清除,为了使操作更方便,这时需要加入一个清空查询条件的功能。
在
之间加入如下代码,也就是在分页工具栏中放一个按钮,使用户可以一次清除所有的查询条件,代码如下
clearFilters详细说明。
关于查询过滤就介绍到这里,篇幅有限,也就不一一介绍了,更多的请参见Ext.NET官方示例,提示,在官方示例左上方的查询框里输入filter
可看到所有关于查询过滤的例子。
分组统计&合计
Ext.NET/ExtJS为GridPanel提供了丰富的插件,分组插件就是其中一个,使用起来也很简单。
我们介绍下客户端分组及统计功能的使用。
在
中加入如下代码
...略去部分代码
其中GroupingSummary
表示分组统计;Summary
表示合计;
在GridPanel的Columns中指定统计方式,修改Columns如下
Groupable="false"
指明是否可以用当前列分组;SummaryType="Average"
指明了统计方式,默认提供的统计方式有Average
(平均值)、Count
(数量)、Max
(最大值)、Min
(最小值)、None
(不统计)、Sum
(求和)。
自定义了统计时的呈现方式,此处调用了Ext.Number.toFixed方法个使只显示2位小数。
下图展示了以民族分组的效果,在民族列右侧单击下拉箭头,下拉菜单中单击以此分组,效果如下:
若需要页面加载时就以民族列分组显示,只需要在Store上加入GroupField属性即可,
...略去部分代码
GroupField="Ethnic"
指明了默认以Ethnic
字段分组。
以上介绍了客户端的分组统计&合计功能,关于服务器端的分组统计&合计与这个类似,具体可参见官方示例Grouping with Remote Summary和Grouping TotalRow,第二个例子中javascript方法updateTotal
中,使用Ext.Net-基础篇中介绍的DirectMethod就可以实现服务器端的合计功能,这里不再赘述。
新建&编辑&From布局
接下来,再来看看如何实现新建和编辑。
Form布局
在ASPX文件中中加入如下Window代码,
Hidden="true"
表示这个Window是隐藏的,调试布局时可以设置为false,关于Window属性更多的说明请参见上一篇Ext.NET-布局篇中关于Window的介绍;- 使用FormPanel铺满整个Window区域,FormPanel的子控件使用VBoxLayout纵向布局,每个FieldContainer内部采用HBoxLayout横向布局,关于FieldContainer的示例可参见此处;
- FormPanel中
分别配置了Label居左,宽度30px、错误消息显示在右边,可参见MsgTarget说明;
以前介绍过,横向拉伸;- 隐藏域
是为新建/编辑时使用的; - 我们为每个控件指定了
Name
属性而没有指明ID,这也是为新建和编辑时使用的,下面会介绍,可参考Name属性说明。
一个纵横交错的From布局就完成了,效果如下
新建
上面完成了Form的布局,接下来我们完成新建的实际动作;
1.首先修改winMain
的Hidden
属性值为true
,因为我们不希望页面刚打开就蹦出来一个编辑窗口;接着修改GridPanel上方工具栏中的新建按钮代码如下:
2.新建default.js文件;
default.js
var btnAdd_Click = function (sender, e) {
//设置标题
App.winMain.setTitle('新增');
//App.winMain.setIconCls('fa fa-plus-circle');
//显示Window
App.winMain.show(sender);
};
/*
* 服务器端执行成功时
*/
var btnMainSave_Success = function (response, result,
control, type, action, extraParams) {
var form = App.frmMain;
//还原From中内容
form.getForm().reset();
//关闭Window
App.winMain.close();
//刷新Grid数据
App.gridMain.getStore().reload();
//提示成功
Ext.net.Notification.show({
html: '保存成功',
title: '提示'
});
};
/*
* 服务器端执行失败时
*/
var btnMainSave_Failure = function (response, result,
control, type, action, extraParams) {
var msg = result.errorMessage;
//提示失败
Ext.net.Notification.show({
html: msg,
title: '提示'
});
};
3.在ASPX文件中加入引用
4.以上,前端的新建界面布局完成了,接下来我们来完成实际保存的后端代码,修改Window中的btnMainSave
代码如下
OnEvent="btnMainSave_Click"
表示服务器端DirectEvent事件;Success="btnMainSave_Success"
服务器端DirectEvent执行成功时在客户端执行的javascript方法,在上面的default.js已定义;Failure="btnMainSave_Failure"
服务器端DirectEvent执行失败时在客户端执行的javascript方法,在上面的default.js已定义;
中定义了此DirectEvent提交给服务器端的参数,此处用
定义了一个名称为model
的参数,其中Mode="Raw"
表示运算后的值,value="#{frmMain}.getForm().getFieldValues()"
表示获取frmMain
的所有Field中的值,前提是为每个Field设置了Name属性,如我们在年龄Field中定义的Name="Age"
,关于getFieldValues方法的详细说明可参见Ext.form.Basic.getFieldValues。
5.接着再来完成保存的服务器端DirectEvent事件,在aspx.cs文件中加入如下代码
protected void btnMainSave_Click(object sender, DirectEventArgs e)
{
//客户端传过来的model参数
string personJson = e.ExtraParams["model"];
//反序列化为Person对象
var model = JSON.Deserialize(personJson);
//服务器端验证示例
if (String.IsNullOrEmpty(model.Name))
{
e.ErrorMessage = "姓名不能为空";
e.Success = false;
return;
}
try
{
//若Id为空则新建,否则为更新
e.Success = Guid.Empty == model.Id ? Insert(model) : Update(model);
}
catch(Exception ex)
{
//logs...ex
e.ErrorMessage = "保存失败" + ex.Message;
e.Success = false;
}
}
以上,我们新建功能就完成了,当点击GridPanel上方的新增按钮将弹出winMain
,填写完成后单击保存
按钮,调用服务器端的btnMainSave_Click
方法将数据存储到数据库中,关闭window,并提示保存成功。
接下来再看看编辑功能。
编辑
由于上面的代码已经为编辑功能做好了铺垫,所以这里的编辑功能实现起来相对要简单的多。
1.在GridPanel中每行的放置一个编辑按钮,
...略去部分代码
...略去部分代码
...略去部分代码
2.点击编辑按钮时为
frmMain
加载数据并显示winMain
,在default.js中加入gridMain_Command方法
var gridMain_Command = function (item, command,
record, recordIndex, cellIndex) {
switch (command) {
case 'edit':
var form = App.frmMain;
form.getForm().reset();
form.loadRecord(record);
App.winMain.show(item);
break;
default:
break;
}
};
其中form.loadRecord(record);
为frmMain
从Store缓存的数据中加载数据到Form界面上,当然,这也得益于为每个Field定义了Name属性,关于loadRecord的说明参见Ext.form.Basic.loadRecord。
如此,编辑功能就完成了,当点击每行的编辑按钮时,将弹出编辑Window,如下图所示
删除
最开始时,我们已经定义好了删除按钮,现在是时候发挥它的作用了。
这里我们用最常见的多选删除。
1.为GridPanel添加CheckBox。
...略去部分代码
这里用客户端Listener事件SelectionChange
使选择行后btnDelete
按钮可用。
2.修改删除按钮
关于models
参数的说明:getRowsValues
方法是Ext.NET对GridPanel的扩展方法,此处的作用为获取选择的行数据,下面是此方法参数的说明
// config :
// - selectedOnly
// - visibleOnly
// - dirtyCellsOnly
// - dirtyRowsOnly
// - currentPageOnly
// - excludeId
// - filterRecord - function (record) - return false to exclude the record
// - filterField - function (record, fieldName, value) - return false to exclude the field for particular record
// -ignoreSubmitEmptyValue - true to ignore the ModelFields' SubmitEmptyValue option; defaults to false
getRowsValues : function getRowsValues (config) {//...略}
3.添加服务器端DirectEvent
ASPX.CS文件中添加btnDelete_Click
方法,代码如下
protected void btnDelete_Click(object sender,DirectEventArgs e)
{
string json = e.ExtraParams["models"];
var personList = JSON.Deserialize>(json);
var ids = from p in personList
select p.Id;
try
{
Delete(ids.ToList());
e.Success = true;
}
catch (Exception ex)
{
//logs...
e.ErrorMessage = "删除失败." + ex.Message;
e.Success = false;
}
}
如上,删除功能也完成了。
总结
如上,我们介绍了一个完整的GridPanel使用实例,完整的代码如下
ASPX
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs"
Inherits="WebFormDemo.GridDemo.Default" %>
Grid示例
javascript文件
var btnAdd_Click = function (sender, e) {
//设置标题
App.winMain.setTitle('新增');
//App.winMain.setIconCls('fa fa-plus-circle');
//显示Window
App.winMain.show(sender);
};
/*
* 服务器端执行成功时
*/
var btnMainSave_Success = function (response, result,
control, type, action, extraParams) {
var form = App.frmMain;
//还原From中内容
form.getForm().reset();
//关闭Window
App.winMain.close();
//刷新Grid数据
App.gridMain.getStore().reload();
//提示成功
Ext.net.Notification.show({
html: '保存成功',
title: '提示'
});
};
/*
* 服务器端执行失败时
*/
var btnMainSave_Failure = function (response, result,
control, type, action, extraParams) {
var msg = result.errorMessage;
//提示失败
Ext.net.Notification.show({
html: msg,
title: '提示'
});
};
var gridMain_Command = function (item, command,
record, recordIndex, cellIndex) {
switch (command) {
case 'edit':
var form = App.frmMain;
form.getForm().reset();
form.loadRecord(record);
App.winMain.show(item);
break;
default:
break;
}
};
ASPX.CS
using Ext.Net;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
namespace WebFormDemo.GridDemo
{
public partial class Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
}
protected void Main_ReadData(object sender, StoreReadDataEventArgs e)
{
#region 查询条件
StringBuilder sbFilter = new StringBuilder();
List paras = new List();
sbFilter.Append(" 1=1 ");
string filtersStr = e.Parameters["filter"];
if (!String.IsNullOrEmpty(filtersStr))
{
FilterConditions fs = new FilterConditions(filtersStr);
foreach (FilterCondition fc in fs.Conditions)
{
string field = fc.Field;
string comparison = fc.Comparison == Comparison.Eq ? "=" :
fc.Comparison == Comparison.Gt ? ">" :
fc.Comparison == Comparison.Lt ? "<" : "";
switch (fc.Type)
{
case FilterType.String:
sbFilter.AppendFormat(" AND {0} LIKE @{0} ", field);
paras.Add(new SqlParameter("@" + field,
"%" + fc.Value() + "%"));
break;
case FilterType.Boolean:
sbFilter.AppendFormat("AND {0} = @{0}", field);
paras.Add(new SqlParameter("@" + field,
fc.Value()));
break;
case FilterType.Date:
DateTime theDate = fc.Value();
sbFilter.AppendFormat(" AND ([{0}] {1} '{2}') ",
field, comparison, theDate);
break;
case FilterType.Number:
if (field == "Age")
{
int age = fc.Value();
sbFilter.AppendFormat(" AND ([Age] {0} {1}) ",
comparison, age);
}
break;
case FilterType.List:
string whereList = String.Empty;
for (int i = 0; i < fc.List.Count; i++)
{
whereList += String.Format(" {0} ([{1}] LIKE @{1}{2}) ",
i > 0 ? "OR" : "", field, i);
paras.Add(new SqlParameter(
String.Format("@{0}{1}", field, i),
String.Format("%{0}%", fc.List[i])
));
}
if (!String.IsNullOrEmpty(whereList))
{
sbFilter.AppendFormat(" AND ({0}) ", whereList);
}
break;
default:
break;
}
}
}
#endregion
#region 排序
StringBuilder sbSort = new StringBuilder();
foreach (DataSorter sort in e.Sort)
{
if (sbSort.Length > 0)
{
sbSort.Append(",");
}
sbSort.AppendFormat(" {0} {1} ", sort.Property,
sort.Direction == Ext.Net.SortDirection.ASC ? "ASC" : "DESC");
}
if (sbSort.Length <= 0)
{
sbSort.Append(" Id DESC ");
}
#endregion
Store store = sender as Store;
//总行数
e.Total = GetRecordCount(sbFilter.ToString(), paras);
//给Store绑定数据
store.DataSource = GetPersonList(sbFilter.ToString(), sbSort.ToString(),
paras, e.Page, e.Limit);
store.DataBind();
}
protected void btnMainSave_Click(object sender, DirectEventArgs e)
{
//客户端传过来的model参数
string personJson = e.ExtraParams["model"];
//反序列化为Person对象
var model = JSON.Deserialize(personJson);
//服务器端验证示例
if (String.IsNullOrEmpty(model.Name))
{
e.ErrorMessage = "姓名不能为空";
e.Success = false;
return;
}
try
{
//若Id为空则新建,否则为更新
e.Success = Guid.Empty == model.Id ? Insert(model) : Update(model);
}
catch (Exception ex)
{
//logs...ex
e.ErrorMessage = "保存失败" + ex.Message;
e.Success = false;
}
}
protected void btnDelete_Click(object sender, DirectEventArgs e)
{
string json = e.ExtraParams["models"];
var personList = JSON.Deserialize>(json);
var ids = from p in personList
select p.Id;
try
{
Delete(ids.ToList());
e.Success = true;
}
catch (Exception ex)
{
//logs...
e.ErrorMessage = "删除失败." + ex.Message;
e.Success = false;
}
}
#region DAL
private readonly string m_connectionString = ConfigurationManager.ConnectionStrings["default"].ConnectionString;
///
/// 从数据库中获取符合条件的数据
///
/// sql where ...
/// sql order by ...
/// paras
/// 页码
/// 页容量
///
private IEnumerable GetPersonList(string where, string orderBy,
IEnumerable paras, int pageIndex, int pageSize)
{
where = String.IsNullOrEmpty(where) ? "1=1" : where;
orderBy = String.IsNullOrEmpty(orderBy) ? "Id desc" : orderBy;
pageIndex = pageIndex > 0 ? pageIndex : 1;
pageSize = pageSize > 0 ? pageSize : 20;
int _rowStart = (pageIndex - 1) * pageSize + 1;
int _rowEnd = pageIndex * pageSize;
List result = new List();
String sql =
String.Format("SELECT * FROM ( SELECT ROW_NUMBER() OVER ( ORDER BY {0} ) AS RowNumber,* FROM [" +
"Person ] WHERE {1}) T WHERE T.RowNumber BETWEEN {2} AND {3} ",
orderBy, where, _rowStart, _rowEnd);
var ds = SqlHelper.ExecuteDataset(m_connectionString, System.Data.CommandType.Text, sql, paras.ToArray());
if (ds != null && ds.Tables.Count > 0 &&
ds.Tables[0].Columns.Count > 0 && ds.Tables[0].Rows.Count > 0)
{
foreach (DataRow row in ds.Tables[0].Rows)
{
Person p = new Person();
p.Age = row.Field("Age");
p.Birthdate = row.Field("Birthdate");
p.Ethnic = row.Field("Ethnic");
p.Gender = row.Field("Gender");
p.Id = row.Field("Id");
p.Name = row.Field("Name");
p.Origo = row.Field("Origo");
p.Remarks = row.Field("Remarks");
result.Add(p);
}
}
return result;
}
///
/// 获取符合条件的记录行数
///
/// 条件,不需要WHERE关键字
/// 符合条件的记录条数
public int GetRecordCount(string where, IEnumerable paras)
{
int _result = 0;
where = String.IsNullOrEmpty(where) ? "1=1" : where;
string _sql = "SELECT COUNT(1) FROM [Person] ";
if (!String.IsNullOrWhiteSpace(where))
{
_sql += " WHERE " + where;
}
Object _obj = SqlHelper.ExecuteScalar(m_connectionString, CommandType.Text, _sql, paras.ToArray());
if (!Object.Equals(null, _obj) && !Object.Equals(DBNull.Value, _obj))
{
Int32.TryParse(_obj.ToString(), out _result);
}
return _result;
}
public bool Insert(Person p)
{
if (p == null)
{
return false;
}
p.Id = Guid.NewGuid();
StringBuilder _sb = new StringBuilder();
_sb.Append("INSERT INTO [Person] ");
_sb.Append("([Id],[Name],[Age],[Gender],[Birthdate],[Ethnic],[Origo],[Remarks])");
_sb.Append(" VALUES ");
_sb.Append("(@Id,@Name,@Age,@Gender,@Birthdate,@Ethnic,@Origo,@Remarks)");
SqlParameter[] _paras = {
new SqlParameter("@Id", p.Id),
new SqlParameter("@Name", p.Name),
new SqlParameter("@Age", p.Age),
new SqlParameter("@Gender", p.Gender),
new SqlParameter("@Birthdate", p.Birthdate),
new SqlParameter("@Ethnic", p.Ethnic),
new SqlParameter("@Origo", p.Origo),
new SqlParameter("@Remarks", p.Remarks)
};
int _rowsAffected = 0;
_rowsAffected = SqlHelper.ExecuteNonQuery(m_connectionString,
CommandType.Text, _sb.ToString(), _paras);
return _rowsAffected > 0;
}
public bool Update(Person p)
{
if (p == null)
{
return false;
}
StringBuilder _sb = new StringBuilder();
_sb.Append("UPDATE [Person] SET ");
_sb.Append("[Id]=@Id,[Name]=@Name,[Age]=Age,[Gender]=@Gender,");
_sb.Append("[Birthdate]=@Birthdate,[Ethnic]=@Ethnic,[Origo]=@Origo,[Remarks]=@Remarks ");
_sb.Append(" WHERE [Id]=@Id");
SqlParameter[] _paras = {
new SqlParameter("@Id", p.Id),
new SqlParameter("@Name", p.Name),
new SqlParameter("@Age", p.Age),
new SqlParameter("@Gender", p.Gender),
new SqlParameter("@Birthdate", p.Birthdate),
new SqlParameter("@Ethnic", p.Ethnic),
new SqlParameter("@Origo", p.Origo),
new SqlParameter("@Remarks", p.Remarks)
};
int _rowsAffected = 0;
_rowsAffected = SqlHelper.ExecuteNonQuery(m_connectionString,
CommandType.Text, _sb.ToString(), _paras);
return _rowsAffected > 0;
}
public bool Delete(IEnumerable ids)
{
if (ids == null || ids.Count() < 1)
{
return false;
}
string sql = "DELETE FROM [Person] WHERE ";
string where = String.Empty;
foreach (var key in ids)
{
if (String.IsNullOrEmpty(where))
{
where = String.Format(" [Id]='{0}' ", key);
}
else
{
where += String.Format(" OR [Id]='{0}' ", key);
}
}
sql += where;
int _rowsAffected = 0;
_rowsAffected = SqlHelper.ExecuteNonQuery(m_connectionString, CommandType.Text, sql);
return _rowsAffected > 0;
}
#endregion
}
}
原本计划在本文中介绍下导出和打印的功能,看大家需要再说吧:)
参考
[1] EXT.NET官方DEMO
[2] Sencha ExtJS API
[3] EXT.NET 官方API
- 转载请注明出处 © guog 2015