动态加载皮肤

ASP.NET 2.0 Website Programming: Problem - Design - Solution
Chapter 2: Developing the Site Design-Solution中介绍的关于动态创建主题部分的原文:

Creating the ThemeSelector User Control

You now have a master page, with a couple of themes for it, so now you can develop a user control that will display the list of available themes and allow the user to pick one. Once you have this control, you will plug it into the master page, in the "themeselector" DIV container. Before creating the user control, create a new folder named "Controls", inside of which you'll put all your user controls so that they are separate from pages, for better organization (select the project, right-click Add Folder ð Regular folder, and name it Controls). To create a new user control, right-click on the Controls folder, select Add New Item ð Web User Control, and name it ThemeSelector.ascx. The content of this .ascx file is very simple and includes just a string and a DropDownList:

<%@ Control Language="C#" AutoEventWireup="true" CodeFile="ThemeSelector.ascx.cs"
Inherits="ThemeSelector" %>
<b>Theme:</b>
<asp:DropDownList runat="server" ID="ddlThemes" AutoPostBack="true" />

Note that the drop-down list has the AutoPostBack property set to true, so that the page is automatically submitted to the server as soon as the user changes the selected value. The real work of filling the drop-down list with the names of the available themes, and loading the selected theme, will be done in this control's code-beside file, and in a base page class that you'll see shortly. In the code-beside file, you need to fill the drop-down list with an array of strings returned by a helper method, and then select the item that has the same value of the current page Theme:

public partial class ThemeSelector : System.Web.UI.UserControl
{
   protected void Page_Load(object sender, EventArgs e)
   {
      ddlThemes.DataSource = Helpers.GetThemes();
      ddlThemes.DataBind();
      ddlThemes.SelectedValue = this.Page.Theme;
   }
}

The GetThemes method is defined in a Helpers.cs file that is located under another special folder named App_Code. Files in this folder are automatically compiled at runtime by the ASP.NET engine, so you don't need to compile them before running the project. You can even modify the C# source code files while the application is running, hit refresh, and the new request will recompile the modified file in a new temporary assembly, and load it. You'll read more about the new compilation model later in the book, and especially in Chapter 12 about deployment.
(App_Code文件夹中的文件会在运行的时候自动编译,所以你不用在运行程序之前编译她们)
(你设置可以在程序  运行的时候来修改里面的代码.当有刷新或是新的请求的时候,会重新代码,然后加载它

The GetThemes method uses the GetDirectories method of the System.IO.Directory class to retrieve an array with the paths of all folders contained in the ~/App_Themes folder (this method expects a physical path and not a URL — you can, however, get the physical path pointed to by a URL through the Server.MapPath method). The returned array of strings contains the entire path, not just the folder name, so you must loop through this array and overwrite each item with that item's folder name part (returned by the System.IO.Path.GetFileName static method). Once the array is filled for the first time it is stored in the ASP.NET cache, so that subsequent requests will retrieve it from there, more quickly. The following code shows the entire content of the Helpers class (App_Code\Helpers.cs):

(GetDirectores 方法返回包括~/App_Thems在内的所用文件夹的一个array(排列)---这个方法得到的是一个物理的路径,而不是一个URL,但是你能通过Server.MapPath这个方法将物理地址转换为URL)

这个返回的排列(array)包括完整的路径,而不是只用文件夹名,所以你必须绕过这个array,然后用他们的文件夹名重写他们(用运System.IO.Path.GetFileName方法)..array 第一次被填充的时候,他被储藏在ASP.NETcache,so后来的请求会从cache中重新得到它,还更快哦..呵呵.

using System.IO;
using System.Web.Caching;

namespace MB.TheBeerHouse.UI
{
   public static class Helpers
   {
      /// <summary>
      /// Returns an array with the names of all local Themes
      /// </summary>
      public static string[] GetThemes()
      {
         if (HttpContext.Current.Cache["SiteThemes"] != null)
         {
            return (string[])HttpContext.Current.Cache["SiteThemes"];
         }
         else
         {
            string themesDirPath =
               HttpContext.Current.Server.MapPath("~/App_Themes");
            // get the array of themes folders under /app_themes
            string[] themes = Directory.GetDirectories(themesDirPath);
            for (int i = 0; i <= themes.Length - 1; i++)
            themes[i] = Path.GetFileName(themes[i]);
            // cache the array with a dependency to the folder
            CacheDependency dep = new CacheDependency(themesDirPath);
            HttpContext.Current.Cache.Insert("SiteThemes", themes, dep);
            return themes;
         }
      }
   }
}

Now that you have the control, go back to the master page and add the following line at the top of the file in the Source view to reference the external user control:

<%@ Register Src="Controls/ThemeSelector.ascx" TagName="ThemeSelector"
TagPrefix="mb" %>

Then declare an instance of the control where you want it to appear — namely, within the "themeselector" container:

<div id="themeselector">
   <mb:ThemeSelector id="ThemeSelector1" runat="server" />
</div>

The code that handles the switch to a new theme can't be placed in the DropDownList's SelectedIndexChanged event, because that happens too late in the page's life cycle. As I said in the "Design" section, the new theme must be applied in the page's PreInit event. Also, instead of recoding it for every page, we'll just write that code once in a custom base page. Our objective is to read the value of the DropDownList's selected index from within our custom base class, and then we want to apply the theme specified by the DropDownList. However, you can't access the controls and their values from the PreInit event handler because it's still too early in the page's life cycle. Therefore, you need to read the value of this control in a server event that occurs later: The Load event is a good place to read it.

However, when you're in the Load event handler you won't know the specific ID of the DropDownList control, so you'll need a way to identify this control, and then you can read its value by accessing the row data that was posted back to the server, via the Request.Form collection. But there is still a remaining problem: You must know the ID of the control to retrieve its value from the collection, but the ID may vary according to the container in which you place it, and it's not a good idea to hard-code it because you might decide to change its location in the future. Instead, when the control is first created, you can save its client-side ID in a static field of a class, so that it will be maintained for the entire life of the application, between different requests (post backs), until the application shuts down (more precisely, until the application domain of the application's assemblies is unloaded). Therefore, add a Globals.cs file to the App_Code folder, and write the following code inside it:

namespace MB.TheBeerHouse
{
   public static class Globals
   {
      public static string ThemesSelectorID = "";
   }
}

Then, go back to the ThemeSelector's code-beside file and add the code to save its ID in that static field:

public partial class ThemeSelector : System.Web.UI.UserControl
{
protected void Page_Load(object sender, EventArgs e)
{
      if (Globals.ThemesSelectorID.Length == 0)
         Globals.ThemesSelectorID = ddlThemes.UniqueID;
ddlThemes.DataSource = Helpers.GetThemes();
ddlThemes.DataBind();
ddlThemes.SelectedValue = this.Page.Theme;
}
}

You're ready to create the custom base class for your pages, and this will just be another regular class you place under App_Code, and which inherits from System.Web.UI.Page. You override its OnPreInit method to do the following:

  1. Check whether the current request is a postback. If it is, check whether it was caused by the ThemeSelector drop-down list. As in ASP.NET 1.x, all pages with a server-side form have a hidden field named "__EVENTTARGET", which will be set with the ID of the HTML control that causes the postback (if it is not a Submit button). To verify this condition, you can just check whether the "__EVENTTARGET" element of the Form collection contains the ID of the drop-down list, based on the ID read from the Globals class.

  2. If the conditions of point 1 are all verified, you retrieve the name of the selected theme from the Form collection's element with an Id equal to the ID saved in Globals, and use it for setting the page's Theme property. Then, you also store that value in a Session variable. This is done so that subsequent requests made by the same user will correctly load the newly selected theme, and will not reset it to the default theme.

  3. If the current request is not a postback, check whether the Session variable used in point 2 is empty (null) or not. If it is not, retrieve that value and use it for the page's Theme property.

The following snippet translates this description to real code:

namespace MB.TheBeerHouse.UI
{
   public class BasePage : System.Web.UI.Page
   {
     protected override void OnPreInit(EventArgs e)
     {
        string id = Globals.ThemesSelectorID;
        if (id.Length > 0)
        {
           // if this is a postback caused by the theme selector's dropdownlist,
           // retrieve the selected theme and use it for the current page request
           if (this.Request.Form["__EVENTTARGET"] == id &&
              !string.IsNullOrEmpty(this.Request.Form[id]))
           {
              this.Theme = this.Request.Form[id];
              this.Session["CurrentTheme"] = this.Theme;
           }
           else
           {
              // if not a postback, or a postback caused by controls other then
              // the theme selector, set the page's theme with the value found
              // in Session, if present
              if (this.Session["CurrentTheme"] != null)
                 this.Theme = this.Session["CurrentTheme"].ToString();
           }
        }
        base.OnPreInit(e);
     }
   }
}
Note 

The downside of the approach used here is that the selected theme is stored in a session variable, which is cleared when the session ends — namely, when the user closes the browser, or when the user does not make a page request for 20 minutes (the duration can be customized). A much better solution would be to use Profile properties, which among other advantages are also persistent between sessions. You'll examine this new feature of ASP.NET 2.0 — and modify this code to use it — in Chapter 4.

The last thing you have to do is change the default code-beside class for the Default.aspx page so that it uses your own BasePage class instead of the default Page class. Your custom base class, in turn, will call the original Page class. You only need to change one word, as shown below (change Page to BasePage):

public partial class _Default : MB.TheBeerHouse.UI.BasePage
{
   protected void Page_Load(object sender, EventArgs e)
   { }
}

You're done! If you now run the project, by default you'll see the home page shown in Figure 2-4 (except for the login box, which doesn't contain anything so far — it will be filled in Chapter 5), with the TemplateMonster theme applied to it. If you pick the PlainHtmlYellow item from the ThemeSelector drop-down list, the home page should change to something very similar to what is shown in Figure 2-6.

Image from book

参看:Asp.NET2.0主题、皮肤的应用 其中的一个列子可以对理解上面的话有帮助:

二. 动态加载皮肤

这也是Asp.NET2.0极大提升页面效果的画龙点睛之处:

我们在App_Themes中配置置了ThemeA皮肤和ThemeB皮肤,我们就可以动态修改皮肤了:

ThemeA里的Label.skin:

<asp:label runat="server" 
    font-bold
="true" 
    forecolor
="orange" />

ThemeB里的Label.skin:

<asp:label runat="server" 
    font-bold
="true" 
    forecolor
="blue" />

CodeTheme.aspx:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="CodeTheme.aspx.cs" Inherits="Default4"%>

<!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>CodeTheme</title>
</head>
<body>
    
<form id="form1" runat="server">
    
<div>
        
<href="CodeTheme.aspx?Theme=ThemeA">Theme A</a> 
        
<href="CodeTheme.aspx?Theme=ThemeB">Theme B</a>
        
<asp:Label ID="Label1" runat="server" Text="Label"></asp:Label>
    
</div>
    
</form>
</body>
</html>

CodeTheme.aspx.cs:

using System;
using System.Data;
using System.Configuration;
using System.Collections;
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 Default4 : System.Web.UI.Page
{
    
protected void Page_PreInit()
    {
        Page.Theme 
= Request.QueryString["Theme"];
    }

    
protected void Page_Load(object sender, EventArgs e)
    {

    }
}
从上面的cs代码中我们可以看出,主题应用应该在Page_PreInit()事件中。
posted @ 2007-04-26 16:43  sliuqin  阅读(477)  评论(0编辑  收藏  举报