雖然分頁控件滿天飛,由於實在沒找到WinForm程序合用的,因此就造了一回輪子。一開始認爲這個事情比較簡單,沒有思考太多就開工了。事實上也沒花多少時間就寫好了初版,想要有的功能也都實現了,覺得萬事大吉。。。。。。控件的樣子長這樣:git
軟件開發法則之一:若是一件事情特別順利,那麼必定會有一些坑在等着你!坑的大小和順利程度成正比。github
果不其然,在前幾天的業務模塊重構時就掉分頁的坑裏面了,切換每頁行數後老是加載兩次數據。問題的緣由也很簡單,加載數據的事件被觸發了兩次。靠,看來這裏業務邏輯有大問題啊!再看別的地方邏輯,也有問題!!!恰好遇到週末,因而,就開始一通全面梳理。怎麼梳理呢?仍是從需求出發。ide
需求一:能夠設置每頁顯示行數函數
修改了每頁顯示行數後,須要反饋到ViewModel,好根據新的顯示行數從新加載數據。等一下!彷佛有的時候也不須要刷新數據吧?譬如當前每頁顯示20行,但總數只有10行,這個時候切換成每頁100行,它仍是隻能顯示10行啊。這個時候就不須要從新加載數據,能省就省啊。這個時候不去刷新數據,不但提升效率,體驗也更好。this
需求二:能夠切換頁碼,首頁|上一頁|下一頁|末頁|到[x]頁spa
切換頁碼後,須要反饋到ViewModel,好根據新的頁碼從新加載數據。這個直來直去的最簡單了!嗯,當前頁是首頁的時候,首頁|上一頁 這兩個按鈕應該屏蔽掉,一樣,當前頁是末頁時,下一頁|末頁 兩個按鈕也應該屏蔽掉。若是隻有一頁,那麼這5個按鈕都不該該可用。設計
分頁的基本需求也就這兩個了,但我還須要一些特殊的需求。這些需求看上去挺簡單的,譬如:code
一、新增一個對象後,將對象放到列表的最後,而且自動選中它。orm
二、刪除一個選定對象後,將對象從列表中移除。若是對象不是列表中最後一個對象,自動選中下一個對象,不然自動選中上一個對象(若是對象是當前頁的惟一對象,則意味着上一個對象位於上一頁,須要自動跳到上一頁)。對象
三、切換每頁顯示行數後仍是選中當前對象,這就須要從新計算當前頁。。。。。。好吧,這裏就是大坑之所在了。究竟是否須要從新加載數據呢?彷佛邏輯至關複雜啊。。。。。。梳理了半天,總結出一句話:切換了頁碼或當前頁實際顯示行數變化後須要從新加載數據!
業務邏輯的梳理到這裏就完成了,接下去就是寫代碼實現的事情了。那麼,對以上業務邏輯,須要如何設計呢?
一、須要定義3個自定義事件和一個委託(由於須要經過事件傳遞參數),用於通知使用者相應參數的變化和從新加載列表數據
1 /// <summary> 2 /// 每頁行數發生改變,通知修改每頁行數 3 /// </summary> 4 public event EventHandler RowsPerPageChanged; 5 6 /// <summary> 7 /// 當前頁發生改變,通知從新加載列表數據 8 /// </summary> 9 public event PageControlHandle CurrentPageChanged; 10 11 /// <summary> 12 /// 總行數發生改變,通知修改FocusedRowHandle 13 /// </summary> 14 public event PageControlHandle TotalRowsChanged; 15 16 /// <summary> 17 /// 表示將處理分頁控件事件的方法 18 /// </summary> 19 /// <param name="sender"></param> 20 /// <param name="e"></param> 21 public delegate void PageControlHandle(object sender, PageControlEventArgs e);
二、須要定義5個屬性,用來傳遞參數
1 /// <summary> 2 /// 每頁行數下拉列表選項 3 /// </summary> 4 public Collection<string> RowsSelectItems 5 { 6 get { return _SelectItems; } 7 set 8 { 9 _SelectItems = value; 10 cbeRows.Properties.Items.AddRange(value); 11 cbeRows.SelectedIndex = 0; 12 RowsPerPage = int.Parse(_SelectItems[0]); 13 } 14 } 15 16 /// <summary> 17 /// 總行數 18 /// </summary> 19 public int TotalRows 20 { 21 set 22 { 23 _Rows = value; 24 _TotalPages = (int) Math.Ceiling((decimal) _Rows/RowsPerPage); 25 Refresh(); 26 } 27 } 28 29 /// <summary> 30 /// 當前選中行Handle 31 /// </summary> 32 public int FocusedRowHandle 33 { 34 private get { return _Handle - RowsPerPage*_Current; } 35 set { _Handle = RowsPerPage*_Current + value; } 36 } 37 38 /// <summary> 39 /// 每頁行數 40 /// </summary> 41 public int RowsPerPage { get; private set; } 42 43 /// <summary> 44 /// 當前頁 45 /// </summary> 46 public int CurrentPage => _Current + 1;
三、須要2個Public方法,用於增長/刪除列表對象後處理相應業務邏輯
1 /// <summary> 2 /// 增長列表成員 3 /// </summary> 4 /// <param name="count">增長數量,默認1個</param> 5 public void AddItems(int count = 1) 6 { 7 _Rows += count; 8 _Handle = _Rows - 1; 9 10 var page = _Current; 11 Refresh(); 12 13 if (_Current > page) 14 { 15 // 切換了頁碼須要從新加載數據 16 CurrentPageChanged?.Invoke(this, new PageControlEventArgs(FocusedRowHandle)); 17 } 18 else 19 { 20 TotalRowsChanged?.Invoke(this, new PageControlEventArgs(FocusedRowHandle)); 21 } 22 } 23 24 /// <summary> 25 /// 減小列表成員 26 /// </summary> 27 /// <param name="count">減小數量,默認1個</param> 28 public void RemoveItems(int count = 1) 29 { 30 _Rows -= count; 31 if (_Handle >= _Rows) _Handle = _Rows - 1; 32 33 var page = _Current; 34 Refresh(); 35 36 if (_TotalPages == 1 || _Handle < RowsPerPage*(_TotalPages - 1) || _Current < page) 37 { 38 // 不是末頁或切換了頁碼須要從新加載數據 39 CurrentPageChanged?.Invoke(this, new PageControlEventArgs(FocusedRowHandle)); 40 } 41 else 42 { 43 TotalRowsChanged?.Invoke(this, new PageControlEventArgs(FocusedRowHandle)); 44 } 45 }
剩下的就是內部的邏輯處理函數了
1 /// <summary> 2 /// 切換每頁行數 3 /// </summary> 4 private void PageRowsChanged() 5 { 6 var change = RowsPerPage < _Rows - RowsPerPage*_Current; 7 RowsPerPage = int.Parse(cbeRows.Text); 8 RowsPerPageChanged?.Invoke(this, null); 9 10 var page = _Current; 11 Refresh(); 12 13 change = change || RowsPerPage < _Rows - RowsPerPage*_Current; 14 if (_Current == page && !change) return; 15 16 // 切換了頁碼或當前頁顯示行數變化後須要從新加載數據 17 CurrentPageChanged?.Invoke(this, new PageControlEventArgs(FocusedRowHandle)); 18 } 19 20 /// <summary> 21 /// 切換當前頁 22 /// </summary> 23 /// <param name="page">頁碼</param> 24 private void ChangePage(int page) 25 { 26 _Handle = RowsPerPage*page; 27 28 Refresh(); 29 30 CurrentPageChanged?.Invoke(this, new PageControlEventArgs(FocusedRowHandle)); 31 } 32 33 /// <summary> 34 /// 刷新控件 35 /// </summary> 36 private new void Refresh() 37 { 38 var total = _TotalPages == 0 ? 1 : _TotalPages; 39 labRows.Text = $" 行/頁 | 共 {_Rows} 行 | 分 {total} 頁"; 40 labRows.Refresh(); 41 42 _Current = (int) Math.Floor((decimal) _Handle/RowsPerPage); 43 btnFirst.Enabled = _Current > 0; 44 btnPrev.Enabled = _Current > 0; 45 btnNext.Enabled = _Current < _TotalPages - 1; 46 btnLast.Enabled = _Current < _TotalPages - 1; 47 btnJump.Enabled = _TotalPages > 1; 48 49 var width = (int) Math.Log10(_Current + 1)*7 + 18; 50 btnJump.Width = width; 51 btnJump.Text = CurrentPage.ToString(); 52 labRows.Focus(); 53 }
完整代碼見:https://github.com/xuanbg/Utility/tree/master/BaseForm/Controls
通過重構後,分頁控件對外僅暴露5個屬性和2個方法。使用者只須要在參數變化後給相應屬性賦值便可,每頁行數的調整、加載列表數據和列表的FocusedRowHandle都經過訂閱事件完成。代碼示例以下:
1 View.TabRole.RowsPerPageChanged += (sender, args) => _PageRows = View.TabRole.RowsPerPage; 2 View.TabRole.CurrentPageChanged += (sender, args) => PageChanged(args.RowHandle); 3 View.TabRole.TotalRowsChanged += (sender, args) => View.GdvRole.FocusedRowHandle = args.RowHandle; 4 5 /// <summary> 6 /// 切換頁碼後從新加載角色列表 7 /// </summary> 8 /// <param name="handel">當前焦點行</param> 9 private void PageChanged(int handel) 10 { 11 _CurrentPage = View.TabRole.CurrentPage; 12 13 LoadRoles(handel); 14 } 15 16 /// <summary> 17 /// 新增角色到角色列表 18 /// </summary> 19 /// <param name="role">RoleInfo</param> 20 internal void AddRole(RoleInfo role) 21 { 22 _Roles.Add(role); 23 24 View.TabRole.AddItems(); 25 View.GrdRole.RefreshDataSource(); 26 } 27 28 /// <summary> 29 /// 刪除當前所選角色 30 /// </summary> 31 internal void RoleDelete() 32 { 33 _Roles.Remove(Role); 34 35 View.TabRole.RemoveItems(); 36 View.GdvRole.RefreshData(); 37 }
————————————————默默無語的分割線——————————————————
在這篇隨筆發佈後,又改了一點東西。把每頁顯示行數這個參數改爲了經過事件參數傳遞,減小了一個屬性。特別補充說明一下,FocusedRowHandle這個屬性其實很是重要,有這個屬性,在刷新或者改變每頁顯示行數後,焦點行就能夠保持在原先選中行上面,這樣界面就不會抖動。
如今總結起來,一個分頁控件只須要公開:2個方法、3個事件、4個屬性。不管是作成什麼樣子,用什麼語言,都是如此。
若是這篇文字對看官有點用處的話,請幫忙點下推薦,謝謝!