天天看點

【上接 9 年前的一篇文章】動态建立控件的一個坑和解決方案

昨天一位網友提出了這麼一個問題:動态建立Disabled的文本輸入框,頁面回發時修改其文本屬性無效:

【上接 9 年前的一篇文章】動态建立控件的一個坑和解決方案

為了更清楚的分析和解決問題,我們先把代碼和運作效果展示一下。

前台代碼很簡單:

1. 一個表單SimpleForm1,背景會動态添加控件到這裡面來

2. 一個按鈕,點選回發

上面是簡化後的代碼:

1. 一個Page_Init事件,在其中動态建立一個文本輸入框,并添加到SimpleForm1中。

2. 一個按鈕點選事件,找到動态建立的文本輸入框,并修改它的值為最新的時間。

頁面顯示效果:

【上接 9 年前的一篇文章】動态建立控件的一個坑和解決方案

實際運作時發現,點選【指派】按鈕時,頁面的文本輸入框的值并未改變。

最開始這位網頁也是懷疑 Enabled=false 的問題,是以我就先把代碼改為:

測試發現沒問題了:

【上接 9 年前的一篇文章】動态建立控件的一個坑和解決方案

好像還真是這麼回事,調試後發現,如果文本輸入框被禁用了,文本輸入框的值是不會送出到背景的,對比一下。

啟用文本輸入框:

【上接 9 年前的一篇文章】動态建立控件的一個坑和解決方案

禁用文本輸入框:

【上接 9 年前的一篇文章】動态建立控件的一個坑和解決方案

那是不是說,頁面回發時,隻要我們把禁用的文本輸入框值也回發到背景,不就解決問題了。

是這樣的嗎?

這麼做的确能解決這個問題。因為 ASP.NET 會查找請求參數中的回發資料,并更新控件的值。

問題的關鍵是,這麼做合規嗎?是否合乎HTML的規範,顯示不是的。

參考下這篇文章:https://stackoverflow.com/questions/7357256/disabled-form-inputs-do-not-appear-in-the-request

【上接 9 年前的一篇文章】動态建立控件的一個坑和解決方案

 HTML5 Spec中明确定義了 disabled 控件的行為:

禁用的控件不會接收焦點

禁用的控件在Tab導航中會自動跳過

禁用的控件不會出現在表單送出的請求參數中

換一種思路,我們測測其他控件,将TextBox換成Label,發現同樣的問題:

測試後發現,在點選按鈕時,兩個控件的值都沒有改變。

【上接 9 年前的一篇文章】動态建立控件的一個坑和解決方案

因為 Label 控件不算是使用者可修改的表單字段,是以表單送出時根本不會将其資料放在請求參數中。說白了這個邏輯和禁用的文本輸入框還是很類似的。

調試了一圈,發現要想解決這個問題,還是要回到動态建立控件上來。

9 年前我就寫過一篇文章,來回顧一下。

9年前的這篇文章對動态建立控件進行了深入的講解:javascript:void(0)

其中 ASP.NET WebForms 頁面的生命周期還是值得我們再次學習一遍:

【上接 9 年前的一篇文章】動态建立控件的一個坑和解決方案

 我們主要關心的是前面 4 個階段,9 年後我們再來回味一下,能感覺到 WebForms 的底層設計還是很巧妙的:

執行個體化階段:處理頁面标簽定義和 Page_Init 中代碼

回發 - 加載視圖狀态:查找頁面中的隐藏字段 __VIEWSTATE,并更新控件屬性

回發 - 加載回發資料:查找請求參數中的資料,并更新控件屬性(本例中從請求參數中找文本輸入框SimpleForm1$Text1的值)

加載階段:執行 Page_Load 中的代碼

上面看起來也很清楚,頁面第一次加載時,執行如下過程:

執行個體化:頁面标簽 + Page_Init

加載:Page_Load 

頁面回發時,執行如下過程:

加載視圖狀态:從頁面隐藏字段 __VIEWSTATE 中查找

加載回發資料:從目前 HTTP 的請求參數中查找

如果對上面幾個階段不陌生,那我就要問一個問題了:

這裡有一個非常關鍵的關鍵點,在 9 年前的那篇文章中我反複提到:

【上接 9 年前的一篇文章】動态建立控件的一個坑和解決方案
當控件完成【加載視圖狀态階段】後,就會立即開始跟蹤其視圖狀态的改變,之後任何對其屬性的改變都會影響最終的控件視圖狀态。

這句話另一層含義就是:在【加載視圖狀态階段】之前,對控件屬性的改變不會被跟蹤,也不會記錄到  __VIEWSTATE 中來。

更加嚴格的說,上面的說法有點問題,因為頁面第一次加載時沒有【加載視圖狀态階段】,更精确的描述:

頁面第一次加載時,将控件添加到層次結構樹之後,即開始跟蹤狀态變化,并記錄到 __VIEWSTATE

頁面回發時,在【加載視圖狀态階段】之後,即開始跟蹤狀态變化,并記錄到 __VIEWSTATE

如果控件是在【加載視圖狀态階段】之後添加到層次結構樹的話,則在将控件添加到層次結構樹之後開始跟蹤狀态變化,并記錄到 __VIEWSTATE

我們再來看一眼最初的代碼:

可以發現問題了:

頁面第一次加載時

 在 Page_Init 中首先對Text1指派:Text1.Text="2021-10-29 11:10:00"

但是這個指派操作是在添加到層次結構樹之前進行的,是以Text1.Text值不會被記錄到 __VIEWSTATE 中

10分鐘之後,頁面回發時

在 Page_Init 中首先對Text1指派:Text1.Text="2021-10-29 11:20:00"

加載視圖狀态時,從 __VIEWSTATE  中回複 Text1 之前的狀态,但是 __VIEWSTATE 中沒有找到

經過上面的詳細分析,可以看出,頁面第一次加載時,将 Text1 設定為 11:10,頁面回發時按道理是應該保持這個值的,但是卻被錯誤的更新為了 11:20 !

怎麼為動态添加控件指派呢?我們也提出了一個最佳實踐:

【上接 9 年前的一篇文章】動态建立控件的一個坑和解決方案

把上面的邏輯搞清楚了,解決問題就不難了:

運作效果:

【上接 9 年前的一篇文章】動态建立控件的一個坑和解決方案