为啥我的自定义用户控件的子项没有被初始化?

Posted

技术标签:

【中文标题】为啥我的自定义用户控件的子项没有被初始化?【英文标题】:Why are children of my custom user-control not being initialized?为什么我的自定义用户控件的子项没有被初始化? 【发布时间】:2010-09-30 13:28:58 【问题描述】:

更新: 我一直在试验我的控件,我想我已经接近了。请继续阅读以获取最新信息。

我有 2 个 ASP.NET 2.0 用户控件,其中一个放置在另一个 内部。在内部控件内部,我有一个 htmlAnchor 标记控件,我正在尝试为外部控件设置 URL。当我尝试从呈现页面的标记中设置HRef 属性时,HtmlAnchor 控件为null 并引发异常。

URL set 属性被调用内部或外部控件的 OnInit 事件之前以及主页的“OnPreInit”事件之前。我假设这是因为我在渲染控件的页面上的父控件标记中设置子控件的 URL,这意味着该值设置在 OnInit() 之前。这是我正在使用的代码的(精简)版本:

[ParseChildren(true)]// <- Setting this to false makes the 
                     //    page render without an exception 
                     //    but ignores anything in the markup.
[PersistChildren(false)]
public partial class OuterControl : System.Web.UI.UserControl

    // The private '_Inner' control is declared 
    // in the .ascx markup of the control
    [PersistenceMode(PersistenceMode.InnerProperty)]
    public InnerControl Inner
    
        get return _Inner; 
        set _Inner = value; 
    


public partial class InnerControl : System.Web.UI.UserControl

    // The private 'linkHref' control is declared
    // in the .ascx markup of the control
    public string Url
    
        get  return linkHref.HRef; 
        set  linkHref.HRef = value; 
    

OuterControl 在我的 Default.aspx 页面上使用如下:

<uc1:OuterControl ID="OuterCtrl1" runat="server">
    <Inner Url="#" />
</uc1:OuterControl>

在上面的标记示例中,如果我尝试呈现此页面,则会引发异常,因为 linkHref 控件为空。使用我的调试器,我可以看到 InnerControl 中的每个控件都是空的,但是当访问 Url 属性时,InnerControl 和 OuterControl 的 OnInit() 事件都没有被触发。

更新 我认为添加属性“ParseChildren”和“PersistChildren”会有所帮助。我以前在服务器控件中使用过它们,但从未在用户控件中使用过,尽管效果似乎相似。我认为我没有正确解释这两个属性的文档,但它可以阻止抛出异常。但是页面标记会被忽略。

有没有人知道如何进行这项工作。我不明白为什么这些控件会在OnInit() 之前设置值。当我尝试使用 ASPX 标记设置它们的值时,InnerControl 的构造函数被调用了两次。一次根据标记设置值(我假设),然后再次在OnInit() 上设置值(我猜这就是为什么标记值被忽略的原因)。

这种努力是没有希望的还是我只是从错误的角度接近它?

【问题讨论】:

【参考方案1】:

您是否在页面的 OnInit 方法上设置 HRef?如果是这样,请尝试将作业移至 Page_Load。

从最外到最内的控件Init。这意味着,如果您确实在 Page 的 OnInit 上分配了值,则控件尚未初始化。

这是一个关于页面生命周期的不错的文档: http://www.codeproject.com/KB/aspnet/lifecycle.aspx

【讨论】:

我在页面的标记中设置 HRef,而不是 OnInit 方法。我知道这发生在 Page 的 OnPreInit() 事件之前。如果可能的话,我希望能够使用标记来设置控制值,因为这意味着不必重新编译来进行更改。【参考方案2】:

你说:

// The private 'linkHref' control is declared
// in the .ascx markup of the control

如果你把它设为受保护的控件,它会改变什么吗?

【讨论】:

【参考方案3】:

控制树是在Init()之后建立的;事实上, Init() 是在视图状态被反序列化之前将控件添加到树的地方。在构建控件树之前,您无法访问 linkHref。

一种可能的解决方案是存储 URL,直到您可以使用它。在内部控件中,将 Url 属性更改为简单的字符串:

public string Url  get; set; 

在内部控件的 Load 或 PreRender 事件处理程序中,将字符串传播到(现已初始化的)linkHref 对象:

linkHref.HRef = value;

【讨论】:

【参考方案4】:

你好,丹,

我之前遇到过类似的问题,嵌套控件的混乱,所以我发现自己很想提供帮助,我也将它标记为收藏,因为我喜欢它。

我将问题重现如下:-

创建了一个名为 Inner 的用户控件:

Inner.ascx

<%@ Control Language="C#" AutoEventWireup="true" CodeFile="Inner.ascx.cs" Inherits="Inner" %>
<a runat="server" id="linkHref">I'm inner</a>

Inner.ascx.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

public partial class Inner : System.Web.UI.UserControl

    public string Url
    
        get
        
            return this.linkHref.HRef;
        
        set
        
            this.linkHref.HRef = value;
        
    

创建了一个名为 Outer 的用户控件:

外部.ascx

<%@ Control Language="C#" AutoEventWireup="true" CodeFile="Outer.ascx.cs" Inherits="Outer" %>
<%@ Register src="Inner.ascx" tagname="Inner" tagprefix="uc1" %>
<uc1:Inner ID="_Inner" runat="server" />

外部.ascx.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

public partial class Outer : System.Web.UI.UserControl

    [PersistenceMode(PersistenceMode.InnerProperty)]
    public Inner Inner
    
        get
        
            return this._Inner;
        
    

然后创建了一个名为Default的页面:

默认.aspx

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>

<%@ Register src="Outer.ascx" tagname="Outer" tagprefix="uc1" %>
<%@ Reference Control="~/Inner.ascx" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <uc1:Outer ID="Outer1" runat="server">
            <Inner Url="http://google.com" />
        </uc1:Outer>
    </div>
    </form>
</body>
</html>

有了所有这些,“默认”页面运行良好。

我在你的代码中发现奇怪的是这一行:

public InnerControl Inner
    
        //...
        set _Inner = value; 
    

为内部控件创建 setter 有什么意义?我无法理解。内部控件实例应该是从外部控件中的标记创建的,并且正是这个创建的实例,其 html 锚点应该被操纵。 如果我将这些行添加到 Outer.ascx.cs 中的 Inner 属性

set

    this._Inner = (ASP.inner_ascx)value;

我会像原来的情况一样得到一个空引用异常。 我认为 setter 指示 ASP.Net 页面构建器创建另一个 Inner 控件实例并使用 Inner 属性对其进行设置。我不确定,但如果您有时间,您可以通过检查页面构建器生成的 cs 文件来确切了解这是如何发生的,它们位于临时 ASP.Net 文件中。

【讨论】:

【参考方案5】:

如果 OuterControl.ascx 看起来像

<%@ Register TagPrefix="uc1" TagName="InnerControl" Src="InnerControl.ascx" %>
<uc1:InnerControl runat="server" ID="_Inner" />

那么问题是OuterControl.Inner 属性有一个set 访问器。删除set 访问器以解决问题:

[PersistenceMode(PersistenceMode.InnerProperty)]
public InnerControl Inner

    get  return _Inner; 

解释:如果OuterControl.Inner 有一个set 访问器...

    首先,ASP.NET 实例化ASP.outercontrol_ascx(从OuterControl 派生的动态生成的类),然后再实例化ASP.innercontrol_ascx 的实例(从InnerControl 派生的动态生成的类)。李> 接下来,因为 Inner 属性有一个 set 访问器,ASP.NET 会创建一个新的 InnerControl 实例(不是 ASP.innercontrol_ascx,正如您所料)并尝试将其Url 属性初始化为"#"。当然,这会抛出 NullReferenceException,因为 linkHref 是由 ASP.innercontrol_ascx 创建的,而不是由 InnerControl 创建的。 最后(如果没有引发异常),ASP.NET 将 Inner 设置为它在步骤 2 中创建的 InnerControl 实例。

如果OuterControl.Inner 没有set 访问器,那么在第2 步中,ASP.NET 只需将OuterCtrl1.Inner.Url 设置为"#",并跳过第3 步。

注意:其他几个答案表明控件是在Init 中创建或初始化的。这是不正确的;在标记中声明的控件是在页面生命周期的早期创建和初始化的,在 FrameworkInitialize 中(发生在 PreInit 之前)。

【讨论】:

以上是关于为啥我的自定义用户控件的子项没有被初始化?的主要内容,如果未能解决你的问题,请参考以下文章

在 WPF C# 中无法访问用户控件的自定义属性

为啥我的自定义控件不总是接收 MouseEnter 事件?

C# WinForm 用户控件的自定义事件问题

我的自定义控件在页面中为何不能显示?

设置绑定到 WPF 用户控件内的自定义 DependencyProperty

为什么工具提示出现在我的自定义控件上?