先看一下效果。美工不好,见笑。呵呵。
Silverlight DataGrid 本身是不带分页功能的,同时他的排序也是针对当前页面的内容进行排序的,而这两样功能在实际的项目中都是必须带的。上网搜索了一下,好像目前还没有那篇文章介绍过如何实现这两个常用的功能,看样子只要自己动手了。呵呵。
下面我们来一步一步实现这些功能。(以下都以NorthWind数据库为演示) 第一步,我们先实现分页。
要分页,首先要在WCF上加上一个分页获取数据的方法。先上代码。
1 [OperationContract]
2 public List<Products> GetProductsPaging(int start, int limit, string sort, string dir, out int TotalPage)
3 {
4 NorthWindLinqDataContext db = new NorthWindLinqDataContext();
5 string sql;
6 sql = "select count(*) as c from Products";
7 int TotalCount = db.ExecuteQuery<int>(sql).Single();
8 TotalPage = Convert.ToInt32(Math.Ceiling((double)TotalCount / (double)limit));
9 sql = "with a as (SELECT *, row_number() over (order by {0}) as rownumber FROM Products) select * from a where rownumber between {1} and {2}";
10 sql = string.Format(sql, sort + " " + dir, start , start+limit-1);
11 var query = db.ExecuteQuery<Products>(sql);
12 return query.ToList();
13 }
14
代码比较简单,用了sql2005的分页方法,就不做解释了,至于这分页方法的效率怎么样不是本次学习的重点,先不管了。
数据库访问使用LinqToSQL做的,本来想用Linq实现分页功能的,但是忙活了半天,最终以失败告终,主要的问题是,我没办法在Linq中实现动态排序,网上也没有找到一个合适的方法,只能写n个ifelse进行排序,就像下面的代码:
2 select p;
3 if (dir == "ProductName")
4 products = products.OrderBy(p => p.ProductName);
5 else if (dir == "ProductID")
6 products = products.OrderBy(p => p.ProductID);
7 .......
这样写的话,虽然功能能实现,但是实在是有点弱智。要写上一大堆的代码,真的不如写sql呢。最终,还是没有搞定这个问题,只好写sql语句了。如果大家有什么好的办法能实现的话,请留言告诉我。谢谢了!
ok,到此分页的基本功能搞定了,DataGrid已经可以获取分页的数据了。但是,这个DataGrid和Web上的不一样,没有Pager,晕,只要手工写一个Pager控件了。
第二步,实现Pager。
动手之前,老办法。先上网找现成的,呵呵,不错,这次终于有点结果了,网上不少这样的例子,在silverlight.net上找了一个Pager,先用着再说。呵呵。
这个是地址http://www.13sides.com/page/A-Generic-Pager-Control.aspx,大家自己去看吧,用法很简单。
现在把代码整合起来,ok,已经可以正确的分页了。哈哈。接下来,我们来实现排序。
第三步,实现排序。
要排序,当然是要点击ColumnHeader了,在DataGrid中找ColumnHeader的点击事件,晕,没有。不会吧,这个微软,怎么搞的啊,虽然Silverlight都2.0了,但是怎么看都还是像个半成品啊,这样常用的事件都没有。半成品就半成品吧,继续手工实现吧。
又是老办法,上网找现成的,呵呵,两种方案,一种是把Header改成模板,然后里面放上Button,这样就可以实现了。还有一种,有人提到了用DataGrid的HitTest方法。呵呵,第一个有点费事,每个Header都要改,第二个看起来酷一点,所以我选择了第二种。
试验了一下,晕,HitTest怎么成了不可访问,受保护级别限制。再查MSDN,呵呵,原来2.0里面该成了VisualTreeHelper.FindElementsInHostCoordinates了。ok,下面上代码:
在DataGrid的MouseLeftButtonDown写。这里有个奇怪的问题,点击ColumnHeader并不会触发MouseLeftButtonUp事件。真的搞不明白。
1 private void dgData_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
2 {
3 //获取点击的ColumnHeader
4 var u = from element in VisualTreeHelper.FindElementsInHostCoordinates(e.GetPosition(null), dgData)
5 where element is DataGridColumnHeader
6 select element;
7
8 if (u.Count() == 1)
9 {
10 DataGridColumnHeader header = (DataGridColumnHeader)u.Single();
11 //为什么要把这个header保存下来,下面会介绍原因的。
12 sortHeader = header;
13 //获取到字段的名称。
14 string headername = header.Content.ToString();
15 //跟现在的排序字段比较一下,确定应该是 desc 还是 asc
16 string newsort = header.Content.ToString();
17 if (newsort == sort)
18 dir = dir == "desc" ? "asc" : "desc";
19 else
20 dir = "asc";
21 sort = newsort;
22 //绑定数据
23 BindGrid();
24 e.Handled = true;
25 }
26 else
27 e.Handled = false;
28 }
好,到此,整个分页、排序的功能就基本上完成了。
呵呵,仔细用一下程序发现,我们在实现了排序之后,DataGrid 然而会自作聪明的帮我们的数据又排了一下,并且,DataGrid默认是支持列拖动的,当我们拖动列时,会触发排序,导致结果和用户预计的不一样。那怎么解决呢,
直接在DataGrid中设置 CanUserSortColumns="False"和CanUserReorderColumns="False"。就可以解决了。
再在测试一下,发现在排序之后,ColumnHeader上面并没有显示标示排序方向的小箭头,查了一下,MSDN上面在介绍 DataGrid 样式和模板的时候,提到过DataGridColumnHeader 状态里面有SortAscending和SortDescending这两种状态,ok,我们就接着写了个方法,来设置Header的状态。在上面MouseLeftButtonDown中我们曾经把排序的Header保存了下来,就是为了在这里使用的。代码如下:
2 {
3 if (dir == "asc")
4 VisualStateManager.GoToState(sortHeader, "SortAscending", false);
5 else
6 VisualStateManager.GoToState(sortHeader, "SortDescending", false);
7 }
很简单,但是有个奇怪的问题,就是在排序之后,设置了Header的状态并不能保存下来,鼠标移动,那个小箭头就会消失,具体原因我也不明白,我不太清楚VisualStateManager.GoToState在设置了Header状态是不是和DataGrid自带的排序功能有关联,才导致了排序的小箭头会自动消失,或者在这里根本就不应该用这个方法。暂时我没有去考虑这个问题,为了让排序小箭头能正确显示,我只好在Grid的MouseMove和LayoutUpdated事件里都调用了setColumnSortState(),用来保证小箭头可以正确显示。
至此,整个分页、排序的功能全部完成。除了那个小箭头显示的处理不完美之外,其它到也没有什么大问题。我会在后面的学习中,来解决这个问题。
另外,祝大家圣诞快乐!!!
补充:
重新修改了关于排序状态的实现。请见: