[原创]FineUI秘密花园(二十一) — 表格之动态创建列
有時我們需要根據數據來動態創建表格列,怎么來做到這一點呢?本章會詳細講解。
?
動態創建的列
還是通過一個示例來看下如何在FineUI中動態創建表格列,示例的界面截圖:
?
先來看下ASPX的標簽定義:
1: <ext:Grid ID="Grid1" runat="server" Width="650px" EnableCheckBoxSelect="true" EnableRowNumber="true" 2: Title="表格(動態創建的列)"> 3: </ext:Grid>?
ASPX標簽中沒有定義任何列,所有列都是在后臺定義的:
1: // 注意:動態創建的代碼需要放置于Page_Init(不是Page_Load),這樣每次構造頁面時都會執行 2: protected void Page_Init(object sender, EventArgs e) 3: { 4: InitGrid(); 5: } 6: ? 7: private void InitGrid() 8: { 9: FineUI.BoundField bf; 10: ? 11: bf = new FineUI.BoundField(); 12: bf.DataField = "Id"; 13: bf.DataFormatString = "{0}"; 14: bf.HeaderText = "編號"; 15: Grid1.Columns.Add(bf); 16: ? 17: bf = new FineUI.BoundField(); 18: bf.DataField = "Name"; 19: bf.DataFormatString = "{0}"; 20: bf.HeaderText = "姓名"; 21: Grid1.Columns.Add(bf); 22: ? 23: bf = new FineUI.BoundField(); 24: bf.DataField = "EntranceYear"; 25: bf.DataFormatString = "{0}"; 26: bf.HeaderText = "入學年份"; 27: Grid1.Columns.Add(bf); 28: ? 29: bf = new FineUI.BoundField(); 30: bf.DataToolTipField = "Major"; 31: bf.DataField = "Major"; 32: bf.DataFormatString = "{0}"; 33: bf.HeaderText = "所學專業"; 34: bf.ExpandUnusedSpace = true; 35: Grid1.Columns.Add(bf); 36: ? 37: Grid1.DataKeyNames = new string[] { "Id", "Name" }; 38: } 39: ? 40: protected void Page_Load(object sender, EventArgs e) 41: { 42: if (!IsPostBack) 43: { 44: LoadData(); 45: } 46: } 47: ? 48: private void LoadData() 49: { 50: DataTable table = GetDataTable(); 51: ? 52: Grid1.DataSource = table; 53: Grid1.DataBind(); 54: }整個代碼結構非常清晰,分為頁面的初始化階段和頁面的加載階段。
在頁面的初始化階段:
?
頁面的加載階段就是綁定數據到表格,和之前的處理沒有任何不同。
?
動態創建的模板列
模板列的動態創建有點復雜,我們先來看下創建好的模板列:
?
ASPX標簽和上面例子一模一樣,就不再贅述。我們來看下動態創建模板列的代碼:
1: FineUI.TemplateField tf = new TemplateField(); 2: tf.Width = Unit.Pixel(100); 3: tf.HeaderText = "性別(模板列)"; 4: tf.ItemTemplate = new GenderTemplate(); 5: Grid1.Columns.Add(tf);?
這里的GenderTemplate是我們自己創建的類,這也是本例的關鍵點。
1: public class GenderTemplate : ITemplate 2: { 3: public void InstantiateIn(System.Web.UI.Control container) 4: { 5: AspNet.Label labGender = new AspNet.Label(); 6: labGender.DataBinding += new EventHandler(labGender_DataBinding); 7: container.Controls.Add(labGender); 8: } 9: ? 10: private void labGender_DataBinding(object sender, EventArgs e) 11: { 12: AspNet.Label labGender = (AspNet.Label)sender; 13: ? 14: IDataItemContainer dataItemContainer = (IDataItemContainer)labGender.NamingContainer; 15: ? 16: int gender = Convert.ToInt32(((DataRowView)dataItemContainer.DataItem)["Gender"]); 17: 18: labGender.Text = (gender == 1) ? "男" : "女"; 19: } 20: }GenderTemplate實現了ITemplate接口,其中InstantiateIn在需要初始化模板中控件時被調用:
?
之后,在對Label進行數據綁定時:
?
上面的兩個示例,我們都把動態創建控件的代碼當時Page_Init函數中,這是為什么呢?
要想明白其中的道理,我們還是要從Asp.Net中動態添加控件的原理說起。
?
學習Asp.Net的視圖狀態和生命周期
這個話題比較深入,也不大容易理解,建議大家在閱讀本節之前詳細了解Asp.Net的視圖狀態和頁面的生命周期,下面是兩個非常經典的參考文章(本節的部分圖片和文字都來自這兩篇文章):
?
Asp.Net頁面的生命周期
從上圖可以看出,Asp.Net頁面的生命周期分為如下幾個階段:
上面的這七個階段是每個Asp.Net開發人員都應該熟悉和掌握的,它可以幫助我們理解頁面中Page_Load和事件處理函數的邏輯關系。
?
注意:上述處理過程不管是在頁面第一次加載還是在頁面回發,都會發生。理解這一點非常重要!
?
動態添加控件的兩種模式
動態添加控件需要在加載視圖狀態和加載回發數據之前進行,因為我們需要能夠在添加控件之后恢復這些數據。所以這個階段就對應了Page_Init處理函數,這也就是為什么上面兩個例子都在此函數中動態添加控件。
?
但是由于在初始化階段時,視圖狀態和回發數據還沒有恢復,因此此時無法訪問存儲在視圖狀態或者回發數據中的控件屬性。所以還有一個常用的模式是在Page_Init中添加控件,在Page_Load中為動態創建的控件設置默認值。
?
下面兩個示例分別展示了動態添加控件的兩種模式。
動態添加控件模式一:
1: protected void Page_Init(object sender, EventArgs e) 2: { 3: AspNet.Label lab = new AspNet.Label(); 4: lab.ID = "Label1"; 5: lab.Text = "Label1"; 6: ? 7: Form.Controls.Add(lab); 8: } 9: ? 10: protected void Page_Load(object sender, EventArgs e) 11: { 12: 13: }?
動態添加控件模式二:
1: protected void Page_Init(object sender, EventArgs e) 2: { 3: AspNet.Label lab = new AspNet.Label(); 4: lab.ID = "Label1"; 5: ? 6: Form.Controls.Add(lab); 7: } 8: ? 9: protected void Page_Load(object sender, EventArgs e) 10: { 11: if (!IsPostBack) 12: { 13: AspNet.Label lab = Form.FindControl("Label1") as AspNet.Label; 14: lab.Text = "Label1"; 15: } 16: }第二種模式是在初始化階段添加動態控件,然后在加載階段(!IsPostBack)設置控件的默認值。
?
?
?
?
錯誤使用動態添加控件的例子一
你可能會想上例中,為什么要將設置控件默認值的代碼放在 !IsPostBack 邏輯塊中,下面就來看下不放在!IsPostBack 邏輯塊中的例子。
首先看下ASPX標簽結構:
1: <form id="form1" runat="server"> 2: <asp:Button ID="Button1" Text="Change Text" OnClick="Button1_Click" runat="server" /> 3: <asp:Button ID="Button2" Text="Empty Post" runat="server" /> 4: <br /> 5: </form>再看下后臺的初始化代碼:
1: protected void Page_Init(object sender, EventArgs e) 2: { 3: AspNet.Label lab = new AspNet.Label(); 4: lab.ID = "Label1"; 5: ? 6: Form.Controls.Add(lab); 7: } 8: ? 9: protected void Page_Load(object sender, EventArgs e) 10: { 11: AspNet.Label lab = Form.FindControl("Label1") as AspNet.Label; 12: lab.Text = "Label1"; 13: } 14: ? 15: ? 16: protected void Button1_Click(object sender, EventArgs e) 17: { 18: AspNet.Label lab = Form.FindControl("Label1") as AspNet.Label; 19: lab.Text = "Changed Label1"; 20: }按如下步驟操作:
這就不對了,點擊“Empty Post”按鈕時顯示的文本也應該是 Changed Label1,但是上例中文本控件的視圖狀態沒有保持,這是為什么呢?
原因也很簡單,當用戶進行第三步操作(即點擊“Empty Post”按鈕):
?
關鍵點:當控件完成加載視圖狀態階段后,就會立即開始跟蹤其視圖狀態的改變,之后任何對其屬性的改變都會影響最終的控件視圖狀態。
理解這一點非常重要,如果你尚未理解這句話的意思,請多讀幾遍,再多讀幾遍,這句話同時會影響后面介紹的另外兩種動態添加控件的模式。
?
如果你能理解上面提到的過程,說明你已經掌握了Asp.Net的頁面生命周期和ViewState的加載過程了。
?
動態添加控件的另外兩種模式
除了在初始化階段動態添加控件外,還可以再加載階段添加控件。這是因為當把一個控件添加到另一個控件的Controls集合時,所添加的控件的生命周期會立即同步到父控件的生命周期。比如,如果父控件處于初始化階段,則會觸發所添加控件的初始化事件;如果父控件處于加載階段,則會觸發所添加控件的的初始化事件、加載視圖事件、加載回發數據事件以及加載事件。
?
由此,我們就有了另外兩種動態添加控件的模式:
動態添加控件模式三:
1: protected void Page_Load(object sender, EventArgs e) 2: { 3: AspNet.Label lab = new AspNet.Label(); 4: lab.ID = "Label1"; 5: lab.Text = "Label1"; 6: Form.Controls.Add(lab); 7: }?
對于這一種模式,你是否有這樣的疑問?:
如果此標簽的Text屬性在某次Ajax回發時改變了,那么下次Ajax回發時,創建此標簽并賦默認值會不會覆蓋恢復的視圖狀態呢(因為此時已經過了加載視圖狀態階段)?
其實不會這樣的,雖然在Page_Load已經過了加載視圖狀態階段,但是由于此標簽控件尚未添加到控件層次結構中,所以尚未經歷加載視圖狀態階段,只有在Controls.Add之后才會經歷標簽控件的初始化階段、加載視圖狀態階段、加載回發數據階段和加載階段。
?
下面通過一個例子說明,首先看下ASPX標簽結構:
1: <form id="form1" runat="server"> 2: <asp:Button ID="Button1" Text="Change Text" OnClick="Button1_Click" runat="server" /> 3: <asp:Button ID="Button2" Text="Empty Post" runat="server" /> 4: <br /> 5: </form>后臺代碼:
1: protected void Page_Load(object sender, EventArgs e) 2: { 3: AspNet.Label lab = new AspNet.Label(); 4: lab.ID = "Label1"; 5: lab.Text = "Label1"; 6: Form.Controls.AddAt(label2Index, lab); 7: } 8: ? 9: protected void Button1_Click(object sender, EventArgs e) 10: { 11: AspNet.Label lab = Form.FindControl("Label1") as AspNet.Label; 12: lab.Text = "Changed Label1"; 13: }進行如下操作:
?
在執行Controls.Add之前,文本值還是Label1:
?
在執行Controls.Add之后,文本值從視圖狀態恢復,變成了 Changed Label1:
?
?
動態添加控件模式四:
1: protected void Page_Load(object sender, EventArgs e) 2: { 3: AspNet.Label lab = new AspNet.Label(); 4: lab.ID = "Label1"; 5: 6: Form.Controls.Add(lab); 7: ? 8: if (!IsPostBack) 9: { 10: lab.Text = "Label1"; 11: } 12: }?
錯誤使用動態添加控件的例子二
如果你認為自己已經掌握了動態添加控件的原理,不妨來看下面這個錯誤的例子,看能否指出其中錯誤的關鍵。
先來看下ASPX標簽結構:
1: <form id="form1" runat="server"> 2: <asp:Button ID="Button2" Text="Empty Post" runat="server" /> 3: <br /> 4: </form>?
在看后臺初始化代碼:
1: protected void Page_Load(object sender, EventArgs e) 2: { 3: AspNet.Label lab = new AspNet.Label(); 4: lab.ID = "Label1"; 5: if (!IsPostBack) 6: { 7: lab.Text = "Label1"; 8: } 9: ? 10: Form.Controls.Add(lab); 11: }是不是和動態添加控件模式四比較類似,不過這里的用法卻是錯誤的,你能看出問題所在嗎?
?
來運行一把:
?
為什么會出現這種情況?我們來分析一下:
- 第一次加載頁面時,設置了文本標簽的默認值,然后添加到控件層次結構中;
- 添加到控件層次結構后,即開始跟蹤視圖狀態的變化,但是此標簽的Text屬性并沒改變,所以最終沒有保存到視圖狀態中;
- 點擊按鈕回發時,文本標簽的默認值為空,然后添加到控件層次結構中,在加載視圖狀態階段沒有發現文本標簽的視圖,所以最終顯示為空。
?
那為什么模式四是正確的呢?
簡單來說,修改標簽的Text屬性時已經在跟蹤視圖狀態的改變了,所以這個修改的值被保存了下來;下次回發時又將此值從視圖中恢復了出來。
?
?
錯誤使用動態添加控件的例子三
如果上面的都掌握了,再來看下面這個錯誤的示例,ASPX標簽結構如下:
1: <form id="form1" runat="server"> 2: <asp:Button ID="Button2" Text="Empty Post" runat="server" /> 3: <br /> 4: <asp:Label ID="Label2" Text="Label2" runat="server"></asp:Label> 5: </form>后臺初始化代碼如下:
1: protected void Page_Load(object sender, EventArgs e) 2: { 3: AspNet.Label lab = new AspNet.Label(); 4: lab.ID = "Label1"; 5: lab.Text = "Label1"; 6: 7: int label2Index = Form.Controls.IndexOf(Label2); 8: ? 9: Form.Controls.AddAt(label2Index, lab); 10: ? 11: ? 12: if (!IsPostBack) 13: { 14: lab.Text = "Changed Label1"; 15: } 16: }這段代碼進行了如下處理:
?
我們來看下頁面第一個加載的顯示:
一切正常,被改變文本值的Label1位于Label2的前面。
?
然后點擊“Empty Post”按鈕,會出現如下情況:
為什么本應該保持狀態的Label2,現在的值卻變成了Changed Label1?
?
根本原因是Asp.Net保存保存視圖狀態的方式,是按照控件出現的順序保存的,當然恢復也是按照順序進行的,關于這一特性,我有專門一篇文章詳細闡述。
?
總之,簡單兩句話:
?
?
或者簡單一句話:在ASP.NET中,所有動態添加控件的代碼都要放到 Page_Init 中進行!
?
?
小結
其實在FineUI中編寫動態創建的表格列非常簡單,但是要想理解其中原理,就不那么簡單了。本篇文章的最后一節詳細描述了動態創建控件的原理,也希望大家能夠細細品味,深入了解Asp.Net的內部運行機制。
下一篇文章我們會詳細講解如何從表格導出Excel文件。
?
注:《FineUI秘密花園》系列文章由三生石上原創,博客園首發,轉載請注明出處。文章目錄 官方論壇
轉載于:https://www.cnblogs.com/sanshi/archive/2012/11/19/2776672.html
總結
以上是生活随笔為你收集整理的[原创]FineUI秘密花园(二十一) — 表格之动态创建列的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 处理 Win 10 开机后输入法不加载问
- 下一篇: 湖南大学离散数学实验——代码(一)