动态切换主题(皮肤skin)
准备知识:
主题是ASP.NET 2.0中的新功能,能够对网页外观进行更多的控制。主题可以定义颜色配置、字体名称、字体大小和样式、甚至是图片(是方角还是圆角,或者使用不同的颜色和阴影)。ASP.NET 2.0中增加了对"skin"的支持,这是对CSS想法的一种扩展。每个用户都可以从列表中选择一个主题,所选的主题决定了该用户会话期间网页的外观格式设置(称为"skin"),相对CSS样式表来说,skin是在服务器端的。skin文件与CSS文件类似,但与CSS不同的是,skin能够把页面中服务器端控件显式设置的外观属性值覆盖掉(全局CSS无法覆盖单独控件上的设置)。可以用主题来保存不同版本的图片,这样如果想拥有几套在当前skin基础上使用不同颜色配置的图片,这就能用到主题。然而,主题不能替代CSS,可以将CSS文件和skin文件结合起来使用实现灵活控制。至于样式表文件,在ASP.NET 2.0中没有什么创新,除了允许为一些新控件指定其CssClass属性外,还增加了一些可视化设计器支持的控件,以便能够为它们选择预定义的CSS样式。
主题是存放在网站App_Themes文件夹中子文件夹下的一组文件,包含以下项目:
CSS样式表文件,其中包含了对HTML对象外观的定义。
skin文件--这些文件定义了ASP.NET服务器端控件的外观,可以认为Skin文件是服务器端的样式表文件。
其他资源,例如图片。
ASP.NET 2.0在实现主题的方法上有个很酷的地方:当在页面上应用主题时,ASP.NET在运行时会自动为每个页面添加一个<link>元标记,用来链接主题文件夹中的各个CSS文件。这样很方便,因为对已存在的CSS文件重命名,或者增加新的CSS文件,页面仍然能够自动地链接它们。这个功能尤其重要,因为能够实现在运行时动态更换主题(就像更换模板页面)的功能,ASP.NET会对新主题文件夹中的文件进行链接,这样网站的外观就得以改变,从而适合不同用户的喜好。如果没有这个机制,就需要根据浏览者所选的主题为每一个页面手工创建<link>元标记,这是非常困难的。
除了在skin中声明时不需要指定控件的ID外,所有在.aspx页面中的声明都是需要指定控件ID的。当在页面上应用skin文件后,页面上的控件就采用skin文件中所指定的外观样式。对于TextBox控件来说这似乎没有什么优势,因为也可以在CSS样式表文件中为<input>定义一个样式类。然而,很多复杂控件都不能这样处理,例如Calendar、DataGrid (或者新的GridView 控件)等,这就要花费很多精力了,因为这些控件之间无法用一个HTML元素联系起来,因此不能轻易地在样式表文件中通过单个类为它们定义样式。
设计视图效果:
对某一页面应用主题,需要使用@Page指令中的Theme属性:
<%@ Page Title="" Language="C#" MasterPageFile="~/Site1.Master" Theme="Theme1" ...... %>
先看运行效果,通过下拉框选择皮肤主题,目前只有两个:Theme1和Theme2,页面默认加载的时候是Theme1
当选择Theme2时,页面如下显示:
文件夹结构中:Theme1下的Skin1.skin内容如下:
<asp:TextBox runat="server" BorderStyle="Dashed" BorderColor="#FF66FF" BorderWidth="3"></asp:TextBox>
而Theme2下的Skin2.skin内容如下:
<asp:TextBox runat="server" BorderStyle="Double" BorderColor="#6699FF" BorderWidth="5px"></asp:TextBox>
注意:
创建ThemeSelector用户控件
现在已有了一个模板页面和两个主题,所以可以开发一个能够显示可用主题并且用户可进行选择的用户控件。完成这个控件后,可以把它放到模板页面中。在创建用户控件之前,为了更好地组织文件,新建一个名为Controls的文件夹,该文件夹用来存放所有的用户控件,把它们同页面分开(选中项目,右击,在所出现的菜单中选择Add Folder | Regular folder,将其命名为Controls)。要创建用户控件,右击Controls 文件夹,从菜单中选择Add New Item | Web User Control,将新用户控件命名为ThemeSelector.ascx。该用户控件的内容非常简单,只包含一个字符串和下拉列表(DropDownList)。
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="ThemeSelector.ascx.cs" Inherits="切换主题.controls.ThemeSelector" %> <asp:DropDownList ID="ddlThemes" runat="server" AutoPostBack="True" > </asp:DropDownList> 对应的后置代码:[为讲解方便,达到一目了然的效果,去掉缓存相关代码和原书Helper类的环节]
public partial class ThemeSelector : System.Web.UI.UserControl { protected void Page_Load(object sender, EventArgs e) { string themePath = HttpContext.Current.Server.MapPath("~/app_themes");//获得实际物理路径 string[] themes = System.IO.Directory.GetDirectories(themePath);//该物理路径下的文件夹目录列表 for (int i = 0; i < themes.Length; i++) { themes[i] = System.IO.Path.GetFileName(themes[i]);//去掉路径,得到文件名。 如 c:\abc\theme1调用后得到 theme1 } ddlThemes.DataSource = themes; ddlThemes.DataBind(); ddlThemes.SelectedValue = this.Page.Theme; } }
如何在母板页或页面中使用用户控件?(注意红色矩形圆角框)
,如果你运行webform1.aspx页面,你可以看到下拉框可以显示出主题名列表了,当切换主题时,页面没有任何效果,文本框的样式并没有变化? 为什么?
1:哦,明白了,忘记设置
2: 还有对应的SelectedIndexChanged事件处理代码,
好了,应该可以了,马上运行,我们会看到什么呢?(一旦你切换主题)
处理主题切换的代码不能放在DropDownList的SelectedIndexChanged事件中,因为该事件在页面生命周期很晚时才发生。就像在"解决方案"部分所提到的,新主题必须放在页面的PreInit事件中。
没有办法,按要求继续前行.........
PreInit 事件中能访问到DropDownList吗?但是目前被封装成了用户控件里,又如何访问呢?
先偷懒,直接在WebForm1.aspx中放一个DropDownList控件,如下图:
在Page_PreInit事件中写入代码,并执行:
此方法也不行,那如何解决呢?请开动脑筋,思考中……………………………………………………………
ASP.NET中的Request获取数据的方法
可以使用 Request 对象访问任何基于 HTTP 请求传递的所有信息,包括从 HTML 表格用 POST 方法或 GET 方法传递的参数、cookie 和用户认证。Request 对象使您能够访问客户端发送给服务器的二进制数据。
改动代码如下,并设置ddlTest的AutoPostBack为True;
可以看到ids[8]="ctl00$ContentPlaceHolder1$ddlTest" ,那我们读取一下这个 Request.Form["ctl00$ContentPlaceHolder1$ddlTest" ]的值,看看是什么?
第一次运行:
继续运行,看到页面,切换ddlTest的选择项,
再来看断点处:
我们总算看到了选择的值了,意味着通过Request.Form[控件的Id]是能够读到页面回传来的客户端改动的值的,呵呵!^_^
剩下的问题好解决了:
ctl00$ContentPlaceHolder1$ddlTest这是什么?(还是看代码吧!我喜欢看事实)
所以:控件的ID是:
查看页面源代码:
控件的ClientID是指生成的页面源代码中的ID
而通过Request.Form方式是放在服务器后置代码中访问的,自然是控件的UniqueID 。
好了,改动代码,并运行:(胜利就在前方。。。。。。。。。。。)
难道“鱼”和“熊掌”就不能兼得吗?(这个就交给各位读者了,嘿嘿~~~~~~~~~~~~)
来看兼得的方案:
public partial class WebForm1 : System.Web.UI.Page { static string ThemeSelectorID="";//静态变量来存储 protected void Page_PreInit(object sender, EventArgs e) { string theme = Request.Form[ThemeSelectorID]; if (theme!=null) { this.Theme = theme;//改变页面的主题 } } protected void Page_Load(object sender, EventArgs e) { if (ThemeSelectorID.Length == 0) ThemeSelectorID = ddlTest.UniqueID;//记录下来(下拉控件)的服务器端ID } }
运行,看效果:
问题解决了!
但是我们需要访问的是上面的那个用户控件封装的下拉框啊!如何访问呢?(这个肯定难不倒你啦!)
protected void Page_Load(object sender, EventArgs e) { if (ThemeSelectorID.Length == 0) ThemeSelectorID = this.Master.FindControl("ThemeSelector1").FindControl("ddlThemes").UniqueID;//麻烦的写法 }
千万要注意:ddlThemes在用户控件ThemeSelector1的容器中(容器中找里面的控件用FindControl方法),而用户控件又在母板页Site1.Master中,这个写法大麻烦了,有更简便的写法吗?【为什么不直接在用户控件中获取ddlThemes的UniqueID呢?----用户控件有自己的加载事件Page_Load】
既要在ThemeSelector.aspx.cs中存ddlThemes.UniqueID,后续又要在WebForm1.aspx.cs中读这个变量,这样干脆建立一个静态类Helpers的静态变量ThemesSelectorID.
好的,看代码:
public static class Helpers { public static string ThemesSelectorID = ""; }
好的,当然书上的代码更加优良:补充如下:
1:重写的BasePage(自定义页面基类)的OnPreInit方法
webForm1.aspx.cs改动如下:
这里面连代码都不需要了!强悍!(继承的强大性!)
当来之世,经道灭尽,我以慈悲哀愍,特留此经止住百岁。其有众生,值斯经者,随意所愿,皆可得度。(南无阿弥陀佛)
道可道,非常道;名可名,非常名。
我常说,一个国家,一个民族,亡国都不怕,最可怕的是一个国 家和民族自己的根本文化亡掉了,这就会沦为万劫不复,永远不会翻身。---南怀瑾<国学大师>