分享一個分頁控件的實現思路

雖然分頁控件滿天飛,由於實在沒找到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);
View Code

二、須要定義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

三、須要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/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         }
View Code

————————————————默默無語的分割線——————————————————

在這篇隨筆發佈後,又改了一點東西。把每頁顯示行數這個參數改爲了經過事件參數傳遞,減小了一個屬性。特別補充說明一下,FocusedRowHandle這個屬性其實很是重要,有這個屬性,在刷新或者改變每頁顯示行數後,焦點行就能夠保持在原先選中行上面,這樣界面就不會抖動。

如今總結起來,一個分頁控件只須要公開:2個方法、3個事件、4個屬性。不管是作成什麼樣子,用什麼語言,都是如此。

 

若是這篇文字對看官有點用處的話,請幫忙點下推薦,謝謝!

相關文章
相關標籤/搜索