主题和母版页(层叠样式表、主题)
整合网站的第一步是采用一组一致的可视化样式。CSS 为格式化 Web 页面提供了跨平台的解决方案,它可以和 HTML 或则 XHTML 一起工作并几乎被所有的现代浏览器支持。
创建 CSS 层叠样式表
使用 CSS 时,可以利用样式表定义一组预定义格式,然后通过 CssClass 属性把这些样式表链接到适当的控件。样式表由规则组成,每个规则定义了 Web 页面中的一个元素应该如何被格式化。
每个规则的名字包含两部分:
- 点号前面表明规则要作用的 HTML 元素,点号前面什么都没有表示这个规则可作用于所有的 HTML 元素。
- 点号后面的部分是一个唯一的名称,叫做 CSS 类名,类名区分大小写。
.heading1
{
font-weight:bold;
font-size:large;
color:Lime;
font-family:Verdana, Arial, Sans-Serif;
}
还可以创建自动应用到 HTML 标签的规则,只要把标签名作为规则名即可:
h2
{
color: Red;
}
这种自动的样式表看起来很有用,但它在 ASP.NET 里却不是那么方便。通常你直接处理的是控件而不是独立的 HTML 标签。有时候你总是不能确定将会用什么标签呈现某个控件,因此,最好通过类名显示指定规则。
如果你不喜欢手工书写 CSS 规则,VS 允许使用设计器来构建。只要添加一个规则的声明,在花括号中点击鼠标右键,选择创建样式即可。
一个典型的样式表会定义大量的规则,样式表常用于正式定义网站用户界面主要的格式,参考下面的样式表:
body
{
font-family: Verdana, Arial, Sans-Serif;
font-size: small;
}
.heading1
{
font-weight: bold;
font-size: large;
color: Lime;
}
.heading2
{
font-weight: bold;
font-size: medium;
font-style: italic;
color: #C0BA72;
}
.blockText
{
padding: 10px;
background-color: #FFFFD9;
border-style: solid;
border-width: thin;
}
VS 中选择视图,其他窗口,文档大纲,可以清楚的罗列当前样式表中所有的规则,可以迅速定位到某一规则:
应用样式表
要在 Web 页面里使用某个规则,首先页面要链接到适当的样式表,可在 <head> 节中添加如下代码(可在VS中拖动样式表进入head):
<link href="MyStyleSheet.css" rel="stylesheet" type="text/css" />
<div>
<!-- 对于普通控件应用样式,可设置 CssClass 属性.-->
<asp:Label ID="Label1" runat="server" Text="This Label uses the heading1 style."
CssClass="heading1"></asp:Label>
<!-- 对于一段普通的 HTML,要使用 class 特性 -->
<div id="paragraph" runat="server" class="blockText">
<p>
This paragraph uses the blockText style.</p>
</div>
</div>
也可通过“应用样式”窗口来指定具体的规则应用(视图->应用样式),这个设计可以实现一个预览的效果,不过注意以下几点:
- 如果选中的是 Web 控件,它添加或修改 CssClass 属性。
- 如果选中的是普通的 HTML 元素,它添加或修改 Class 特性。
- 如果选中一段 HTML 内容,它添加一个<span>或<div>元素,然后设置它的 Class 特性。
VS 还提供了更多的样式表助手,下面这些特性可以帮助你在平时管理样式:
- “管理样式”窗口:打开一个 Web 页面,可以选择“视图” -> “管理样式”。
- 样式表工具条:它提供修改现有样式以及新增样式的按钮。(右击工具栏并勾选“样式表”复选框)。
- “CSS属性”窗口:这个窗口允许仔细检查样式表并修改它的格式化属性。(“视图” -> “CSS属性”)。
主题
CSS 规则只限于一组固定的样式特性,它们允许重用特定的格式化细节(字体、边框、前景色和背景色等),但它们显然不能控制 ASP.NET 控件的其他方面。例如,CheckBoxList 控件有一些用于控制如何把项组织为行或列的属性。虽然这些属性影响的是控件的可视外观,但它们在 CSS 的范围之外,所以必须手工设置。此外,你可能还希望在定义控件格式的同时定义控件的部分行为。例如,你可能希望标准化 Calender 控件的选择模式或者 TextBox 控件的折行。显然,这些都不可能通过 CSS 实现。
主题弥补了这一缺陷。和 CSS 相似,主图允许定义一组作用于多个页面中控件的样式特性。不过,和 CSS 不同的是,主题不是由浏览器实现的。相反,它们是在服务器上实现的 ASP.NET 自有的解决方案。
主题不会代替样式,但它们可以提供 CSS 不能提供的一些特性。下面是它们的主要区别:
- 主题基于控件而不是 HTML。主题允许定义和重用几乎所有的控件属性。例如,可以在主题中定义一组通用的节点图片并把它们应用于多个 TreeView 控件,还可以为多个 GridView 控件定义一组模版。CSS 只是直接作用于 HTML 的样式特性。
- 主题应用在服务器上。主题作用到页面时,样式化后的最终页面被传送给用户。而使用样式表时,浏览器同时接收到页面和样式信息并在客户端合并它们。
- 主题不会像 CSS 那样层叠。如果在一个主题和另一个控件里同时指定了一个属性,那么主题里定义的值会覆盖控件的属性。不过,有一个方法可以改变这个行为,提高页面属性的优先级,这样主题的行为将更像样式表。
主题文件夹和外观
所有的主题都是与应用程序相关的。要在 Web 应用程序里使用主题,必须创建一个定义它的文件夹,这个文件夹必须放在一个叫做 App_Themes 的文件夹里,而 App_Themes 又必须位于 Web 应用程序的顶层目录中。
只要每个主题都在一个单独的文件夹里,应用程序就可以定义多个主题。对于一个给定的页面,每次只能有一个主题处于活动状态。
要让主题真正有效,必须在主题文件夹里创建至少一个外观文件。外观文件是一个使用 .skin 扩展名的文本文件。ASP.NET 不会直接提供外观文件,相反,它们用于在幕后定义主题。
外观文件从本质上来说是一些控件标签(稍有些变形)。外观文件里的控件标签不必完整定义控件,它们只有一组你希望标准化的属性。例如,如果希望应用一个一致的颜色方案,可能只需设置 ForeColor 和 BackColor 之类的属性。若在外观文件中为 ListBox 控件添加控件标签,代码看起来应该是这样的:
<asp:ListBox runat="server" ForeColor="White" BackColor="Orange" />
runat="server" 是必需的,其他所有部分都是可选的。id 特性不允许在主题里使用,因为它必须唯一区别实际网页中的每个控件。
你可以决定是创建多个外观文件还是把所有的控件标签都放到一个外观文件里。这两种方式等效,因为 ASP.NET 把同一个主题目录里的所有外观文件都看成是同一个主题定义的一部分。通常,把复杂控件(如数据控件)的控件标签单独放在外观文件里比较有意义。
应用简单主题
VS 没有为创建主题提供任何设计时支持,因此从其他 Web 页面复制和粘贴控件标签是个好的选择,现在创建一个外观文件如下:
<asp:TextBox runat="server" ForeColor="White" BackColor="Orange" />
<asp:ListBox runat="server" ForeColor="White" BackColor="Orange" />
<asp:Button runat="server" ForeColor="White" BackColor="Orange" />
让一个页面应用主题,需要在 Page指令内加上 Theme 特性(也可以在设计时从属性窗口中选中“Document”对象设置它的 Theme 属性):
<%@ Page Language="C#" AutoEventWireup="true" ... Theme="FunkyTheme" %>
当某个主题应用到页面后,ASP.NET 会考虑 Web 页面上的每个控件并检查外观文件以便查看它们是否为控件定义了属性,如果发现了匹配的标签,那么从外观文件获得的信息会覆盖控件的当前属性。
处理主题冲突
如果控件的属性和主题定义的属性产生冲突,总是优先使用主题定义的属性。不过,有时候你可能希望改变这一行为,以便使控件可以覆盖某些特定的细节从而优化主题。ASP.NET 允许这么做,不过这个选项要么对整个页面的所有控件起作用,要么都不起作用。
要实现这样的变化,只要再 Page 指令里用 StyleSheetTheme 特性替代 Theme 特性即可,下面是一个示例:
<%@ Page Language="C#" AutoEventWireup="true" ... StylesheetTheme="FunkyTheme" %>
另一个办法是配置特定控件,从而使它完全忽略主题,只需把该控件的 EnableTheming 属性设为 false 。
<asp:TextBox ID="TextBox1" runat="server" Width="130px" EnableTheming="false">Test</asp:TextBox>
为同一控件创建多个主题
把每个控件锁定于某个单一格式有利于标准化,不过对于实际的应用程序而言,这可能不够灵活。例如,根据文本框出现的位置以及它们包含的数据类型,你可能需要几种不同的文本框;而对于标签,当它们用作标题或者正文文字的时候可能更需要差异化。
一般而言,如果为同一个控件创建了不止一个主题,那么 ASP.NET 会给出一个编译错误,提示每个控件只可以有一个默认外观。要解决这个问题,需要指定 SkinID 特性来创建一个被命名的外观。下面是一个示例:
<asp:TextBox runat="server" ForeColor="White" BackColor="Orange" />
<asp:ListBox runat="server" ForeColor="White" BackColor="Orange" />
<asp:Button runat="server" ForeColor="White" BackColor="Orange" />
<asp:TextBox runat="server" ForeColor="White" BackColor="Orange"
Font-Bold="True" SkinID="Dramatic" />
<asp:Button runat="server" ForeColor="White" BackColor="Orange"
Font-Bold="True" SkinID="Dramatic" />
这种方式的缺点是不能像默认外观一样自动被应用。要使用命名外观,必须指定 Web 页面上匹配控件的 SkinID 属性。
<asp:TextBox ID="TextBox1" runat="server" SkinID="Dramatic">Test</asp:TextBox>
如果你不喜欢为主题默认加入的类型,可以把所有的外观都变成命名的,这样,除非设置了控件的 SkinID 属性,否则它们不会被应用。
SkinID 不必保持唯一,它只要对每个控件唯一即可。例如,对于显示大量信息的页面,你可以创建新的 Button、TextBox 以及 Label 控件,并给它们相同的外观名称(例如 Smaller)。
含有模版和图片的外观
到目前为止,你见到的主题示例都只应用了相对简单的属性。其实,你可以在外观文件里创建更加详细的控件标签。大多数属性都支持主题,如果某个属性不能在主题里声明,尝试运行应用程序时会得到一个编译错误。
很多控件支持指定一组格式化信息的样式。数据控件是一个这样的例子,Calender 控件也是。为了匹配主题,你可以在外观文件中如下定义 Calender 样式:
<asp:Calendar runat="server" BackColor="White" BorderColor="Black" BorderStyle="Solid" CellSpacing="1"
Font-Names="Verdana" Font-Size="9pt" ForeColor="Black" Height="250px" Width="500px"
NextPrevFormat="ShortMonth" SelectionMode="Day">
<SelectedDayStyle BackColor="DarkOrange" ForeColor="White" />
<DayStyle BackColor="Orange" Font-Bold="True" ForeColor="White" />
<NextPrevStyle Font-Bold="True" Font-Size="8pt" ForeColor="White" />
<DayHeaderStyle Font-Bold="True" Font-Size="8pt" ForeColor="#333333" Height="8pt" />
<TitleStyle BackColor="Firebrick" BorderStyle="None" Font-Bold="True" Font-Size="12pt"
ForeColor="White" Height="12pt" />
<OtherMonthDayStyle BackColor="NavajoWhite" Font-Bold="False" ForeColor="DarkGray" />
</asp:Calendar>
这个外观定义了 Calender 的字体,颜色和样式,还设置了选择模式、月份导航链接的格式和日历的大小。
外观的另一个强大技能是把图片作为主题的一部分而重用它们。假设你完善了一幅图片,想把它应用于整个网站的“确定”按钮,你还有另一幅图片想要用于所有的“取消”按钮。
- 把图片加到主题文件夹(为了达到最好的组织效果,应该为存放图片创建一个或多个子文件夹)。
- 创建使用这些图片的外观。此时,所有这些标签必须是已命名的外观,因为你定义的标准化按钮类型应该只在需要时才被页面使用。你不是在定义一个作用于所有按钮的默认样式。(在外观文件里添加对图片的引用时,一定要保证图片的 URL 是相对于主题文件夹而不是页面所在的文件夹。主题应用到控件时,ASP.NET 自动在 URL 开始处插入 Themes\ThemeName)
<asp:ImageButton runat="server" SkinID="OKButton" ImageUrl="ButtonImages/buttonOK.jpg" />
<asp:ImageButton runat="server" SkinID="CancelButton" ImageUrl="ButtonImages/buttonCancel.jpg" />
- 现在就可以应用这些图片了,在 Web 页面里创建一个引用相应外观名称的 ImageButton(你可以使用同样的技术为其他使用图片的控件创建外观,例如可以标准化 TreeView 中使用的节点图片、BulletList 控件的列表图片或者 GridView 里使用的图标):
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="ImageInTheme.aspx.cs" Inherits="Chapter16_ImageInTheme" Theme="FunkyTheme" %>
<!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></title>
</head>
<body>
<form id="form1" runat="server">
<div>
<asp:ImageButton ID="ImageButton1" runat="server" SkinID="OKButton" />
<asp:ImageButton ID="ImageButton2" runat="server" SkinID="CancelButton" />
</div>
</form>
</body>
</html>
在主题里使用 CSS
ASP.NET 还允许把样式表用作主题的一部分。你可能会由于以下几个原因使用这一功能:
- 你希望样式化那些不和服务器对应的 HTML 元素
- 你倾向于使用样式表,因为它们更加标准化,或者因为它们同时还可以格式化静态 HTML 页面
- 你已经为创建样式表付出了大量努力,因而不愿意创建主题曲实现同样的格式化
要在主题里使用样式表,必须把样式表放入主题文件夹。ASP.NET 搜索这个文件夹里所有的 .CSS 文件并把它们动态的插入到所有应用此主题的页面。也正因此,<head> 元素必须加上 runat="server" 变为服务器控件,ASP.NET 才能将 <link> 标签插入。
通过配置文件来应用主题
有时,你可能希望把主题应用到整个 Web 应用程序。要达到这一目的,最简捷的办法是在 web.config 文件的 <pages> 元素里为应用程序配置主题,如下所示:
<configuration>
<system.web>
<pages theme="FunkyTheme" />
</system.web>
</configuration>
如果希望使用样式表行为以便主题不会覆写冲突的控件属性,那么可以使用 styleSheetTheme 特性替代 Theme 特性:
<configuration>
<system.web>
<pages styleSheetTheme="FunkyTheme" />
</system.web>
</configuration>
无论哪种方式,配置文件里指定了主题之后,只要页面没有单独设置主题,主题就会应用到网站的所有页面。如果某个页面指定了 Theme 或 StyleSheetTheme 特性,页面设置的优先级将高于 web.config 的设置。
利用这一技术,还可以很方便的把主题应用到 Web 应用程序某些特定部分。例如,可以为每个子文件夹创建一个一个单独的 web.config 文件,并通过 <pages> 设置不同的主题。
如果某个单独的页面不想应用这个全局的主题,也可以在 Page 指令中设置 EnableTheming 为 false 。
动态应用主题
有时候,主题不用于标准化网站外观,而是用于使每个用户可配置外观。这时,Web 应用程序就使用户有机会选择页面要使用的主题了。这项技术其实非常简单。你所要做的只是在代码里动态的设置 Page.Theme 属性或 Page.StyleSheet 属性。唯一需要注意的是,这一步必须在 Page.PreInit 事件阶段完成。以后,尝试设置这些属性会触发异常。
可以把选定的主题放在 Session、cookie、用户配置属性或者其他用户特定的位置。需要使用一些技巧才能创建一个允许用户选择主题的页面。这一问题是因为,在页面加载前不能够读取用户的选择,而此时已过了 PreInit 阶段,所有这个时候再设置主题已经太晚了。解决这一问题的一个办法是重定向到页面自身从而触发一次刷新。完成这一任务的最高效办法是使用 Server.Transfer()方法,这样,所有的处理过程都发生在服务器端。(Response.Redirect()向客户端发送一个重定向首部,所以需要一次往返。)
下面这个示例可以说明上述所讲的技术:
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
DirectoryInfo themeDir = new DirectoryInfo(Server.MapPath("~\\App_Themes"));
lstThemes.DataTextField = "Name";
lstThemes.DataSource = themeDir.GetDirectories();
lstThemes.DataBind();
}
}
protected void Page_PreInit(object sender, EventArgs e)
{
if (Session["Theme"] == null)
{
Page.Theme = "";
}
else
{
Page.Theme = Session["Theme"].ToString();
}
}
protected void cmdApply_Click(object sender, EventArgs e)
{
Session["Theme"] = lstThemes.SelectedValue;
Server.Transfer(Request.FilePath);
}
(使用命名外观时,可以在设计页面时声明性的设置控件的 SkinID,也可以在代码里动态的指定它。)