自定义服务器控件 - 基本类和用法

时间:2009-05-07 20:09:32

标签: asp.net custom-server-controls

我希望那里的人能够阐明创建服务器控件,控制基类和这些类的用法的主题。

这是我想要实现的一个例子。我想创建一个自定义面板控件,可以在ASPX标记中实例化,如下所示:

<acme:Panel ID="MyPanel" runtat="server" Scrolling="true">
  <Header>My Panel Header</Header>
  <Toolbars>
    <acme:Toolbar ID="Toolbar1" runat="server"/>
    <acme:Toolbar ID="Toolbar2" runat="server"/>
  </Toolbars>
  <Contents>
    <%-- Some Content for the Contents section --%>
  </Contents>
  <Footer>
    <%-- Some Content for the Footer section --%>
  </Footer>
</acme:Panel>

它应该呈现以下HTML:

<div id="MyPanel" class="panel scroll-contents">
  <div class="panel-header">
    <div class="panel-header-l"></div>
    <div class="panel-header-c">
      <div class="panel-header-wrapper">My Panel Header</div>
    </div>
    <div class="panel-header-r"></div>
  </div>
  <div class="panel-toolbars">
    // HTML of Toolbar control
  </div>
  <div class="panel-body">
    <div class="panel-body-t">
      <div class="panel-body-tl"></div>
      <div class="panel-body-tc"></div>
      <div class="panel-body-tr"></div>
    </div>
    <div class="panel-body-m">
      <div class="panel-body-ml"></div>
      <div class="panel-body-mc">
        <div class="panel-body-wrapper">
          // Contents
        </div>
      </div>
      <div class="panel-body-mr"></div>
    </div>
    <div class="panel-body-b">
      <div class="panel-body-bl"></div>
      <div class="panel-body-bc"></div>
      <div class="panel-body-br"></div>
    </div>
  </div>
  <div class="panel-footer">
    <div class="panel-footer-l"></div> 
    <div class="panel-footer-c">
      <div class="panel-footer-wrapper">
        // Footer contents
      </div>
    </div>
    <div class="panel-footer-r"></div>
  </div>
</div>

开发人员应该能够省略除“内容”部分之外的任何部分。不应呈现省略的部分的HTML。此外,用户应该能够在页面后面的代码中实例化/添加Panel控件,并向Panel控件的各个部分添加其他控件,如下所示:

ACME.Panel MyPanel = new ACME.Panel();
MyPlaceHolder.Controls.Add(MyPanel);

MyPanel.Header = "My Panel Header";

MyPanel.Toolbars.Controls.Add(new ACME.Toolbar());

MyPanel.Footer.Controls.Add(new Literal());

MyPanel.Contents.Controls.Add(new GridView());

我已阅读以下文章:通信部门对话:编写自定义ASP.NET服务器控件(哪个基类?)

由于我不希望开发人员改变我的控件的样式,System.Web.UI.Control基类应该足够了,但我还需要应用INamingContainer接口。因此我的控制应该是这样的:

using System.Web;
using System.Web.UI;
using System.Web.UI.Design;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Security.Permissions;
namespace ACME
{
    [ToolboxData("&lt;{0}:Panel runat=server></{0}:Panel >")]
    [ParseChildren(true)]
    public class Panel : Control, INamingContainer
    {
        private string _header;
        private ITemplate _toolbars;
        private ITemplate _contents;
        private ITemplate _footerContents;
        public DialogBox()
        {
        }
        [Browsable(false),
        PersistenceMode(PersistenceMode.InnerProperty)]
        public virtual ITemplate Toolbars
        {
            get { return _toolbars; }
            set { _toolbars = value; }
        }
        [Browsable(false),
        PersistenceMode(PersistenceMode.InnerProperty)]
        public virtual ITemplate Contents
        {
            get { return _contents; }
            set { _contents= value; }
        }
        [Browsable(false),
        PersistenceMode(PersistenceMode.InnerProperty)]
        public virtual ITemplate Footer
        {
            get { return _footerContents; }
            set { _footerContents = value; }
        }
    }
}

我已阅读了许多教程,但它们要么不涵盖我的预期实现,要么解释为什么采取某种方法。他们也让我很困惑,以至于我使用JavaScript来动态呈现必要的HTML。如果有任何控制大师在那里,你能解释一下你将如何处理这项任务吗?

1 个答案:

答案 0 :(得分:2)

这将是一个漫长的过程,需要遵循许多代码:

因为你想要渲染很多常见元素,所以我开始使用BaseControl定义一些常用方法来生成你想要的所有div。这继承自System.Web.UI.Control - 作为文档状态:

  

这是您在开发自定义ASP.NET服务器控件时派生的主类。 控件没有任何用户界面(UI)特定功能。如果您正在创作没有UI的控件,或者组合了呈现自己的UI的其他控件,则可以从控件派生。

所以基本控件看起来像这样:

/// <summary>
/// Provides some common methods.
/// </summary>
public class BaseControl: Control
{
    protected Control[] TempControls;

    /// <summary>
    /// Clears the child controls explicitly, and stores them locally.
    /// </summary>
    protected void ClearControls()
    {
        if (HasControls())
        {
            TempControls = new Control[Controls.Count];
            Controls.CopyTo(TempControls, 0);
        }

        Controls.Clear();
    }
    /// <summary>
    /// Creates a new panel (HTML div) with the requested CSS 
    /// and containing any controls passed in.
    /// </summary>
    /// <param name="cssClass">The CSS class to be applied</param>
    /// <param name="controls">Any controls that should be added to the panel</param>
    protected Panel NewPanel(string cssClass, params Control[] controls)
    {
        // Create a new Panel, assign the CSS class.
        var panel = new Panel { CssClass = cssClass };

        // Loop through the controls adding them to the panel.
        foreach (var control in controls)
        {
            panel.Controls.Add(control);
        }

        return panel;
    }

    /// <summary>
    /// Creates a new row of panels (HTML div), based on the CSS class prefix.
    /// The center panel holds the controls passed in.
    /// </summary>
    /// <param name="cssClassPrefix"></param>
    /// <param name="controls"></param>
    protected Panel NewRow(string cssClassPrefix, params Control[] controls)
    {
        // Expaned for clarity, but could all be passed in on one call.
        var row = NewPanel(cssClassPrefix);
        row.Controls.Add(NewPanel(cssClassPrefix + "-l"));
        row.Controls.Add(NewPanel(cssClassPrefix + "-c", controls));
        row.Controls.Add(NewPanel(cssClassPrefix + "-r"));

        return row;
    }
}

然后你需要创建一些控件来处理你的每个模板,我在这里创建了两个。

首先,一个生成一行的控件 - 由页眉和页脚使用:

public class AcmeSimple : BaseControl, INamingContainer
{
    private string m_CssPrefix;

    public AcmeSimple(string cssPrefix)
    {
        m_CssPrefix = cssPrefix;
    }

    protected override void CreateChildControls()
    {

        ClearControls();

        Panel wrapper = NewPanel(m_CssPrefix + "-wrapper", TempControls);

        Panel simple = NewRow(m_CssPrefix, wrapper);

        Controls.Add(simple);

        base.CreateChildControls();
    }
}

它创建一个新面板来保存控件,然后创建一个新的div行来保存包装器。

然后是一个稍微复杂的内容控件,其工作原理与标题相同:

public class AcmeContents: BaseControl, INamingContainer
{
    protected override void CreateChildControls()
    {
        ClearControls();

        Panel wrapper = NewPanel("panel-body-wrapper", TempControls);

        Panel contents = NewPanel("panel-body");
        contents.Controls.Add(NewRow("panel-body-t"));
        contents.Controls.Add(NewRow("panel-body-m", wrapper));
        contents.Controls.Add(NewRow("panel-body-b"));

        Controls.Add(contents);

        base.CreateChildControls();
    }
}

所以这个只创建了三行,其中一行包含控件。

最后,您放置在页面上的实际控件:

[ParseChildren(true)]
[ToolboxData("<{0}:AcmeControl runat=server></{0}:AcmeControl>")]
public class AcmeControl: BaseControl, INamingContainer
{

    public bool Scrolling { get; set; }

    [TemplateContainer(typeof(AcmeSimple))]
    public ITemplate Header { get; set; }
    [TemplateContainer(typeof(AcmeContents))]
    public ITemplate Contents { get; set; }
    [TemplateContainer(typeof(AcmeSimple))]
    public ITemplate Footer { get; set; }

    protected override void CreateChildControls()
    {
        Controls.Clear();

        string cssClass = "panel";

        if (Scrolling)
        {
            cssClass += " scrollContents";
        }

        Panel panel = NewPanel(cssClass);
        panel.ID = ID;
        Controls.Add(panel);

        if (Header != null)
        {
            var header = new AcmeHeader("panel-header");
            Header.InstantiateIn(header);
            panel.Controls.Add(header);
        }

        if (Contents != null)
        {
            var contents = new AcmeContents();
            Contents.InstantiateIn(contents);
            panel.Controls.Add(contents);
        }
        else
        {
            // Possibly a little harsh, as it's a runtime exception.
            throw new ArgumentNullException("Contents", "You must supply a contents template.");
        }

        if (Footer != null)
        {
            var footer = new AcmeSimple("panel-footer");
            Footer.InstantiateIn(footer);
            panel.Controls.Add(footer);
        }
    }
}

因此,我们将支持的模板定义为控件的属性,以及您想要的Scrollable属性。然后,在CreateChildControls中,我们开始使用我们在开始时创建的控件和BaseControl中的方法构建控件的主体。

这就是这样的页面:

<cc1:AcmeControl ID="AcmeControl1" runat="server">
  <Header>
     <b>Here's a header</b>
  </Header>
  <Contents>
     <i>Here's some controls in the content.</i>
  </Contents>
</cc1:AcmeControl>

并且这样渲染出来:

<div id="AcmeControl1_AcmeControl1" class="panel">
    <div class="panel-header">
        <div class="panel-header-l">
        </div>
        <div class="panel-header-c">
            <div class="panel-header-wrapper">
                <b>Here's a header</b>
            </div>
        </div>
        <div class="panel-header-r">
        </div>
    </div>
    <div class="panel-body">
        <div class="panel-body-t">
            <div class="panel-body-t-l">
            </div>
            <div class="panel-body-t-c">
            </div>
            <div class="panel-body-t-r">
            </div>
        </div>
        <div class="panel-body-m">
            <div class="panel-body-m-l">
            </div>
            <div class="panel-body-m-c">
                <div class="panel-body-wrapper">
                    <i>Here's some controls in the content.</i>
                </div>
            </div>
            <div class="panel-body-m-r">
            </div>
        </div>
        <div class="panel-body-b">
            <div class="panel-body-b-l">
            </div>
            <div class="panel-body-b-c">
            </div>
            <div class="panel-body-b-r">
            </div>
        </div>
    </div>
</div>

所以唯一的区别是内容样式是t-l而不是tl。

然而(这对你来说可能是一个大问题),模板控件实际上并不是设计用于从代码隐藏中填充 - 你会注意到尝试编写:

AcmeControl1.Footer.Controls.Add([...]);

不会编译。

你可以做的是打电话:

AcmeControl1.Footer = Page.LoadTemplate([...])

并将路径传递给 ascx 文件。

可以找到有关创建模板化控件的进一步阅读:

我说会很久。