BlogEngine(4)---Widget小部件
?? 前面的兩篇文章中,我們分別介紹了BE的插件和主題機制,這一篇我們來看看BE三大特性中的最后一個:Widget。
所謂的widget,在BE中可以理解為一塊特定的顯示區域,在這個區域中可以用來顯示文章分類信息,博主個人信息,訪客信息等等一系列你可以想到的東西。在BE中,一個widget就是一個用戶控件,統一放在widget目錄中。當用戶想添加自己的widget時只需要在widget下添加以這個widget命名的文件夾以及對應的widget控件,相當的方便。下面咱們就來通過一個簡單的例子來“重現”widget的實現方法。當然,在這個例子中我只是實現了“顯示”而已,額外的“編輯”,“排序”在弄懂了下面的實現后應該不難。
"重現"widget
?? 首先看一下項目圖,我仍然使用的上次實現主題更換的那個項目。只不過添加了一個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=\"添加部件\" οnclick=\"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=\"#\" οnclick=\"BlogEngine.widgetAdmin.removeWidget('{0}');return false\" title=\"{1} widget\"><span class=\"widgetImg imgDelete\">?</span></a>", widgetId, "delete"); sb.AppendFormat("<a class=\"edit\" href=\"#\" οnclick=\"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=\"#\" οnclick=\"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的實現方式已經說完了,不知道你是否已經明白其中的流程?這是最后的效果圖:
源碼下載
http://www.vdisk.cn/down/index/7644535A9490
轉載于:https://www.cnblogs.com/qianlifeng/archive/2011/05/03/2034899.html
總結
以上是生活随笔為你收集整理的BlogEngine(4)---Widget小部件的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: idea base64encoder没有
- 下一篇: rabbitmq direct 多个消费