最近发现oschina不给我发每周精彩回顾了,才想起距离上次写东西已经有9个月了,于是狠狠的惭愧了一把。程序员光干活,不总结是不行的,于是便跟大家分享下下面这些开发体验。 最近公司的外网项目,都准备用Gwt开发,经过之前的一些试验,公司决定采用SmartGwt 2.4作为前台框架。经过一段使用之后,感觉SmartGwt基本上比较容易上手。不过,虽然他的showcase的例子挺丰富,但只覆盖了很少的api,所以基本上还是天天翻着api来开发。 其组件库涵盖了绝大多数常见的控件,但是没有分页条,居然少了这么常用的组件,着实让人遗憾。先google了一下,没有令人满意的现成的扩展组件,查了半天,只在一个英文论坛里,发现一个半成品,只是个实现了部分页面展示逻辑的页面组件,而且没有数据交互。无奈,找不到现成的,只能自己动手丰衣足食了。 一、定义分页条功能 功能至少要满足基本需要。 比如对于用户来说,页面功能应该包括: 1、能够定位数据到:首页,上一页,下一页,末页 2、能够展示当前页和总页数 3、直接跳转到指定页(大于零,小于总页数) 4、根据当前页和总页数,控制按钮可用状态 对于程序员来说,这个组件应该可以: 1、方便的初始化工具条组件并能绑定数据展示组件(比如grid) 2、定制每页记录数 3、最好和数据查询方法分离,消除对业务查询方法的依赖 4、最好能有个模态遮罩,在数据加载过程中,给予提示,同时限制用户的重复操作。 二、代码实现 经过分析和简单的设计,决定通过两个类来实现: 1、一个纯页面组件,继承自com.smartgwt.client.widgets.toolbar.ToolStrip。代码实现思路就不写了,反正是从混沌到清晰的一个过程。直接帖代码了 001 <span class="Apple-style-span">import com.smartgwt.client.types.Alignment; 002 import com.smartgwt.client.types.Overflow; 003 import com.smartgwt.client.util.SC; 004 import com.smartgwt.client.widgets.Canvas; 005 import com.smartgwt.client.widgets.IButton; 006 import com.smartgwt.client.widgets.Label; 007 import com.smartgwt.client.widgets.events.ClickEvent; 008 import com.smartgwt.client.widgets.events.ClickHandler; 009 import com.smartgwt.client.widgets.form.DynamicForm; 010 import com.smartgwt.client.widgets.form.fields.TextItem; 011 import com.smartgwt.client.widgets.grid.ListGrid; 012 import com.smartgwt.client.widgets.grid.ListGridRecord; 013 import com.smartgwt.client.widgets.toolbar.ToolStrip; 014 015 /** 016 * 017 * 分页工具条 018 * 019 * 使用方法: 020 * 1、实例化一个分页工具栏,传入每页记录条数和绑定的grid 021 * 2、实现一个ListGridDataControl(实例化方法参见类中注释,注意和分页工具的相互调用),并把它设值给分页工具栏的dataControl属性, 022 * 注意:分页工具条和ListGridDataControl需要紧密配合相互调用。 023 * 3、调用active()方法,激活分页条并加载grid数据 024 * 025 * 提示: 026 * 1、本分页工具不处理grid的行号,行号理解为grid的列定义范畴 027 * 2、可以多次调用active()方法,调用的结果相当于把分页栏定位到首页,并重新获取总页数 028 * 3、如果查询业务数据的参数改变时,可以重新实现并设置ListGridDataControl,然后调用active() 029 * 4、可以调用setMaskMessage()设置遮罩的提示信息,如果不设置,采用默认值 030 * 031 * 说明: 032 * 之所以需要ListGridDataControl回调分页工具条的方法,导致增加了相互依赖,主要是考虑到获取数据和获取记录总数的方法通常是异步的, 033 * 为保持操作的同步性,只能通过互相调用的方式保持工具条和grid的正常工作。 034 * 035 * @author weichao 036 * @version 1.0 037 */ 038 public class GridPaginationBar extends ToolStrip{ 039 040 /** 041 * 模态遮罩 042 */ 043 ModalWindow mask; 044 045 private String maskMessage = " 数据加载中...... "; 046 047 public void setMaskMessage(String maskMessage) { 048 this.maskMessage = maskMessage; 049 } 050 /** 051 * 每页条数 052 */ 053 private int pageSize = -1; 054 055 /** 056 * 当前页 057 */ 058 private int pageNum = -1; 059 060 /** 061 * 总页数 062 */ 063 private int totalPage = -1; 064 065 /** 066 * 数据记录条数 067 */ 068 private int totalRowNum = -1; 069 070 /** 071 * 绑定的grid 072 */ 073 private final ListGrid grid; 074 075 //首页</span><span class="Apple-style-span"> TopImgButton first = new TopImgButton(16,16,"/img/control_start_blue.png","/img/control_start.png"); 076 077 //上一页 078 TopImgButton backward = new TopImgButton(16,16,"/img/control_rewind_blue.png","/img/control_rewind.png"); 079 080 //下一页 081 TopImgButton forward = new TopImgButton(16,16,"/img/control_fastforward_blue.png","/img/control_fastforward.png"); 082 083 //末页 084 TopImgButton last = new TopImgButton(16,16,"/img/control_end_blue.png","/img/control_end.png"); 085 086 //输入框form 087 DynamicForm pageForm; 088 089 //页码输入框 090 protected TextItem pageText; 091 092 //转到按钮 093 IButton go = new IButton("转到"); 094 095 //总页数 096 protected Label totalLabel; 097 098 //grid数据控制器 099 private ListGridDataControl dataControl; 100 101 public void setDataControl(ListGridDataControl dataControl){ 102 this.dataControl = dataControl; 103 } 104 105 public GridPaginationBar(ListGrid listGrid, int pageSize) { 106 this(listGrid, pageSize,null); 107 } 108 109 /** 110 * 111 * 构造方法中,只初始化工具条元素和事件,不初始化数据, 112 * 构造完成后工具条处于禁用状态,需要首先传入grid数据控制器, 113 * 然后调用active()方法来激活工具条,并开始加载数据。 114 * 115 * @param listGrid 绑定的grid 116 * @param pageSize 每页记录条数 117 * @param maskCanvas 加载数据过程中,需要用遮罩遮盖的控件,如果传入null,则默认遮盖的对象为绑定的grid 118 */ 119 public GridPaginationBar(ListGrid listGrid, int pageSize, Canvas maskCanvas) { 120 this.grid = listGrid; 121 mask = new ModalWindow(maskCanvas == null ? grid : maskCanvas); 122 mask.setLoadingIcon("/img/loading.gif"); 123 this.pageSize = pageSize; 124 first.setButtonTip("首页"); 125 first.setButtonClickHandler(new ClickHandler(){ 126 public void onClick(ClickEvent event) { 127 goToPage(1); 128 } 129 }); 130 backward.setButtonTip("上一页"); 131 backward.setButtonClickHandler(new ClickHandler(){ 132 public void onClick(ClickEvent event){ 133 goToPage(pageNum - 1); 134 } 135 }); 136 pageText = new TextItem(); 137 pageText.setWidth(40); 138 pageText.setHeight(20); 139 pageText.setShowTitle(false); 140 pageText.setTextAlign(Alignment.RIGHT); 141 go.setWidth(34); 142 go.setHeight(20); 143 go.addClickHandler(new ClickHandler(){ 144 public void onClick(ClickEvent event) { 145 String page = pageText.getValueAsString(); 146 if(page == null || page.trim().equals("")){ 147 SC.say("请输入页码"); 148 return ; 149 }else { 150 try{ 151 int pageint = Integer.valueOf(page); 152 if(pageint<1){ 153 SC.say("输入的页码不能小于1"); 154 return; 155 }else if(pageint>totalPage){ 156 SC.say("输入的页码不能大于总页数 "+totalPage); 157 return; 158 }else { 159 goToPage(pageint); 160 } 161 }catch(NumberFormatException e){ 162 SC.say("请输入整数"); 163 return; 164 } 165 } 166 }}); 167 168 forward.setButtonTip("下一页"); 169 forward.setButtonClickHandler(new ClickHandler(){ 170 public void onClick(ClickEvent event){ 171 goToPage(pageNum + 1); 172 } 173 }); 174 175 last.setButtonTip("末页"); 176 last.setButtonClickHandler(new ClickHandler(){ 177 public void onClick(ClickEvent event){ 178 goToPage(totalPage); 179 } 180 }); 181 182 totalLabel = new Label(); 183 totalLabel.setWrap(false); 184 totalLabel.setWidth(50); 185 186 setHeight(20); 187 setOverflow(Overflow.VISIBLE); 188 setStyleName("normal"); 189 this.setMargin(5); 190 191 pageForm = new DynamicForm(); 192 pageForm.setNumCols(1); 193 pageForm.setWidth(45); 194 pageForm.setItems(pageText); 195 Label blank1 = new Label(); 196 blank1.setContents(" "); 197 blank1.setWidth(5); 198 blank1.setHeight(20); 199 200 Label blank2 = new Label(); 201 blank2.setContents(" "); 202 blank2.setWidth(5); 203 blank2.setHeight(20); 204 205 Label blank3 = new Label(); 206 blank3.setContents(" "); 207 blank3.setWidth(15); 208 blank3.setHeight(20); 209 210 Label blank4 = new Label(); 211 blank4.setContents(" "); 212 blank4.setWidth(5); 213 blank4.setHeight(20); 214 215 Label blank5 = new Label(); 216 blank5.setContents(" "); 217 blank5.setWidth(5); 218 blank5.setHeight(20); 219 220 addMember(first); 221 addMember(blank1); 222 addMember(backward); 223 addMember(blank2); 224 addMember(pageForm); 225 addMember(go); 226 addMember(blank5); 227 addMember(forward); 228 addMember(blank4); 229 addMember(last); 230 addMember(blank3); 231 addMember(totalLabel); 232 setAlign(Alignment.RIGHT); 233 resetPaginationState(); 234 } 235 236 /** 237 * 转到指定页,获取分页数据并装载, 238 * 获取数据过程中,加模态遮罩 239 * 240 * @param pageNum 241 */ 242 public void goToPage(int pageNum) { 243 go.disable(); 244 if (pageNum > totalPage) 245 pageNum = totalPage; 246 if (pageNum < 1) 247 pageNum = 1; 248 if (pageNum == this.pageNum) { 249 go.enable(); 250 return; 251 } 252 mask.show(maskMessage, true); 253 //fetch data and reload grid 254 this.pageNum = pageNum; 255 int startNum = (pageNum - 1) * pageSize; 256 dataControl.fetchData(startNum, pageSize); 257 } 258 259 /** 260 * 根据记录总数计算总页数 261 */ 262 private void countTotalPages() 263 { 264 if(totalRowNum < 1){ 265 totalPage = -1; 266 return; 267 } 268 int pages = (int) Math.ceil(((float) totalRowNum) / ((float) pageSize)); 269 // never return zero pages 270 if (pages == 0) 271 pages = 1; 272 this.totalPage=pages; 273 pageNum = 1; 274 } 275 276 /** 277 * 首次查询数据,获取数据总数等信息,并更新分页条状态 278 * 获取数据过程中,加模态遮罩 279 */ 280 public void active(){ 281 if(dataControl == null){ 282 SC.say("错误","分页栏未配置ListGridDataControl数据控制器,请检查代码"); 283 return; 284 } 285 //首先禁用工具条 286 if(pageNum != -1){ 287 resetPaginationState(); 288 } 289 mask.show(maskMessage, true); 290 dataControl.getTotal(); 291 } 292 293 /** 294 * ListGridDataControl获取总记录数后的回调方法 295 * @param all 296 */ 297 public void afterGetTotal(int all){ 298 this.totalRowNum = all; 299 countTotalPages(); 300 if(totalPage==-1){ 301 //如果没有查询到数据,则清空原来的数据 302 afterFetchData(new ListGridRecord[0]); 303 } else { 304 dataControl.fetchData(0, pageSize); 305 } 306 } 307 308 /** 309 * ListGridDataControl获取数据后的回调方法 310 * @param datas 311 */ 312 public void afterFetchData(ListGridRecord[] datas){ 313 if(datas != null){ 314 dataControl.loadData(datas); 315 } 316 updatePageinationState(); 317 mask.hide(); 318 } 319 320 /** 321 * 清除分页工具条状态,全部置灰,不负责清空grid当前数据 322 */ 323 private void resetPaginationState(){ 324 pageNum = -1; 325 totalPage = -1; 326 totalRowNum = -1; 327 first.disable(); 328 backward.disable(); 329 last.disable(); 330 forward.disable(); 331 pageText.disable(); 332 go.disable(); 333 totalLabel.setContents(""); 334 } 335 336 /** 337 * 依据当前页和总页数控制工具条状态 338 * 规则: 339 * 当前页为-1,则说明工具条尚未激活或者没有查询到数据,不需要处理; 340 * 当前页等于1,则首页按钮和上一页按钮置灰,否则,激活; 341 * 当前页等于总页数,则末页按钮和上一页按钮置灰,否则,激活; 342 */ 343 private void updatePageinationState() { 344 if(pageNum==-1||totalPage==-1){ 345 return; 346 } 347 totalLabel.setContents("第" + pageNum + "页/共" + totalPage + "页"); 348 pageText.enable(); 349 go.enable(); 350 pageText.setValue(pageNum); 351 if (pageNum == 1){ 352 first.disable(); 353 backward.disable(); 354 } else { 355 first.enable(); 356 backward.enable(); 357 } 358 if (pageNum == totalPage){ 359 last.disable(); 360 forward.disable(); 361 } else { 362 last.enable(); 363 forward.enable(); 364 } 365 } 366 }</span> 2、一个控制接口类 即GridPaginationBar 类里的ListGridDataControl ,代码如下: 01 import com.smartgwt.client.widgets.grid.ListGridRecord; 02 03 /** 04 * 05 * @author weichao 06 * listGrid数据获取及装载接口 ,和工具条有紧密的相互调用 07 * grid加分页工具条时,需要实现此接口并提供给分页工具条 08 * 09 * 10 * 示例: 11 * 12 * GridPaginationBar pageBar = new GridPaginationBar(bizGrid,20,bizGrid.getParentElement()); 13 * 14 * pageBar.setDataControl(new ListGridDataControl(){ 15 * public void fetchData(int start, int pageSize) { 16 * service.getBusiness(sid,start,pageSize, 17 * new AsyncCallback<List<XXXXXVO>>() { 18 * public void onFailure(Throwable caught) { 19 * Window.alert(caught.getMessage()); 20 * } 21 * public void onSuccess(List<XXXXXVO> result) { 22 * list = result; 23 * pageBar.afterFetchData(RenderBizData.getNewRecords(result)); 24 * } 25 * }); 26 * } 27 * public void getTotal() { 28 * service.getBusinessTotal(sid, new AsyncCallback<Integer>(){ 29 * public void onFailure(Throwable arg0) { 30 * Window.alert(arg0.getMessage()); 31 * } 32 * public void onSuccess(Integer arg0) { 33 * pageBar.afterGetTotal(arg0); 34 * }} ); 35 * } 36 * public void loadData(ListGridRecord[] records) { 37 * bizGrid.setData(records); 38 * }}); 39 */ 40 public interface ListGridDataControl { 41 42 /** 43 * 获取分页数据,并回调分页工具栏的afterFetchData()方法, 44 * 如果需要异步获取数据,请注意回调afterFetchData()的时机,保持数据的同步操作。 45 * 46 * @param start 47 * @param pageSize 48 */ 49 public void fetchData(int start,int pageSize); 50 51 /** 52 * 获取数据记录总数,并回调分页工具栏的afterGetTotal()方法 53 * 如果需要异步获取数据,请注意回调afterGetTotal()的时机,保持数据的同步操作。 54 * 55 * @return 总数 56 */ 57 public void getTotal(); 58 59 /** 60 * 把数据装载入grid 61 * @param records 62 */ 63 public void loadData(ListGridRecord[] records); 64 } 3、从网上找了个模态窗口ModalWindow ,直接使用了,这里就不贴代码了: 需要的可以从这里找 http://code.google.com/p/smartgwt-extensions/source/browse/trunk/mainprojects/src/main/java/com/smartgwt/extensions#extensions%2Fmodal%2Fclient%253Fstate%253Dclosed 4、TopImgButton类是自己实现的一个按钮类,当然你可以用smartgwt的按钮 ,或者你自己写个。 01 import com.smartgwt.client.types.Cursor; 02 import com.smartgwt.client.widgets.Img; 03 import com.smartgwt.client.widgets.events.ClickHandler; 04 import com.smartgwt.client.widgets.layout.HLayout; 05 06 /** 07 * @author weichao 08 * 有激活和置灰两种状态的图片按钮 09 */ 10 public class TopImgButton extends HLayout{ 11 Img enable = new Img(); 12 Img disable = new Img(); 13 private boolean _enable = true; 14 public TopImgButton(int buttonWidth,int buttonHeight,String enableImgSrc,String disableImgSrc){ 15 setWidth(buttonWidth); 16 setHeight(buttonHeight); 17 enable.setSrc(enableImgSrc); 18 enable.setWidth(buttonWidth); 19 enable.setHeight(buttonHeight); 20 enable.setCursor(Cursor.POINTER); 21 disable.setSrc(disableImgSrc); 22 disable.setWidth(buttonWidth); 23 disable.setHeight(buttonHeight); 24 addMember(enable); 25 } 26 27 public void enable(){ 28 if(!_enable){ 29 removeMember(disable); 30 addMember(enable); 31 _enable = !_enable; 32 } 33 } 34 35 public void disable(){ 36 if(_enable){ 37 removeMember(enable); 38 addMember(disable); 39 _enable = !_enable; 40 } 41 } 42 43 public void setButtonClickHandler(ClickHandler handler){ 44 enable.addClickHandler(handler); 45 } 46 47 public void setButtonTip(String tipMessage){ 48 enable.setTooltip(tipMessage); 49 } 50 } 三、效果 注释里写的比较多了,还有示例也帖在类的注释里了,所以就不再解释怎么用了。 经过几番测试,感觉运行比较稳定。希望能对需要smartgwt分页的朋友有所帮助。