1

我希望有人可以对创建服务器控件、控件基类和这些类的用法的主题有所了解。

这是我想要实现的一个例子。我想创建一个可以在 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>

开发人员应该能够省略除 Contents 部分之外的任何部分。不应呈现省略部分的 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。如果那里有任何控制大师,您能否解释一下您将如何完成这项任务?

4

1 回答 1

2

这将是一个很长的代码,需要遵循很多代码:

因为您有很多想要渲染的通用元素,所以我从一个 BaseControl 开始,它定义了一些通用方法来生成您所追求的所有 div。这继承自System.Web.UI.Control - 如文档所述:

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

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

/// <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>

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

但是(这对您来说可能是一个大问题),模板控件并不是真正设计为从代码隐藏填充 - 您会注意到尝试编写:

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

不会编译。

但是,您可以调用:

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

并传入ascx文件的路径。

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

我说会很长。

于 2009-05-08T00:19:27.683 回答