zoukankan      html  css  js  c++  java
  • BlogEngine(4)Widget小部件

       前面的两篇文章中,我们分别介绍了BE的插件和主题机制,这一篇我们来看看BE三大特性中的最后一个:Widget。

    所谓的widget,在BE中可以理解为一块特定的显示区域,在这个区域中可以用来显示文章分类信息,博主个人信息,访客信息等等一系列你可以想到的东西。在BE中,一个widget就是一个用户控件,统一放在widget目录中。当用户想添加自己的widget时只需要在widget下添加以这个widget命名的文件夹以及对应的widget控件,相当的方便。下面咱们就来通过一个简单的例子来“重现”widget的实现方法。当然,在这个例子中我只是实现了“显示”而已,额外的“编辑”,“排序”在弄懂了下面的实现后应该不难。

    "重现"widget

    2011-05-02_203547

       首先看一下项目图,我仍然使用的上次实现主题更换的那个项目。只不过添加了一个widgets文件夹,并在其中放置了Search和TextBox两个widget,具体的widget.ascx中的内容我们后面再看。

       重点看下面三个用户控件。

           WidgetBase.ascx:这个用户控件时所有widget的基类,所有的widget都要继承这个用户控件。它定义了所有widget的一些通用的属性,比如名字,是否可编辑,是否显示标题等等。

         WidgetContainer.ascx:这个用户控件可以看成是对widget的一层包装,所有的widget最后并不是直接显示到页面中的,而是要经过这个控件的包装确定统一的显示外观后再显示到页面中。这样做的好处显而易见,用户在前台能够看到一个具有统一界面与操作体验的widget。

          WidgetZone.ascx:所有的用户控件最后不可能散落的显示在页面的各个地方,肯定有一个专门的地方(容器)在盛放这些控件。而这个WidgetZone就是用来盛放这些控件的了。

     

       大体介绍了几个必要的控件的作用。我们从具体的一个widget入手(比如说Search),来看看到底widget是怎样被加入到页面中并显示出来的。下面是search的widget.ascx中的代码:


     

     

    public partial class Widget : WidgetBase 
       {
    
           public override bool IsEditable 
           { 
               get 
               { 
                   return true; 
               } 
           }
    
           public override string Name 
           { 
               get 
               { 
                   return "搜索"; 
               } 
           }
    
           public override void LoadWidget() 
           { 
               //var settings = this.GetSettings(); 
               //if (!settings.ContainsKey("content")) 
               //{ 
               //    return; 
               //}
    
               string content = "<input type='text' id='key' /><input type='submit' value='search' id='btnSubmit'/>";
    
               LiteralControl text = new LiteralControl { Text = content }; 
               this.Controls.Add(text); 
           } 
       }
    

    根据前面讲到的,这个widget必须继承WidgetBase,以便让每个widget都有统一的属性。在这个widget中没有自己的方法,都是通过override来重写了父类中的方法或者属性。那就来看看WidgetBase中的内容:

    public abstract partial class WidgetBase : System.Web.UI.UserControl 
      { 
          /// <summary> 
          ///     Gets a value indicating whether the header is visible. This only takes effect if the widgets isn't editable. 
          /// </summary> 
          /// <value><c>true</c> if the header is visible; otherwise, <c>false</c>.</value> 
          public virtual bool DisplayHeader 
          { 
              get 
              { 
                  return true; 
              } 
          }
    
          /// <summary> 
          ///     Gets a value indicating whether or not the widget can be edited. 
          ///     <remarks> 
          ///         The only way a widget can be editable is by adding a edit.ascx file to the widget folder. 
          ///     </remarks> 
          /// </summary> 
          public abstract bool IsEditable { get; }
    
          /// <summary> 
          ///     Gets the name. It must be exactly the same as the folder that contains the widget. 
          /// </summary> 
          public abstract string Name { get; }
    
          /// <summary> 
          ///     Gets or sets a value indicating whether [show title]. 
          /// </summary> 
          /// <value><c>true</c> if [show title]; otherwise, <c>false</c>.</value> 
          public bool ShowTitle { get; set; }
    
          /// <summary> 
          ///     Gets or sets the title of the widget. It is mandatory for all widgets to set the Title. 
          /// </summary> 
          /// <value>The title of the widget.</value> 
          public string Title { get; set; }
    
          /// <summary> 
          ///     Gets or sets the widget ID. 
          /// </summary> 
          /// <value>The widget ID.</value> 
          public Guid WidgetId { get; set; }
    
          /// <summary> 
          ///     Gets or sets the name of the containing WidgetZone 
          /// </summary> 
          public string Zone { get; set; }
    
          /// <summary> 
          /// GetSettings会根据WidgetID从储存介质中获得相应的配置信息(也就是内容信息)并存储在Cache中 
          /// </summary> 
          public StringDictionary GetSettings() 
          { 
              //MOCK 
              var cacheId = string.Format("be_widget_{0}", this.WidgetId); 
              if (this.Cache[cacheId] == null) 
              { 
                  StringDictionary s = new StringDictionary(); 
                  s.Add("content", "<a href='#' >text href</a>"); 
                  // var ws = new WidgetSettings(this.WidgetId.ToString()); 
                  this.Cache[cacheId] = s; 
              }
    
              return (StringDictionary)this.Cache[cacheId]; 
          }
    
          /// <summary> 
          /// 这个方法在用户自定义的widget中被重写 
          /// </summary> 
          public abstract void LoadWidget();
    
    
    
          protected override void Render(HtmlTextWriter writer) 
          { 
              if (string.IsNullOrEmpty(this.Name)) 
              { 
                  throw new NullReferenceException("Name must be set on a widget"); 
              }
    
              base.Render(writer); 
          } 
      }
    

    前面的那一大堆用英文注释的属性是我直接从BE中拿过来的,就是定义了一些widget的共有的特性。值得注意的有两个方法:1.GetSetting,这个方法用于从存储介质(数据库,xml)中获得这个widget的一些配置信息,相当于给每个widget提供了一个自由存储的功能。2.loadWidget,这个方法是一个抽象方法,在子类中实现。好像看到这里我们并没有看到这个方法是怎样被调用的,先不着急。我们接着往下看 :)

    现在widget都一切准备就绪了,就差其他人来将它加到特定的 显示区域显示了。这个任务交给widgetZone来完成。下面看看widgetZone的实现:

    public abstract partial class WidgetZone : System.Web.UI.UserControl 
    { 
        //用于存放所有的WidgetZone及其对应的子widget信息,无论WidgetZone有几个,这个只有一个 
        private static readonly Dictionary<string, XmlDocument> XmlDocumentByZone = 
            new Dictionary<string, XmlDocument>();
    
        private string zoneName = "be_WIDGET_ZONE";
    
        /// <summary> 
        /// 区域的名字(标志) 
        /// </summary> 
        public string ZoneName 
        { 
            get { return zoneName; }
    
            set { zoneName = value; } 
        }
    
        /// <summary> 
        /// 这个zone包含的子widgetxml信息 
        /// </summary> 
        private XmlDocument XmlDocument 
        { 
            get { return XmlDocumentByZone.ContainsKey(ZoneName) ? XmlDocumentByZone[ZoneName] : null; } 
        }
    
        protected override void OnInit(EventArgs e) 
        { 
            //从存储介质中获得这个widgetZone所包含的widget信息 
            if (XmlDocument == null) 
            { 
                //Mock data 
                string mockXml = "<widgets>" + 
                                 "<widget id=\"d9ada63d-3462-4c72-908e-9d35f0acce40\" title=\"TextBox\" showTitle=\"True\">TextBox</widget> " + 
                                 "<widget id=\"19baa5f6-49d4-4828-8f7f-018535c35f94\" title=\"Administration\" showTitle=\"True\">Administration</widget> " + 
                                 "<widget id=\"d81c5ae3-e57e-4374-a539-5cdee45e639f\" title=\"Search\" showTitle=\"True\">Search</widget> " + 
                                 "<widget id=\"77142800-6dff-4016-99ca-69b5c5ebac93\" title=\"Tag cloud\" showTitle=\"True\">Tag cloud</widget>" + 
                                 "<widget id=\"4ce68ae7-c0c8-4bf8-b50f-a67b582b0d2e\" title=\"RecentPosts\" showTitle=\"True\">RecentPosts</widget>" + 
                                 "</widgets>"; 
                XmlDocument xmlDocument = new XmlDocument(); 
                xmlDocument.LoadXml(mockXml); 
                XmlDocumentByZone[ZoneName] = xmlDocument; 
            } 
            base.OnInit(e); 
        }
    
        protected override void OnLoad(EventArgs e) 
        { 
            //将取出的每个widget控件写入 
            var zone = this.XmlDocument.SelectNodes("//widget"); 
            if (zone == null) 
            { 
                return; 
            }
    
            //// This is for compatibility with older themes that do not have a WidgetContainer control. 
            //var widgetContainerExists = WidgetContainer.DoesThemeWidgetContainerExist(); 
            //var widgetContainerVirtualPath = WidgetContainer.GetThemeWidgetContainerVirtualPath();
    
            foreach (XmlNode widget in zone) 
            { 
                var fileName = string.Format("{0}widgets/{1}/widget.ascx", Utils.RelativeWebRoot, widget.InnerText); 
                try 
                { 
                    //加载特定的控件,控件类型为WidgetBase(因为每个控件都继承了WidgetBase) 
                    var control = (WidgetBase)Page.LoadControl(fileName); 
                    if (widget.Attributes != null) 
                    { 
                        //从读取的xml属性中将值复制给control属性 
                        control.WidgetId = new Guid(widget.Attributes["id"].InnerText); 
                        control.Title = widget.Attributes["title"].InnerText; 
                        control.ShowTitle = control.IsEditable 
                                                ? bool.Parse(widget.Attributes["showTitle"].InnerText) 
                                                : control.DisplayHeader; 
                    }
    
                    control.ID = control.WidgetId.ToString().Replace("-", string.Empty); 
                    control.Zone = zoneName;
    
                    control.LoadWidget();
    
                    //将此控件包装到widgetContainer里面,这样每个control都有一个统一的外观(修改,删除按钮在这里统一) 
                    var widgetContainer = WidgetContainer.GetWidgetContainer(control); 
                    //将包装好的widget加入这个zone中 
                    Controls.Add(widgetContainer); 
                } 
                catch (Exception ex) 
                { 
                    //找不到则不加载 
                } 
            }
    
            base.OnLoad(e); 
        }
    
        protected override void Render(System.Web.UI.HtmlTextWriter writer) 
        { 
            writer.Write("<div id=\"widgetzone_{0}\" class=\"widgetzone\">", this.zoneName);
    
            base.Render(writer);
    
            writer.Write("</div>");
    
            //如果没有权限修改widget,则不输出管理按钮 
            //if (!Security.IsAuthorizedTo(Rights.ManageWidgets)) 
            //{ 
            //    return; 
            //}
    
            //var selectorId = string.Format("widgetselector_{0}", this.zoneName); 
            //writer.Write("<select id=\"{0}\" class=\"widgetselector\">", selectorId); 
            //var di = new DirectoryInfo(this.Page.Server.MapPath(string.Format("{0}widgets", Utils.RelativeWebRoot))); 
            //foreach (var dir in di.GetDirectories().Where(dir => File.Exists(Path.Combine(dir.FullName, "widget.ascx")))) 
            //{ 
            //    writer.Write("<option value=\"{0}\">{1}</option>", dir.Name, dir.Name); 
            //}
    
            //writer.Write("</select>  "); 
            //writer.Write( 
            //    "<input type=\"button\" value=\"添加部件\" onclick=\"BlogEngine.widgetAdmin.addWidget(BlogEngine.$('{0}').value, '{1}')\" />", //by Spoony 
            //    selectorId, 
            //    this.zoneName); 
            //writer.Write("<div class=\"clear\" id=\"clear\"> </div>"); 
        } 
    }
    

    一些属性我们就不啰嗦了,懂点看一下OnInit和OnLoad方法。在Oninit方法中会从存储介质中加载xml格式的需要加载的widget信息,里面记录了这个widgetZone需要加载哪些widget,注意到XmlDocumentByZone这个属性是个静态的方法,也就是说如果有多个widgetZone,那么这个键值对里面将会有多个值。接着看onload方法,先将前面读取到的xml中的所有widget标签解析出来,这样就能得到具体的widget的信息。然后通过Page.LoadControl来加载widgets文件夹下面对应的widget,然后根据读取的xml信息,给这个从loadControl中加载的widget设置一些基本的信息(因为继承了WidgetBase,所以这里的设值就可以统一了)。设置完之后调用control.LoadWidget(); 来执行用户在loadwidget方法中的代码。最后在通过widgetContainer将此widget包装一下加入这个zone,具体怎么包装的我们继续来看widgetContainer就知道了。

    public partial class WidgetContainer : System.Web.UI.UserControl 
    { 
        /// <summary> 
        /// 要包装的widget 
        /// </summary> 
        public WidgetBase Widget 
        { 
            get; 
            set; 
        }
    
        /// <summary> 
        /// 获得操作按钮的html代码 
        /// </summary> 
        protected string AdminLinks 
        { 
            get 
            { 
                //根据用户是否登录,判断是否显示操作按钮(删除,修改等) 
                //if (Security.IsAuthorizedTo(Rights.ManageWidgets)) 
                //{ 
                if (this.Widget != null) 
                { 
                    var sb = new StringBuilder();
    
                    var widgetId = this.Widget.WidgetId;
    
                    sb.AppendFormat("<a class=\"delete\" href=\"#\" onclick=\"BlogEngine.widgetAdmin.removeWidget('{0}');return false\" title=\"{1} widget\"><span class=\"widgetImg imgDelete\"> </span></a>", widgetId, "delete"); 
                    sb.AppendFormat("<a class=\"edit\" href=\"#\" onclick=\"BlogEngine.widgetAdmin.editWidget('{0}', '{1}');return false\" title=\"{2} widget\"><span class=\"widgetImg imgEdit\"> </span>", this.Widget.Name, widgetId, "edit"); 
                    sb.AppendFormat("<a class=\"move\" href=\"#\" onclick=\"BlogEngine.widgetAdmin.initiateMoveWidget('{0}');return false\" title=\"{1} widget\"><span class=\"widgetImg imgMove\"> </span></a>", widgetId, "move");
    
                    return sb.ToString(); 
                } 
                //}
    
                return String.Empty; 
            } 
        }
    
        /// <summary> 
        /// Raises the <see cref="E:System.Web.UI.Control.Load"/> event. 
        /// </summary> 
        /// <param name="e">The <see cref="T:System.EventArgs"/> object that contains the event data.</param> 
        protected override void OnLoad(EventArgs e) 
        { 
            base.OnLoad(e); 
            ProcessLoad(); 
        }
    
        private bool _processedLoad; 
        /// <summary> 
        /// Manually run the Initialization process. 
        /// </summary> 
        public void ProcessLoad() 
        { 
            if (_processedLoad) { return; }
    
            // phWidgetBody is the control that the Widget control 
            // gets added to. 
            var widgetBody = this.FindControl("phWidgetBody");
    
            if (widgetBody != null) 
            { 
                widgetBody.Controls.Add(this.Widget); 
            } 
            else 
            { 
                var warn = new LiteralControl 
                { 
                    Text = "无法在当前主题模板的部件容器中找到 ID 为 \"phWidgetBody\" 的控件."//by Spoony 
                }; 
                this.Controls.Add(warn); 
            }
    
            _processedLoad = true; 
        }
    
        /// <summary> 
        /// Raises the <see cref="E:System.Web.UI.Control.PreRender"/> event. 
        /// </summary> 
        /// <param name="e">An <see cref="T:System.EventArgs"/> object that contains the event data.</param> 
        protected override void OnPreRender(EventArgs e) 
        { 
            base.OnPreRender(e);
    
            // Hide the container if the Widget is null or also not visible. 
            this.Visible = (this.Widget != null) && this.Widget.Visible; 
        }
    
        /// <summary> 
        /// 从主题中得到widgetContainer的位置 
        /// </summary> 
        /// <returns></returns> 
        public static string GetThemeWidgetContainerVirtualPath() 
        { 
            return string.Format("~/themes/{0}/WidgetContainer.ascx", "stardard" /*为了演示方便,这里直接读取默认的主题*/); 
        }
    
        /// <summary> 
        /// 得到特定主题下的widgetContainer的物理位置 
        /// </summary> 
        /// <returns></returns> 
        public static string GetThemeWidgetContainerFilePath() 
        { 
            return HostingEnvironment.MapPath(GetThemeWidgetContainerVirtualPath()); 
        }
    
        /// <summary> 
        /// 是否存在widgetContainer文件 
        /// </summary> 
        /// <returns></returns> 
        public static bool DoesThemeWidgetContainerExist() 
        { 
            // This is for compatibility with older themes that do not have a WidgetContainer control. 
            return File.Exists(GetThemeWidgetContainerFilePath()); 
        }
    
        /// <summary> 
        /// 加载widgetContainer,用于包装widget,如果当前主题文件没有提供widgtContainer.ascx,则使用默认的容器 
        /// </summary> 
        /// <param name="widgetControl"></param> 
        /// <param name="widgetContainerExists"></param> 
        /// <param name="widgetContainerVirtualPath"></param> 
        /// <returns></returns> 
        private static WidgetContainer GetWidgetContainer( 
            WidgetBase widgetControl, bool widgetContainerExists, 
            string widgetContainerVirtualPath) 
        { 
            //如果主题提供了用于包装的widgetContainer,则读取。否则返回某人的WidgetContainer 
            WidgetContainer widgetContainer = widgetContainerExists ? (WidgetContainer)widgetControl.Page.LoadControl(widgetContainerVirtualPath) : new DefaultWidgetContainer();
    
            widgetContainer.ID = "widgetContainer" + widgetControl.ID; 
            widgetContainer.Widget = widgetControl;
    
            return widgetContainer; 
        }
    
        /// <summary> 
        /// 加载widgetContainer,用于包装widget,如果当前主题文件没有提供widgtContainer.ascx,则使用默认的容器 
        /// </summary> 
        public static WidgetContainer GetWidgetContainer( 
            WidgetBase widgetControl) 
        { 
            return GetWidgetContainer(widgetControl, DoesThemeWidgetContainerExist(), GetThemeWidgetContainerVirtualPath()); 
        } 
    }
    

    重点来看GetWidgetContainer这个方法。他有三个参数,第一个就是我们要包装的widget对象,第二标明了主题中是否提供了包装样式,如果没有那么就使用默认的包装样式,第三个参数是主体的虚拟路径,用来从主题文件中加载包装样式文件。接着,程序通过判断widgetContainerExists 来判断到底应该使用哪种包装样式,然后将传进来的widget对象赋值给这个包装对象的widget属性,供render的时候使用。具体的render方法并不在这个widgetContainer中。而是在默认提供的包装样式控件和主题提供的样式中,我们看一下某人提供的包装容器:

    internal sealed class DefaultWidgetContainer : WidgetContainer 
       { 
           /// <summary> 
           /// The widgetBody instance needed by all WidgetContainers. 
           /// </summary> 
           private readonly System.Web.UI.WebControls.PlaceHolder widgetBody = new System.Web.UI.WebControls.PlaceHolder 
           { 
               ID = "phWidgetBody" 
           };
    
    
    
           /// <summary> 
           /// Initializes a new instance of the <see cref="DefaultWidgetContainer"/> class. 
           /// </summary> 
           internal DefaultWidgetContainer() 
           { 
               this.Controls.Add(this.widgetBody); 
           }
    
    
    
           /// <summary> 
           /// Sends server control content to a provided <see cref="T:System.Web.UI.HtmlTextWriter"/> object, which writes the content to be rendered on the client. 
           /// </summary> 
           /// <param name="writer">The <see cref="T:System.Web.UI.HtmlTextWriter"/> object that receives the server control content.</param> 
           protected override void Render(HtmlTextWriter writer) 
           { 
               if (this.Widget == null) 
               { 
                   throw new NullReferenceException("WidgetContainer requires its Widget property be set to a valid WidgetBase derived control"); 
               }
    
               var widgetName = this.Widget.Name; 
               var widgetId = this.Widget.WidgetId;
    
               if (string.IsNullOrEmpty(this.Widget.Name)) 
               { 
                   throw new NullReferenceException("Name must be set on a widget"); 
               }
    
               var sb = new StringBuilder();
    
               sb.AppendFormat("<div class=\"widget {0}\" id=\"widget{1}\">", widgetName.Replace(" ", string.Empty).ToLowerInvariant(), widgetId); 
               sb.Append(this.AdminLinks); 
               if (this.Widget.ShowTitle) 
               { 
                   sb.AppendFormat("<h4>{0}</h4>", this.Widget.Title); 
               } 
               else 
               { 
                   sb.Append("<br />"); 
               }
    
               sb.Append("<div class=\"content\">");
    
               writer.Write(sb.ToString()); 
               base.Render(writer); 
               writer.Write("</div>"); 
               writer.Write("</div>"); 
           } 
       }
    

    在默认的提供的包装容器中,首先声明了一个placeholder用来放置widget,不然在WidgetContainer下的processLoad方法中会报错。主要还是看render方法,在这里就是具体怎样显示这个widget的外表了,比如标题应该显示在哪里,内容显示在哪里等等布局。这样就给所有的widget提供统一的样式了。

    好了,到这里widget的实现方式已经说完了,不知道你是否已经明白其中的流程?这是最后的效果图:

    1

    源码下载

    http://www.vdisk.cn/down/index/7644535A9490

  • 相关阅读:
    C#获取类以及类下的方法(用于Asp.Net MVC)
    ES6学习笔记
    在nuget上发布自己的程序集教程
    C#创建IIS站点及相应的应用程序池,支持IIS6.0+Windows Server 2003. 使用Builder设计模式
    ASP.Net Mvc实现自定义User Identity用户身份识别系统(2)
    ASP.Net Mvc实现自定义User Identity用户身份识别系统(1)
    C#实现.ini文件读写操作
    C#实现注册表 LocalMachine 目录下CURD工具类
    博客园打赏功能(未申请下js权限使用二维码打赏功能)
    WebServeice 动态代理类
  • 原文地址:https://www.cnblogs.com/qianlifeng/p/2034899.html
Copyright © 2011-2022 走看看