雖然分頁控件滿天飛,因為實在沒找到WinForm程序合用的,所以就造了一回輪子。一開始認為這個事情比較簡單,沒有思考太多就開工了。事實上也沒花多少時間就寫好了第一版,想要有的功能也都實現了,以為萬事大吉。。。。。。控件的樣子長這樣:
軟件開發法則之一:如果一件事情特別順利,那麼一定會有一些坑在等著你!坑的大小和順利程度成正比。
果不其然,在前幾天的業務模塊重構時就掉分頁的坑裡面了,切換每頁行數後總是加載兩次數據。問題的原因也很簡單,加載數據的事件被觸發了兩次。靠,看來這裡業務邏輯有大問題啊!再看別的地方邏輯,也有問題!!!剛好遇到周末,於是,就開始一通全面梳理。怎麼梳理呢?還是從需求出發。
需求一:可以設置每頁顯示行數
修改了每頁顯示行數後,需要反饋到ViewModel,好根據新的顯示行數重新加載數據。等一下!似乎有的時候也不需要刷新數據吧?譬如當前每頁顯示20行,但總數只有10行,這個時候切換成每頁100行,它還是只能顯示10行啊。這個時候就不需要重新加載數據,能省就省啊。這個時候不去刷新數據,不但提高效率,體驗也更好。
需求二:可以切換頁碼,首頁|上一頁|下一頁|末頁|到[x]頁
切換頁碼後,需要反饋到ViewModel,好根據新的頁碼重新加載數據。這個直來直去的最簡單了!嗯,當前頁是首頁的時候,首頁|上一頁 這兩個按鈕應該屏蔽掉,同樣,當前頁是末頁時,下一頁|末頁 兩個按鈕也應該屏蔽掉。如果只有一頁,那麼這5個按鈕都不應該可用。
分頁的基本需求也就這兩個了,但我還需要一些特殊的需求。這些需求看上去挺簡單的,譬如:
1、新增一個對象後,將對象放到列表的最後,並且自動選中它。
2、刪除一個選定對象後,將對象從列表中移除。如果對象不是列表中最後一個對象,自動選中下一個對象,否則自動選中上一個對象(如果對象是當前頁的唯一對象,則意味著上一個對象位於上一頁,需要自動跳到上一頁)。
3、切換每頁顯示行數後還是選中當前對象,這就需要重新計算當前頁。。。。。。好吧,這裡就是大坑之所在了。到底是否需要重新加載數據呢?似乎邏輯相當復雜啊。。。。。。梳理了半天,總結出一句話:切換了頁碼或當前頁實際顯示行數變化後需要重新加載數據!
業務邏輯的梳理到這裡就完成了,接下去就是寫代碼實現的事情了。那麼,對以上業務邏輯,需要如何設計呢?
1、需要定義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);View Code
2、需要定義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;View Code
3、需要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 }View Code
剩下的就是內部的邏輯處理函數了
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 }View Code
完整代碼見:https://github.com/xuanbg/Utility/tree/master/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 }View Code
如果這篇文字對看官有點用處的話,請幫忙點下推薦,謝謝!