65.9K
CodeProject 正在变化。 阅读更多。
Home

带样式和基于区域的面板容器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.73/5 (11投票s)

2006年10月26日

6分钟阅读

viewsIcon

82707

downloadIcon

957

可拖放控件到自定义面板容器,并支持基于区域的可编辑性

Sample Image - Image1.gif

引言

我将尝试解释如何使用现有的控件,通过 ASP.NET 2.0 构建一个带样式和基于区域编辑的简单面板。我们将它命名为 shSimplePanel。

Framework.NET 2.0 为我们提供了用于构建自定义控件的新控件和基类。System.Web.UI.WebControls.CompositeControl 提供了构建实现 INamingContainerWebControl 的自定义控件的基类。System.Web.UI.WebControls.CompositeControlDesigner 提供了构建具有区域化编辑的自定义设计时编辑器的基类。当我想要构建一个像面板控件一样的自定义控件时,我没有继承自 Panel,因为在设计时行为和外观会相同:只是一个方形控件。我想要一个自定义控件,它可以在工具箱中进行拖放,并添加标题文本、标题图像和内容图像。

shSimplePanel 在设计时执行这些任务。此外,我还实现了一个“即时图像”类,以便 shSimplePanel 在运行时仅允许图像的拉伸。虽然这个类不完整,但它是一个有用的工具。在本文的最后,我提供了一些参考资料。

背景

当我决定制作这个控件时,我在网上搜索了可能帮助我的文档和代码。Mohammed Mahmoud 编写了一个具有可编辑区域的 WebTabControl。它成为了这个简单控件的基础,并结合 MSDN 基于区域 的一些帮助,成为了关于如何在设计时使用基于区域的一个极好的参考。此外,该控件基于“开发 Microsoft ASP.NET 服务器控件和组件”一书中编写的样式类型技术。这是一个从 Panel Web 控件构建的控件,它实现了标题文本、标题图像、内容背景图像、标题和内容的样式以及设计时的区域编辑。shSimplePanel 的复合体由三个面板构成。

Screenshot - Image2.gif

使用代码

这个自定义控件实现了 VS.NET 2005 的 IntelliSense 在设计时使用的属性。使用这个控件很简单;只需将其拖放到 ASPX 页面上即可。

<cc1:shSimplePanel ID="ShSimplePanel1" runat="server" Height="269px" 
    TitleText="Title shSimplePanel">
    <ContentTemplate>
        This is a Content of shSimplePanel. You can to add controls too.<br />
        <asp:HyperLink ID="HyperLink1" runat="server" 
            NavigateUrl="HypExample1.aspx" Width="225px">
            HyperLink Example 1</asp:HyperLink><br/> 
        <asp:HyperLink ID="HyperLink2" runat="server" 
            NavigateUrl="HypeExample2.aspx" Width="223px">
            HyperLink Example 2</asp:HyperLink><br/>
        <asp:Button ID="Button1" runat="server" Text="Button" 
            Width="93px" OnClick="Button1_Click"/>
    </ContentTemplate>
</cc1:shSimplePanel>

构建代码

首先,您需要从 VS.NET 2005 的向导中创建一个 WebControlLibrary 项目。之后,实现这些引用

Adding References

接下来,为 shSimplePanel 实现这个骨架

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Drawing;
using System.Collections;
using System.Web.UI.Design;
using System.Web.UI.Design.WebControls;
using System.Drawing.Design;
using System.ComponentModel.Design;

namespace shSimplePanel
{
    [ParseChildren(true)]
    [Designer(typeof(shSimplePanelControlDesigner))]
    [ToolboxData("<{0}:shSimplePanel runat=server 
        width=300px height=100px >")]
    [ToolboxBitmap(@"Please Put here some path of your 
        ToolboxBitmap 16x16 pixeles")]
    public class shSimplePanel : CompositeControl
    {..the code..}

所有 CompositeControl 自定义控件都实现类似的骨架。我们稍后将定义 Designer metadataParseChildren(true) 表示所有子控件都将作为属性进行解析。ToolboxBitmap 允许为我们的自定义控件添加一些位图。现在我们将实现我们的 Panel 控件以及控件中使用的变量

        internal List<IAttributeAccessor> regions;
        private csImageOnFly IOFTitle = new csImageOnFly();
        private csImageOnFly IOFContent = new csImageOnFly();
        private ITemplate _contentTemplate;
        
        private Style _StyleTitle;
        private Style _StyleContent;

        //Controls
        private Panel cPanelMain;
        private Panel cPanelTitle;
        private Panel cPanelContent;
        private Label cLabelTitle;

在这段代码中,我们定义了我们的 Panel Web 控件以及我们的 Label 作为 LabelTitle。internal List<IAttributeAccessor> regions 变量定义了我们的区域组件。它被声明为 internal,因为我们将在 shSimplePanelControlDesigner 类中使用它。这个变量有一个 IAttributeAccessor 属性,因为它表明我们将在这个 List 中添加控件。还定义了 csImageOnFly 类,以实现标题和内容图像的拉伸。private ITemplate _contentTemplate 被声明用于启用定义可编辑区域的模板。最后是标题和内容面板的 Style。接下来,我们将定义这个自定义控件的属性

#region Properties of shSimplePanel

        .... for see all properties see the code please

        #region public ITemplate ContentTemplate
        [Browsable(false)]
        [MergableProperty(false)]
        [DefaultValue(null)]
        [PersistenceMode(PersistenceMode.InnerProperty)]
        [TemplateContainer(typeof(shSimplePanel))]
        [TemplateInstance(TemplateInstance.Single)]
        public ITemplate ContentTemplate
        {
            get{return _contentTemplate;}
            set{_contentTemplate = value;}
        }
        #endregion

        .... for see all properties see the code please

#endregion Properties of shSimplePanel

这些属性您可能很熟悉;唯一特殊的是 ContentTemplate。我声明了 Browsable(false),因为它不会显示在 VS.NET 的属性浏览器中。我将 TemplateContainer(typeof(shSimplePanel)) 属性定义为主子控件容器。最后,TemplateInstance(TemplateInstance.Single) 允许按原样访问子控件。例如,一个 Button Web 控件将被视为其 NameID。现在我们重写 protected CreateChildControls 方法来创建我们的控件并设置区域的可编辑性

protected override void CreateChildControls()
{
  cLabelTitle = new Label();
  cPanelTitle = new Panel();
  cLabelContent = new Label();
  cPanelContent = new Panel();
  cPanelMain = new Panel();

  if (_contentTemplate != null)
  {
    _contentTemplate.InstantiateIn(cPanelContent);
  }

  cPanelMain.Controls.Add(cPanelTitle);
  cPanelMain.Controls.Add(cPanelContent);

  regions = new List<IAttributeAccessor>();
  regions.Add(cPanelContent);
  Controls.Add(cPanelMain);
}

这里我只构建了 shSimplePanel 的骨架,实例化了带有可编辑面板的 ContentTemplate,最后将区域添加到 List<IAttributeAccessor>protected Render 方法将允许控件在页面上进行写入。这是代码

protected override void Render(HtmlTextWriter writer)
{
  try
  {
    if (cPanelMain == null)
      CreateChildControls();
    //Label Titulo                
    cLabelTitle.Width = Width;
    cLabelTitle.Text = TitleText;
    //Panel Titulo
    cPanelTitle.Width = Width;
    cPanelTitle.Height = TitleHeight;
    cPanelTitle.Wrap = true;
    if ((ImageTitle != null) && (ImageTitle.Length > 0))
    {
      string resStretch;
      if (StretchImageTitle)
      {
        if (DesignMode)
        {
          cPanelTitle.BackImageUrl = ResolveClientUrl(ImageTitle);
        }
        else
        {
          IOFTitle.SourceFileName = 
              HttpContext.Current.Server.MapPath(ImageTitle);
          resStretch = 
              IOFTitle.StartConvertTo(
                  Convert.ToInt32(cPanelTitle.Width.Value), 
                  Convert.ToInt32(cPanelTitle.Height.Value), "_tempTitle");
          if (resStretch != null)
            cPanelTitle.BackImageUrl = "~/" + 
            resStretch.Substring(Page.Server.MapPath(
                HttpContext.Request.ApplicationPath).Length+1);
          else
          {
            cPanelTitle.BackImageUrl = ResolveClientUrl(ImageTitle);
            writer.Write("IOFTitle Error: " + IOFTitle.ErrorDescription + 
                "<br >");
          }
        }
      }
      else
        cPanelTitle.BackImageUrl = ResolveClientUrl(ImageTitle);
    }
    else //set empty string at ImageUrl of Panel Title
    {
      cPanelTitle.BackImageUrl = String.Empty;
    }
    cPanelTitle.Controls.Add(cLabelTitle); 
        //Add the Label Title at cPanelTitle
    if (_StyleTitle != null)               
        //Apply the Style at cPanelTitle 
      cPanelTitle.ApplyStyle(StyleTitle);
    cPanelTitle.HorizontalAlign = HorizontalAlignTitle;  
        //And the property HorizontalAlign for Title
     
    ... The same technics is applied at Content Panel. See the code

  }
  catch (Exception e)
  {
    writer.Write("Render Error: <br >" + e.Message);
  }
}

这段代码简单易懂。首先,确保所有子控件都通过 CreateChildControls() 方法创建。之后,为面板设置相同的宽度。我们确保图像已设置,并在需要时进行拉伸。csImageOnFly 是一个简单的类,它会在同一路径下创建原始图像的副本,但已调整大小。要创建调整大小的图像,它需要最终的宽度和高度值、原始完整路径以及作为后缀的文本来标识创建的图像。

为了获取图像的完整路径,我使用了 HttpContext.Current.Server.MapPath 方法。这个类的问题是它总是创建调整大小的图像,并且不允许自动删除。:-( 最后,该方法返回创建的新图像的路径,如果发生错误则返回 null。之后,只需对 Panel 应用一些属性和样式。同样的技术也适用于内容面板。对于这个类,我为内容和标题面板使用了样式。这些是属性

        #region Custom Styles
        [
        Category("Appearance"),
        DefaultValue(null),
        PersistenceMode(PersistenceMode.InnerProperty),
        Description("PanelTitle Style"),
        ]
        public virtual Style StyleTitle
        {
            get
            {
                if (_StyleTitle == null)
                {
                    _StyleTitle = new Style();
                    if (IsTrackingViewState)
                        ((IStateManager)_StyleTitle).TrackViewState();
                }
                return _StyleTitle;
            }
        }
        ... for the title style see the code
        #endregion

这些属性仅供用户声明。这些样式的任务由受保护的方法 LoadViewStateSaveViewStateTrackViewState 管理。我将展示如何工作

        #region Custom state management
        protected override void LoadViewState(object savedState)
        {
            if (savedState == null)
            {
                base.LoadViewState(null);
                return;
            }
            else
            {
                object[] myState = (object[])savedState;
                if (myState.Length != 3)
                {
                    throw new ArgumentException("Invalid view state");
                }
                base.LoadViewState(myState[0]);
                ((IStateManager)StyleTitle).LoadViewState(myState[1]);
                ((IStateManager)StyleContent).LoadViewState(myState[2]);
            }
        }

        protected override object SaveViewState()
        {
            // Customized state management to save the state of styles.
            object[] myState = new object[3];

            myState[0] = base.SaveViewState();
            if (_StyleTitle != null)
                myState[1] = ((IStateManager)_StyleTitle).SaveViewState();
            if (_StyleContent != null)
                myState[2] = ((IStateManager)_StyleContent).SaveViewState();

            return myState;
        }
        protected override void TrackViewState()
        {
            // Customized state management to track the state 
            // of styles.
            base.TrackViewState();

            if (_StyleTitle != null)
                ((IStateManager)_StyleTitle).TrackViewState();
            if (_StyleContent != null)
                ((IStateManager)_StyleContent).TrackViewState();
        }
        #endregion

这些技术实现在“开发 Microsoft ASP.NET 服务器控件和组件”一书中。只有保存和加载样式会保存在状态管理器中。检查它是否总是在位置零 base.SaveViewState() 中保存。

shSimplePanelControlDesigner 类继承了 CompositeControlDesigner 以实现区域的可编辑性。首先,我们重写 Initialize 方法

public class shSimplePanelControlDesigner : CompositeControlDesigner
{
  private shSimplePanel _shSimplePanel;
  private int _currentRegion = -1;
  private int _nbRegions = 0;

  public override void Initialize(IComponent component)
  {
    _shSimplePanel = (shSimplePanel)component;
    base.Initialize(component);
    SetViewFlags(ViewFlags.DesignTimeHtmlRequiresLoadComplete, true);
    SetViewFlags(ViewFlags.TemplateEditing, true);
  }
...
}

在这里,我们获取了 shSimplePanel 组件,并通过 SetViewFlags(ViewFlags.TemplateEditing, true); 启用了设计时模式。然后重写 CreateChildControls 方法来为区域设置名称属性

protected override void CreateChildControls()
{
  base.CreateChildControls();
  if (_shSimplePanel.regions != null)
  {
    _nbRegions = _shSimplePanel.regions.Count;
    for (int i = 0; i < _nbRegions; i++)
    {
      _shSimplePanel.regions[i].SetAttribute(
          DesignerRegion.DesignerRegionAttributeName, i.ToString());
    }
  }
}

在这段代码中,我们获取了 shSimplePanel 类的 internal List 的区域,并设置了名称。然后我们重写 OnClick 事件来获取当前选定的区域

protected override void OnClick(DesignerRegionMouseEventArgs e)
{
  base.OnClick(e);
  _currentRegion = -1;
  if (e.Region != null)
  {
    for (int i = 0; i < _nbRegions; i++)
    {
      if (e.Region.Name == i.ToString())
      {
        _currentRegion = i;
        break;
      }
    }
    UpdateDesignTimeHtml();
  }
}

当用户单击内容面板时,OnClick 事件将被触发。在这里,我们将获取当前区域并更新设计时 HTML。为此,我们重写 GetDesignTimeHtml 方法

public override string GetDesignTimeHtml(DesignerRegionCollection regions)
{
  this.CreateChildControls();
  for (int i = 0; i < _nbRegions; i++)
  {
    DesignerRegion r;
    if (_currentRegion == i)
      r = new EditableDesignerRegion(this, i.ToString());
    else
      r = new DesignerRegion(this, i.ToString());
    regions.Add(r);
  }

  if ((_currentRegion >= 0) && (_currentRegion < _nbRegions))
    regions[_currentRegion].Highlight = true;
  return base.GetDesignTimeHtml(regions);
}

在这段代码中,我们为当前选定的区域设置了设计器可编辑区域,然后,我们只突出显示该区域。当您重写 TemplateGroups 属性时...

public override TemplateGroupCollection TemplateGroups
{
  get
  {
    TemplateGroupCollection collection = new TemplateGroupCollection();
    TemplateGroup group = new TemplateGroup("ContentTemplate");
    TemplateDefinition definition = new TemplateDefinition(this, 
        "ContentTemplate",  _shSimplePanel, "ContentTemplate", false);
    group.AddTemplateDefinition(definition);
    collection.Add(group);
    return collection;
  }
}

...您可以获得一组区域组,以便在设计时进行编辑,如下图所示

Editing on design-time

最后,重写 GetEditableDesignerRegionContentSetEditableDesignerRegionContent

public override string GetEditableDesignerRegionContent(
    EditableDesignerRegion region)
{
  IDesignerHost host = (IDesignerHost)Component.Site.GetService(
      typeof(IDesignerHost));
  if (host != null)
  {
    ITemplate contentTemplate;
    if (_currentRegion == 0)
    {
      contentTemplate = _shSimplePanel.ContentTemplate;
      return ControlPersister.PersistTemplate(contentTemplate, host);
    }
  }
  return String.Empty;
}

public override void SetEditableDesignerRegionContent(
    EditableDesignerRegion region, string content)
{
  if (content == null)
    return;
  IDesignerHost host = (IDesignerHost)Component.Site.GetService(
      typeof(IDesignerHost));
  if (host != null)
  {
    ITemplate template = ControlParser.ParseTemplate(host, content);
    if (template != null)
    {
      if (_currentRegion == 0)
      {
        _shSimplePanel.ContentTemplate = template;
      }
    }
  }
}

通过这个方法,我们可以在 ControlParserControlPersister 中获取当前模板以在设计时进行编辑。

结论

有很多文档可以帮助我们。书中引用的和其他文章对于自定义控件开发的初学者来说都非常出色。

我希望这对您有所帮助。如果我的英语或代码有任何错误,请接受我的道歉并通知我。感谢这个空间和时间。

历史

  • 22/05/2007 文章已编辑并发布到 CodeProject.com 主文章库。
  • 09/11/2006 属性中的 ViewState。
    • 运行时,由于未实现 ViewState,属性丢失了值。属性中使用的私有变量已被删除。
© . All rights reserved.