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

发布于 2024-07-18 08:22:48 字数 4156 浏览 3 评论 0原文

我希望有人能够对创建服务器控件、控件基类以及这些类的用法这一主题有所启发。

这是我想要实现的目标的一个例子。 我想创建一个可以在 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。 另外,用户应该能够在页面后面的代码中实例化/添加面板控件,并向面板控件的各个部分添加其他控件,如下所示:

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。 如果有任何控制大师,您能解释一下您将如何解决这项任务吗?

I hope someone out there can shed some light on the topic of creating server controls, the control base classes and the usage of those classes.

Here is an example of what I want to achieve. I want to create a custom panel control that one can instantiate in the ASPX markup like so:

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

It should render the following 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>

The developer should be able to omit any of the sections except the Contents section. Omitted sections' HTML should not be rendered. Plus the user should be able to instantiate/add a Panel control in the code behind of the page and add additional controls to the various sections of the Panel control, like so:

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());

I have read the following article : Communications Sector Conversations : Authoring Custom ASP.NET Server Controls (Which base class?)

Since I do not really want the developer to change the styling of my control, the System.Web.UI.Control base class should be sufficient, but I will also need to apply the INamingContainer interface. Thus my control should look like so:

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("<{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; }
        }
    }
}

I have read many tutorials but they either don't cover my intended implementation or explain WHY a certain approach was taken. They have also confused me so much that I have resorted to JavaScript to dynamically render the necessary HTML. If there are any Control Guru's out there, could you please explain how you would have tackled this task?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

怎言笑 2024-07-25 08:22:48

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

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

这是您在开发自定义 ASP.NET 服务器控件时派生的主要类。 Control 没有任何用户界面 (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 文件。

有关创建模板化控件的进一步阅读,请参阅:

我说过会很长。

This is going to be a long one, a lot of code to follow:

Because you have a lot of common elements that you want rendered out, I started with a BaseControl that defined some common methods to generate all the divs you're after. This inherits from System.Web.UI.Control - as the docs state:

This is the primary class that you derive from when you develop custom ASP.NET server controls. Control does not have any user interface (UI) specific features. If you are authoring a control that does not have a UI, or combines other controls that render their own UI, derive from Control.

So the base control looks like this:

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

You then need to create some controls that will handle each of your templates, I've created two here.

First up, a control that will generate a single row - used by both the header and footer:

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();
    }
}

It creates a new panel to hold the controls, and then creates a new row of divs to hold the wrapper.

Then a slightly more complex contents control, that works on the same principle as the header:

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();
    }
}

So this one just created three rows, the middle one of which contains the controls.

Finally, the actual control that you place on the page:

[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);
        }
    }
}

So we define the templates that we support as properties of the control, along with the Scrollable property you wanted. Then, in CreateChildControls we start building up the body of the control using the controls we created at the begining and the methods in the BaseControl.

This goes onto the page like this:

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

And renders out like this:

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

So the only difference is that the contents styles are t-l rather than tl.

However (and this could be a big issue for you), template controls aren't really designed to be filled from code-behind - you'll notice that trying to write:

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

won't compile.

What you can do however is call:

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

and pass in the path to an ascx file.

Further reading on creating templated controls can be found:

I said it would be long.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文