前段時間著手做一個網站,在使用ListView和DataPager時遇到了一個新問題。先描述一下頁面的要求吧:有兩級類別,一個大類,下面有子類,子類下才對應了產品,然後在一個頁面中把大類(指定了哪些)、子類、產品按結構顯示出來,其中產品要有分頁。好了,目前來說,整個頁面互動性只在於產品的分頁上,大類、子類在伺服器端只是顯示的作用,故我理所當然地想到了使用ListView來層層綁定,並在產品層添加DataPager控制項來分頁。
背景資料DTO也已經定義好,分別有大類、小類、產品:
1 // 產品大類DTO 2 public class ProductTypeDTO 3 { 4 public int ID { get; set; } 5 // ... 6 public IEnumerable<CategoryDTO> Categorys { get; set; } 7 } 8 9 // 產品小類DTO10 public class CategoryDTO11 {12 // ...13 public IEnumerable<ProductDTO> Products { get; set; }14 }15 16 // 產品DTO17 public class ProductDTO18 {19 public int ID { get; set; }20 // ...21 }
以慣性思維,先把大類資料來源傳給第一層ListView,再在一層內寫小類ListView,最後再在小類ListView中寫產品與DataPager。目前假設已經處理完成的資料來源為IList<ProductTypeDTO> ProductTypeList,它就是我們要顯示的東西了。
最初設計的前台代碼為:
1 <!-- Begin Types --> 2 <asp:ListView ID="LV_Products" runat="server"> 3 <ItemTemplate> 4 // 這裡是大類需要解析的HTML控制項 5 <!-- Begin Categorys--> 6 <asp:ListView DataSource='<%# DataBinder.Eval(Container.DataItem,"Categorys") %>' runat="server"> 7 <ItemTemplate> 8 // 這裡是小類需要解析的HTML控制項 9 <!-- Begin Products -->10 <asp:ListView ID="LV_Item" DataSource='<%# DataBinder.Eval(Container.DataItem,"Products") %>' runat="server">11 <ItemTemplate>12 // 這是具體產品需要解析的HTML控制項 13 </ItemTemplate>14 </asp:ListView>15 <!-- End Products-->16 <div class="cleardiv"></div>17 <!-- Begin DataPager-->18 <div class="product_dp">19 <asp:DataPager ID="DP_Item" PageSize="10" PagedControlID="LV_Item" runat="server">20 <Fields>21 // 這是分頁按鈕22 </Fields>23 </asp:DataPager>24 </div>25 <!-- End DataPager--> 26 <div class="cleardiv"></div>27 <br />28 </ItemTemplate>29 </asp:ListView>30 <!-- End Categorys-->31 <div class="cleardiv"></div>32 <br />33 </ItemTemplate>34 </asp:ListView>35 <!-- End Types -->
以上省略了與問題無關的一些繫結項目代碼,根據以往的經驗,要想不出現點擊兩次才翻頁的情況,可以把資料繫結語句:
LV_Products.DataSource = ProductTypeList;
LV_Products.DataBind();
寫在Page_PreRender中,在IIS中調試一看,唔,資料正常顯示,分頁已經分好,但是我一點其它頁的,完全沒作用,頁面閃了一下,頁面的元素還是第一頁那些。經過排查,我覺得是資料繫結的問題(在PreRender中進行綁定,會導致每次都進行了一次資料更新,而我的LV_Item是動態產生的,DataPager也是動態,這會導致產生一個全新的頁面,而不會根據以前的PageIndex產生翻頁),於是把後台綁定的代碼改成寫在!IsPost中,看了下,能分頁、能顯示,點擊分頁,好像是發生了跳轉,可惜那些資料不能正常地綁定進來,而且需要點擊2次才能正常跳轉。
思來想去,還是對ASP.NET的頁面載入不瞭解,於是仔細看了它的初始化流程,覺得與Page_PreRender相似的,我使用ListView.PreRender事件應該也可以達到相同效果,那麼需要取消LV_Item的綁定,而在後台手動來綁定其資料來源(LV_Products的綁定放在!IsPostBack內)。當頁面第一次載入時就綁定所有LV_Item的資料,當分頁回傳時,只重新綁定對應的LV_Item的資料來源。LV_Item的前台代碼改為:
<asp:ListView ID="LV_Item" OnPagePropertiesChanged="LV_PageChanged" runat="server"> // ...</asp:ListView>
看到了沒,就是通過ListView的分頁改變事件來觸發資料來源綁定的,綁定需要發生在它的PreRender事件中:
protected void LV_PageChanged(object sender, EventArgs e){ (sender as ListView).PreRender += LV_OnPreRender;}
這下明白了吧,第一次載入頁面,所有的ListView都會自動觸發OnPagePropertiesChanged事件,於是所有的ListView都會載入PreRender事件處理函數進行資料繫結,而當點擊分頁回傳裡,只有一個ListView會觸發OnPagePropertiesChanged事件,也就只有一個ListView會載入PreRender事件處理函數,所以繫結來源的重新綁定將只發生一個LV_Item身上。
好了,LV_OnPreRender作為LV_Item的重新綁定函數,需要有具體的大類、小類的資訊才能把產品列表綁定給ListView,目前我只有一個ProductTypeList把大類、小類、產品的關係給封裝好了,而LV_OnPreRender、LV_PageChanged都不能得到目前這個ListView是哪個大類下的哪個小類的列表資訊,怎麼辦?
只好另闢蹊徑了,來看個LV_Item產生的用戶端代碼:
發現了什麼,這個是有命名規則的!CPH_main是我用的母片名,LV_Products是我的大類ListView,然後ctl100,它是我的小類ListView(沒給它命名,自動產生的),然後後面的0,就是第0個大類!再又接了個ctl100是我用的ajax UpdataPanel(本文中沒有,這是實際項目中用的),後面的0、1、2、3...就是實際的小類了!唔,經過測試,這是完全沒有問題的,只要你沒有改ClientIDMode。在後台代碼中,我們可以通過ListView.UniqueID來擷取與這類似的值,只是有一點區別。於是我們最後一個要點LV_OnPreRender的函數為:
1 private static Regex _productRegex = new Regex(@"(?<=ctrl)\d+", RegexOptions.Singleline); 2 // 綁定資料來源 3 protected void LV_OnPreRender(object sender, EventArgs e) 4 { 5 var lv = sender as ListView; 6 // 確認資料來源 7 /* 根據具體情況來,我的UnitqueID是這樣的: 8 * 全域標識 ctl00$CPH_main$LV_Products$ctrl0$ctl00$ctrl0$LV_Item 9 * 大類 小類 10 */11 var matche = _productRegex.Match(lv.UniqueID, 25);12 // 重新綁定資料13 try14 {15 int categoryID = Int32.Parse(matche.NextMatch().Value);16 int typeID = Int32.Parse(matche.Value);17 lv.DataSource = BaseConfig.ProductTypeList[typeID].Categorys.ElementAt(categoryID).Products;18 lv.DataBind();19 }20 catch21 {22 // 錯誤處理23 }24 }
至此,分頁完成,效率不錯,在IIS上測試起來非常快,百來條一下的事。想看最終的,可以點我開啟圖片連結。
轉載請註明原址:http://www.cnblogs.com/lekko/archive/2013/04/27/3046481.html