【上接 9 年前的一篇文章】动态创建控件的一个坑和解决方案

技术【上接 9 年前的一篇文章】动态创建控件的一个坑和解决方案 【上接 9 年前的一篇文章】动态创建控件的一个坑和解决方案提出问题
昨天一位网友提出了这么一个问题:动态创建Disabled的文本输入框,

[续自9年前的一篇文章]一个动态创建控件的坑和解决方案。

提出问题

昨天有网友提出了这样一个问题:动态创建一个Disabled文本输入框,在页面回贴时修改其文本属性是无效的:

分析问题

为了更清晰地分析和解决问题,我们先展示一下代码和运行效果。

f :页面管理器ID='页面管理器1' runat='服务器'/f :页面管理器

f : simpleform runat=' server ' ID=' simpleform 1 '

/f :s实现形式

f : BUTTON id=' btnset value ' runat=' server ' onclick=' btnset value _ click ' text=' assignment '/f : BUTTON。

前台代码很简单:

1.一个表单SimpleForm1,后台会动态的给它添加控件。

2.一个按钮,点击回复。

受保护的void Page_Init(对象发送者,事件参数e)

{

文本框t=新文本框()

{

ID='Text1 ',

标签='动态创建的1 ',

文本=日期时间。现在. ToString(),

Enabled=false,

};

SimpleForm1。项目.添加(t);

}

受保护的空值点击(对象发送者,事件参数)

{

TextBox t=SimpleForm1。作为文本框的find control(' Text1 ');

t.文本=日期时间。now . ToString();

}

以上是简化代码:

1.一个Page_Init事件,其中一个文本输入框被动态创建并添加到SimpleForm1。

2.单击带有按钮的事件,找到动态创建的文本输入框,并将其值修改为最新时间。

页面显示效果:

在实际操作中发现,点击【分配】按钮时,页面的文本输入框的值没有变化。

怀疑是 Enabled=false 的问题

起初,这个网页也怀疑Enabled=false,所以我将代码改为:

受保护的void Page_Init(对象发送者,事件参数e)

{

文本框t=新文本框()

{

ID='Text1 ',

标签='动态创建的1 ',

文本=日期时间。现在. ToString(),

Enabled=true,

};

SimpleForm1。项目.添加(t);

}

测试未发现问题:

似乎是这样。调试后发现,如果禁用文本输入框,文本输入框的值将不会提交到后台。对比一下。

启用文本输入框:

禁用文本输入框:

这是否意味着当页面被回贴时,只要我们将禁用的文本输入框值发送回后台,就不会解决问题。

是这样吗?

因为这么做的确能解决这个问题。ASP.NET将在请求参数中找到回发数据并更新控件的值。

关键问题是,是否合规?是否符合HTML规范?它表明它不是。

HTML5 Spec - 禁用的表单项不会出现在表单请求中

请参阅本文:https://stack overflow.com/questions/7357256/disabled-form-inputs-不要出现在请求中。

HTMLSPEC中明确定义了禁用控件的行为:

禁用的控件不会获得焦点。

选项卡导航中将自动跳过禁用的控件。

禁用的控件不会出现在表单提交的请求参数中。

换个控件测试,发现真的不是 Disabled=false 的问题

另一方面,我们测试其他控件,将TextBox更改为Label,发现同样的问题:

受保护的void Page_Init(对象发送者,事件参数e)

{

文本框t=新文本框()

{

ID='Text1 ',

Label = "动态创建的1",
Text = DateTime.Now.ToString(),
Enabled = false,
};
SimpleForm1.Items.Add(t);

Label l = new Label()
{
ID = "Label1",
Label = "动态创建的Label1",
Text = DateTime.Now.ToString()
};
SimpleForm1.Items.Add(l);
}
protected void btnSetValue_Click(object sender, EventArgs e)
{
TextBox t = SimpleForm1.FindControl("Text1") as TextBox;
t.Text = DateTime.Now.ToString();
Label l = SimpleForm1.FindControl("Label1") as Label;
l.Text = DateTime.Now.ToString();
}

测试后发现,在点击按钮时,两个控件的值都没有改变。

因为 Label 控件不算是用户可修改的表单字段,所以表单提交时根本不会将其数据放在请求参数中。说白了这个逻辑和禁用的文本输入框还是很类似的。

调试了一圈,发现要想解决这个问题,还是要回到动态创建控件上来。

9 年前我就写过一篇文章,来回顾一下。

回顾 9 年前的一篇文章

9年前的这篇文章对动态创建控件进行了深入的讲解:https://www.cnblogs.com/sanshi/archive/2012/11/19/2776672.html

其中 ASP.NET WebForms 页面的生命周期还是值得我们再次学习一遍:

我们主要关心的是前面 4 个阶段,9 年后我们再来回味一下,能感觉到 WebForms 的底层设计还是很巧妙的:

  1. 实例化阶段:处理页面标签定义和 Page_Init 中代码
  2. 回发 - 加载视图状态:查找页面中的隐藏字段 __VIEWSTATE,并更新控件属性
  3. 回发 - 加载回发数据:查找请求参数中的数据,并更新控件属性(本例中从请求参数中找文本输入框SimpleForm1$Text1的值)
  4. 加载阶段:执行 Page_Load 中的代码

上面看起来也很清楚,页面第一次加载时,执行如下过程:

  1. 实例化:页面标签 + Page_Init
  2. 加载:Page_Load

页面回发时,执行如下过程:

  1. 实例化:页面标签 + Page_Init
  2. 加载视图状态:从页面隐藏字段 __VIEWSTATE 中查找
  3. 加载回发数据:从当前 HTTP 的请求参数中查找
  4. 加载:Page_Load

如果对上面几个阶段不陌生,那我就要问一个问题了:

__VIEWSTATE里面的数据是怎么来的

这里有一个非常关键的关键点,在 9 年前的那篇文章中我反复提到:

当控件完成【加载视图状态阶段后,就会立即开始跟踪其视图状态的改变,之后任何对其属性的改变都会影响最终的控件视图状态。

这句话另一层含义就是:在【加载视图状态阶段之前,对控件属性的改变不会被跟踪,也不会记录到__VIEWSTATE 中来。

更加严格的说,上面的说法有点问题,因为页面第一次加载时没有【加载视图状态阶段】,更精确的描述:

  • 页面第一次加载时,将控件添加到层次结构树之后,即开始跟踪状态变化,并记录到 __VIEWSTATE
  • 页面回发时,在【加载视图状态阶段】之后,即开始跟踪状态变化,并记录到 __VIEWSTATE
    • 如果控件是在【加载视图状态阶段】之后添加到层次结构树的话,则在将控件添加到层次结构树之后开始跟踪状态变化,并记录到 __VIEWSTATE

我们再来看一眼最初的代码:

protected void Page_Init(object sender, EventArgs e)
{
    TextBox t = new TextBox()
    {
        ID = "Text1",
        Label = "动态创建的1",
        Text = DateTime.Now.ToString(),
        Enabled = false,
    };
    SimpleForm1.Items.Add(t);
}

可以发现问题了:

  1. 页面第一次加载时
    1. 在 Page_Init 中首先对Text1赋值:Text1.Text="2021-10-29 11:10:00"
    2. 但是这个赋值操作是在添加到层次结构树之前进行的,所以Text1.Text值不会被记录到 __VIEWSTATE 中
  2. 10分钟之后,页面回发时
    1. 在 Page_Init 中首先对Text1赋值:Text1.Text="2021-10-29 11:20:00"
    2. 加载视图状态时,从__VIEWSTATE 中回复 Text1 之前的状态,但是__VIEWSTATE 中没有找到

经过上面的详细分析,可以看出,页面第一次加载时,将 Text1 设置为 11:10,页面回发时按道理是应该保持这个值的,但是却被错误的更新为了 11:20 !

怎么为动态添加控件赋值呢我们也提出了一个最佳实践:

解决问题

把上面的逻辑搞清楚了,解决问题就不难了:

protected void Page_Init(object sender, EventArgs e)
{
    TextBox t = new TextBox()
    {
        ID = "Text1"
    };
    SimpleForm1.Items.Add(t);
}
protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
    {
        TextBox t = SimpleForm1.FindControl("Text1") as TextBox;
        t.Label = "动态创建的1";
        t.Enabled = false;
        t.Text = DateTime.Now.ToString();
    }
}
protected void btnSetValue_Click(object sender, EventArgs e)
{
    TextBox t = SimpleForm1.FindControl("Text1") as TextBox;
    t.Text = DateTime.Now.ToString();
}

运行效果:

内容来源网络,如有侵权,联系删除,本文地址:https://www.230890.com/zhan/62047.html

(0)

相关推荐

  • MySQL 5.5中SHOW PROFILE、SHOW PROFILES语句怎么用

    技术MySQL 5.5中SHOW PROFILE、SHOW PROFILES语句怎么用这篇文章将为大家详细讲解有关MySQL 5.5中SHOW PROFILE、SHOW PROFILES语句怎么用,小编觉得挺实用的,因此

    攻略 2021年10月30日
  • Postgresql性能相关操作系统及数据库说明

    技术Postgresql性能相关操作系统及数据库说明本篇内容主要讲解“Postgresql性能相关操作系统及数据库说明”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Postg

    攻略 2021年11月9日
  • 数据库迁移需要多长时间

    技术数据库迁移需要多长时间这篇文章将为大家详细讲解有关数据库迁移需要多长时间,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。什么是数据库迁移?数据库迁移是从一个数据库到另一个数据库的任何

    攻略 2021年10月26日
  • C++怎么将连结线程看作范围化的容器

    技术C++怎么将连结线程看作范围化的容器这篇文章主要讲解了“C++怎么将连结线程看作范围化的容器”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“C++怎么将连结线程看作范围

    攻略 2021年11月25日
  • 黄老,黄老道家与道家是什么关系

    技术黄老,黄老道家与道家是什么关系黄老道家是田氏取代姜子牙后裔齐国政权后,为证明其执政合法性而在思想领域新创造的学说,上联黄帝是为了便于修正老子道学理论,特别是田氏要找一个高于姜子牙祖宗炎帝的祖宗,当然最合适的就是黄帝了

    生活 2021年10月20日
  • python中如何计算个数(python怎么求球的体积)

    技术Python怎么计算球的个数这篇文章主要讲解了“Python怎么计算球的个数”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Python怎么计算球的个数”吧!代码如下:

    攻略 2021年12月17日