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中的代码:


 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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中的内容:

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

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

一些属性我们就不啰嗦了,懂点看一下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就知道了。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
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

posted @   qianlifeng  阅读(2344)  评论(11编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· .NET周刊【3月第1期 2025-03-02】
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
点击右上角即可分享
微信分享提示