在@Page指令中设置主题的第一个问题是,不能依据需要实现的任何标准设置主题。因此,如果要让移动用户有一种主题,让其他用户有另一种主题,那么就不能在@Page指令中实现这一点,需要做的工作是以编程方式设置主题。
然而,在讨论如何操作之前,首先必须决定如何应用主题:通过Theme还是StyleSheetTheme。遗憾的是,这个决定会影响如何以编程方式设置主题(或者如何访问代码内的当前主题)。
9.3.1 Theme方法
第一种讨论假设要通过Theme方法设置主题。后面将介绍如何通过StyleSheetTheme方法设置主题。
对于Theme方法而言,重要的是熟悉页面生命周期中的Page_PreInit事件。Page_PreInit事件是.NET 2.0 Framework中引入的新事件,是在代码中能够访问的第一个事件(它在Page_Load或者Page_Init事件之前激活)。和母版页一样,如果要以编程方式设置主题,那么就必须在Page_PreInit事件中设置它们。
为了说明其工作方式,可以建立一个新项目(这里使用C:/Themes2)。该项目包含Default.aspx页面,其代码如下所示。
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs"
Inherits="_Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Untitled Page</title>
</head>
<body>
<form id="form1" runat="server">
<div>
hello, world
</div>
</form>
</body>
</html>
实际上这只是回复到第8章中Themes示例的最早版本(典型的“hello, world”示例)。在新项目内,创建一个新的主题专用文件夹(App_Themes),通过在App_Themes中添加子目录来为项目添加一个主题(myFirstTheme)。在新的主题子目录内,添加一个新的名为StyleSheet.css的样式表文档,其中包含下面的规则。
body
{
background-color: SteelBlue;
color: Silver;
font-size: xx-large;
}
可以猜想到,页面的Theme属性是Page对象的一部分,因此为了设置主题,代码中需要包含如下所示的代码。
Page.Theme = "myFirstTheme";
为了说明在页面生命周期中设置主题太晚会发生什么情况,可以尝试在Page_Load事件中定义主题,如下所示。
protected void Page_Load(object sender, EventArgs e)
{
Page.Theme = "myFirstTheme";
}
现在运行应用程序,就会得到如图9-1所示的错误信息。
图9-1
这条错误信息表明“只能在Page_PreInit事件内或者该事件之前设置Theme属性”。这条错误消息可能不像所熟悉的其他消息,不需要过多地对其进行解释。实际上,它只是说明了本章已经介绍的内容:必须在Page_PreInit事件中设置主题。因此将修改代码,如下所示。
protected void Page_PreInit(object sender, EventArgs e)
{
Page.Theme = "myFirstTheme";
}
现在如果运行应用程序,页面就会顺利通过编译,如图9-2所示。
现在,可以更好地控制如何为页面设置主题。例如,如果需要Internet Explorer的简单浏览器检测,只为IE浏览器应用该主题,那么可以修改代码,如下所示。
protected void Page_PreInit(object sender, EventArgs e)
{
if (Page.Request.Browser.Browser == "IE")
{
Page.Theme = "myFirstTheme";
}
else
{
Page.Theme = "";
}
}
图9-2
执行该操作最初看起来可能没有完成任何工作。例如,如果将项目加载到默认浏览器,而该浏览器刚好是某个版本的Internet Explorer,那么就会看到与图9-2完全相同的结果。
然而,如果在不同的浏览器中(例如在Mozilla Firefox中)重新加载该页面,就会发现根本没有设置主题,它看起来像白色页面,没有格式应用于页面上的任何元素,如图9-3所示。
用于获得这种结果的代码有点多余,因为其实不需要将主题设置为非IE浏览器的空字符串值,这样做只是为了便于思考。在项目中可以实现什么样的if...then...else逻辑来设置不同的主题?注册用户还是非注册访问者?管理员还是其他任何人?基于文本还是GUI (为了便于用户访问)?Internet Explorer访问者还是其他浏览器访问者?夜晚还是白天?冬天还是夏天?美国还是英国?配置文件设置?一旦控制设置主题的实际过程,就可以依据对项目有意义的实际标准来设置主题。
图9-3
另外有必要指出的是,这种方法允许在项目中使用Theme设置作为自身的标准。例如,可以在Page_Load事件中使用下面的代码将实际设置的主题写入浏览器。
protected void Page_Load(object sender, EventArgs e)
{
if (Page.Theme != "")
{
Response.Write("<p>Theme: " + Page.Theme + "</p>");
}
else
{
Response.Write("<p>Theme: No Theme Has Been Set</p>");
}
}
在第8章中已经介绍过其中一些代码,但现在可以更实际地了解在Page_PreInit事件中如何使用Page.Theme以编程方式设置主题。
这也提醒了用户注意母版页的限制之一:没有Page_PreInit事件。这就是不能在母版页上设置主题的原因。
9.3.2 StyleSheetTheme方法
如果对StyleSheetThemes不太了解,可能认为设置这种属性的方式与将Theme属性应用于页面的方式相同。但幸运的是,您已经知道这两种方法之间存在一些重大区别,将这两种属性设置到页面时,直观上这些差别会转移。
例如,修改上面的Page_PreInit事件,使用StyleSheetTheme取代Theme。
protected void Page_PreInit(object sender, EventArgs e)
{
if (Page.Request.Browser.Browser == "IE")
{
Page.StyleSheetTheme = "myFirstTheme";
}
else
{
Page.StyleSheetTheme = "";
}
}
输入该代码时可能会注意到,在输入Page之后(或者至少在输入属性的一些字母之后),IntelliSense中就会出现StyleSheetTheme。完成这些改动之后,可能就会发现代码中没有出现错误。但是如果尝试运行代码,就会得到如图9-4所示的错误信息。
图9-4
如同所预测的那样,该错误消息表明不能按照设置Theme的方法来设置StyleSheetTheme。那么应该如何设置StyleSheetTheme呢?正如错误消息所说明的那样,必须在页面中重写StyleSheetTheme属性。为了实现这一点,需要在代码中合并下列代码。
public override string StyleSheetTheme
{
get
{
return "myFirstTheme";
}
set
{
base.StyleSheetTheme = value;
}
}
根据.NET编码的经验可知,使用get{} set{}存取器通过页面中的代码重写页面的属性。这里使用代码重写页面的StyleSheetTheme属性。
然而更有意思的是,不能使用这种方法合并Page_PreInit事件。这可能在“不能在母版页中设置主题”原则中找到了一个漏洞:可以在母版页中重写StyleSheetTheme属性。
遗憾的是,该方法也不可行。如果这样做,就会得到下面的错误信息。
Compiler Error Message: CS0115: ‘MasterPage.StyleSheetTheme’: no suitable method found to override
因此,与Page_PreInit事件非常类似,在母版页层中不可以使用StyleSheetTheme属性,这就意味着这种方法也不起作用。
回到上面的示例——像下面这样修改Default页面的代码。
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
public partial class _Default : System.Web.UI.Page
{
public override string StyleSheetTheme
{
get
{
if (Page.Request.Browser.Browser == "IE")
{
return "myFirstTheme";
}
else
{
return "";
}
}
set
{
base.StyleSheetTheme = value;
}
}
protected void Page_Load(object sender, EventArgs e)
{
if (Page.StyleSheetTheme != "")
{
Response.Write("<p>StyleSheetTheme: " + Page.StyleSheetTheme + "</p>");
}
else
{
Response.Write("<p>StyleSheetTheme: No StyleSheetTheme
Has Been Set</p>");
}
}
}
正如所看到的,将浏览器标准添加到StyleSheetTheme重写的get{}存取器,从而依据客户端是不是Internet Explorer来设置主题。运行该代码,对于Internet Explorer而言,输出结果如图9-5所示;对于其他浏览器而言,输出结果如图9-6所示。
图9-5
图9-6
9.3.3 程序化方法的缺点
将程序化标准集成到Theme逻辑的应用程序中,这种思想有许多优点。通过执行该操作,就可以重新控制主题以及应用它的方式。可以依据需要设置的任何标准来设置主题。
但是该方法至少有一个严重的缺点:代码驻留在每个页面上。在项目每个页面的代码中都要设置在这些示例中看到的逻辑,不管是用于Theme还是StyleSheetTheme。可以设想如下的情况:在公司内部网上有1000个页面,您负责维护这些页面。现在假设内部网的开发被分配给不同办公室的几名(或许多)开发人员,这些办公室位于不同城市、不同州甚至不同的国家。每名开发人员创建的每个页面都必须包含这种逻辑。下面设想代码需要区分浏览器的特定版本(例如,区分是否是IE已经不再充分,现在需要区分IE 6和IE 7)。那么会出现什么情况?您不得不到每个页面上添加新标准。需要联系所有分散的开发人员,让其更新页面以包含这种新逻辑:事实证明,对于最小的项目而言,这项工作已经很困难,而对于由分散的开发人员维护的分布式企业系统而言,则完全不可能完成这项工作。这种方法有一些优点,但还不如在@Page指令中设置主题。对于小型站点而言,该方法可行。但对于较大的站点而言,则需要更好的方法。如果继续阅读本章,就会找到一些方法,它们有助于避免这种方法的缺点。
9.3.4 不同主题的优先级
如果在每个页面上的@Page指令中设置Theme(或者StyleSheetTheme),但是在某些页面上,通过引入Page_PreInit事件中的逻辑来引入将主题应用于页面的逻辑,谁会胜出?在@Page指令中声明主题会胜出吗?还是Page_PreInit事件中的程序化代码会胜出?
在本示例中,Page_PreInit胜出。
为了说明这一点,可以创建第二个主题mySecondTheme,该主题中有一个样式表,其中有不同的规则(因此知道谁会胜出)。例如,将下面的规则添加到mySecondTheme子目录中的样式表文档内。
body
{
background-color: Silver;
color: SteelBlue;
font-size: xx-large;
}
在@Page指令中将Theme属性设置为myFirstTheme,然后在Page_PreInit事件中添加代码,将主题设置为mySecondTheme(例如,Page.Theme = "mySecondTheme";)。浏览项目,就会看到新主题胜出。该规则也可应用于StyleSheetThemes (Page_PreInit重写@Page指令中的设置)。