前面的两篇文章中,我们分别介绍了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小部件相关推荐

  1. 编程英语:widget [小部件]

    在刚开始学编程时感觉一定要学好英语才行,学着学着感觉不太懂英语也行,但在想深入学习编程时,感觉没有英语真不行. 养成写博客的习惯,加深对英语的学习. widget:这单词是在学习python GUI编 ...

  2. android 时间显示格式,Android setting中修改时间显示格式后,桌面的数字时钟widget小部件显示不更新...

    桌面的数字时间的widget的显示主要需要关注的有如下5个. vendor\mediatek\proprietary\packages\apps\DeskClock\src\com\android\a ...

  3. python tkinter 小部件汇总

    tkinter 小部件汇总,自己总结的.备用. # !/usr/bin/env python # -*- coding:utf-8 -*- # 随波逐流:tkinter 小部件汇总from tkint ...

  4. Android-RemoteView-桌面小部件

    Android-RemoteView-桌面小部件 学习自 <Android开发艺术探索> https://developer.android.google.cn/guide/topics/ ...

  5. Android 之窗口小部件详解--App Widget

    1 App Widget简介 App Widget是应用程序窗口小部件(Widget)是微型的应用程序视图,它可以被嵌入到其它应用程序中(比如桌面)并接收周期性的更新.你可以通过一个App Widge ...

  6. Android 之窗口小部件高级篇--App Widget 之 RemoteViews - 跨到对岸去

    在之前的一篇博文( Android 之窗口小部件详解--App Widge t)中,已经介绍了App Widget的基本用法和简单实例.这篇主要讲解 App Widget 的高级内容,即通过 Remo ...

  7. 显示农历天气时钟小部件下载_安卓最强桌面小部件:Zooper Widget

    天气.时钟还是其他应用的小部件,对于安卓用户来说,主屏放哪个小部件也是一件让人纠结的事,功能强大的,外观.尺寸不满意,外观好看的功能性又不够强,如何才能拥有一款二者兼顾的小部件呢,来看看可以自定义Wi ...

  8. android widget 发送广播,android-从应用程序向小部件发送数据

    我被困在一个特殊的场景中.用户从应用程序更新时间后,我需要立即更新我的小部件.我确实通过发送Intent Extras数据来尝试广播,但是没有这样做.当前,我的数据存储在AppWidgetProvid ...

  9. 自定义小部件Widget的探讨

    目录 一.前言 二.Widget基本使用 2.1 AppWidgetProvider继承类对象 2.2 AppWidgetProviderInfo资源配置文件 三.定制化需求 3.1 困境 3.2 自 ...

最新文章

  1. 研究生失联19天,父母焦急求助!最后竟然是在写博士研究计划......
  2. 合唱队形(递增再递减的最长子序列)
  3. webpack初探——js打包
  4. 英语和数学不好可以学python-西安童程童美Python人工智能少儿编程课程好不好
  5. opencv matlab测距,基于MATLAB和OpenCV的双目视觉测距系统的实现
  6. 如何实现Java类隔离加载?
  7. Qtum量子链研究院:Plasma扩容方案详解(上)
  8. 20-forEach循环语句
  9. 数据库优化、数据库基础等常用知识点总结
  10. Spring Boot中常见注解诠释
  11. jsp网页上实现计算三角形面积小程序
  12. 百度启动“2021好运中国年” 22亿福利项目
  13. 数字鉴相,关于相位差的提取
  14. 滴滴章文嵩:我们比AlphaGo面临的问题要复杂很多很多倍
  15. 用latex的tikz宏包mindmap包绘制mindmap
  16. 网络常用密码忘记破解方法
  17. 如何下载并安装turbo pascal 7.0?
  18. 从校园到职场 | YK菌的2022年中总结
  19. DHT网络原理制作bt采集蜘蛛,开源版
  20. 使用Java+OpenCV3制作,用于生成萌萌哒的GitHub默认头像

热门文章

  1. 如何防御DDoS攻击
  2. How to publish more papaers?
  3. everytime you write on a whiteboard
  4. string与stringBuilder的效率与内存占用实测
  5. 即将推出.NET Framework 4.7.2中的一些亮点
  6. Java利用QRCode.jar包实现二维码编码与解码
  7. TP5安装失败怎么办?
  8. leetcode 264. Ugly Number II
  9. POJ 2155 Matrix 二维线段树
  10. Asp.Net上传组件