Jonathan 的个人资料Jon's Vista Gadgets Blog日志列表 工具 帮助

日志


10月3日

HOW TO: Skin selection

You want to provide your users with a visual skin selector in your settings, but don't know where to start.  Here's all the code you'll need to do it.  The end result will look something like this:

skinSelection

 

File structure

Firstly, create a file structure to support skins.  You'll be placing all the skins in subfolders under a themes folder.

<Gadget Folder>
    images
    themes
        <skin1 folder>
        <skin2 folder>

This keeps all your skins out of the way, and easy to distinguish from the rest of the Gadget.  Each skin subfolder should be named as you'd like the skin name to appear to the user.  ie Default, Blue, Black etc.  And each folder needs to contain a folder.png file, which should be a screenshot of the Gadget with the skin applied.  This image should ideally be scaled and no larger than 100x100 pixels.

 

Images

You'll need some images for the previous/next buttons, so save the following images in the images folder created above.

left.png: Left leftdown.png: LeftDown lefthover.png: LeftHover
right.png: Right rightdown.png: RightDown righthover.png: RightHover

 

Settings.html

The settings file needs to search the themes subfolder, store the options and select the current skin.

Without going into too much detail, it will create an array of the subfolders under themes and using the gSkin stored setting, select the currently selected skin.  The only entry you need to change is the default skin name, which is the first line of the script.

<html>
 <style>
     * {
      font-family:Segoe UI;
      font-size:12px;
     }

  body{
   width:300px;
   height:250px;
   margin:0px;
  }
 </style>
 <head>
  <script language="javascript" type="text/javascript">
   var defaultSkin = "Default";   // default skin name
   var maxSkins = 256;  // max number of skins allowed

   System.Gadget.onSettingsClosing = settingsClosing;

   var oFSO = new ActiveXObject("Scripting.FileSystemObject");
   var aSkins = new Array(maxSkins);
   var skinCount = 0;        // number of skins found
   var currentSkinNo = 0;    // the current skin selected

   var currentSetting = System.Gadget.Settings.readString("gSkin");
   if (currentSetting == "")
    currentSetting = defaultSkin;

   
   function init() {
       getSkins();
       updateSkin();
   }
   
   function settingsClosing(event) {
       try{
            if (event.closeAction == event.Action.commit) {
               //write settings to INI
               System.Gadget.Settings.write("gSkin", aSkins[currentSkinNo].substring(aSkins[currentSkinNo].lastIndexOf("\\") + 1, aSkins[currentSkinNo].length));
   
               // update the Gadget if the skin has changed
               if (currentSetting != System.Gadget.Settings.readString("gSkin"))
                   updateGadget();
           }
       } catch(err) {System.Debug.outputString("settingsClosing: " + err.name+" - "+err.message)}
   
       event.cancel = false;
   }
   
   function updateSkin() {
       imgSkin.src = aSkins[currentSkinNo] + "\\folder.png";
       divCount.innerText = (currentSkinNo + 1) + "/" + skinCount;
       divName.innerText = aSkins[currentSkinNo].substring(aSkins[currentSkinNo].lastIndexOf("\\") + 1, aSkins[currentSkinNo].length);
   }
   
   // Read skin folders
   function getSkins() {
       var oFolder = oFSO.GetFolder(System.Gadget.path + "\\themes");
       var oSubFolders = new Enumerator(oFolder.SubFolders);
   
       try{
           for (; !oSubFolders.atEnd() && skinCount < maxSkins; oSubFolders.moveNext())
           {
               aSkins[skinCount] = String(oSubFolders.item());
   
               // have we found the current skin
               if(aSkins[skinCount].toLowerCase() == (System.Gadget.path.toLowerCase() + "\\themes\\" + currentSetting).toLowerCase())
                   currentSkinNo = skinCount;
   
               skinCount++;
           }
       } catch(err) {System.Debug.outputString("getSkins: " + err.name + " - " + err.message)}
   }
   
   // Display the next Skin
   function nextSkin() {
       currentSkinNo = (currentSkinNo+1) % skinCount;
       updateSkin();
   }
   
   // Display the previous Skin
   function previousSkin() {
       currentSkinNo = (currentSkinNo-1+skinCount) % skinCount;
       updateSkin();
   }
   
   // Change a IMG source
   function changeIMG(imagesource, imagename) {
       imagesource.src= "images\\" + imagename;
   }
  </script>
 </head>
 <body onload="init()">
  <div style="position:static; width:100%; height:160px;">
   <div style="position:absolute; top:0px; left:0px; width:100%; height:100px; text-align:center;">
    <div style="width:128px; height:100%; background-color:white; border-style: solid; border-width:1px; border-color:rgb(180,180,180); overflow:hidden;">
     <div style="position:absolute; top:50%; left:50%;">
      <img id="imgSkin" src="images/blank.png" style="position:relative; top:-50%; left:-50%"/>
     </div>
    </div>
   </div>
   <div style="position:absolute; width:100%; height:50px; top:110px; left:0px; text-align:center;">
    <div style="position:absolute; top:0px; left:50%;">
     <div style="position:absolute; left:-35px;">
        <img id="imgPreviousSkin" title="Previous" src="images/left.png"
       style="position:relative; left:-50%;"
       onMouseOver="javascript:changeIMG(imgPreviousSkin,'LeftHover.png')"
       onMouseOut="javascript:changeIMG(imgPreviousSkin,'Left.png')"
       onClick="previousSkin()"
      />
     </div>
       <div style="position:absolute; left:35px;">
          <img id="imgNextSkin" title="Next" src="images/right.png"
           style="position:relative; left:-50%;"
       onMouseOver="javascript:changeIMG(imgNextSkin,'RightHover.png')"
       onMouseOut="javascript:changeIMG(imgNextSkin,'Right.png')"
       onClick="nextSkin()"
      />
     </div>
    </div>
    <div style="position:absolute; top:2px; left:50%;">
     <div id="divCount" style="position:relative; left:-50%;"></DIV>
    </div>
    <div style="position:absolute; top:22px; left:50%;">
     <B><DIV id="divName" style="position:relative; left:-50%;"></DIV></B>
    </div>
   </div>
  </div>
   </body>
</html>

 

The only function not coded above is updateGadget(), which needs to apply the skin to the Gadget.  An example would be to reload the Gadget:

function updateGadget() {
    try{
        System.Gadget.document.parentWindow.location.href = System.Gadget.document.parentWindow.location.href;
    } catch(err) {}
}

Or, call the parent function that loads the skin (see Loading the Gadget skin):

function updateGadget() {
    try{
        System.Gadget.document.parentWindow.loadSkin();
    } catch(err) {}
}

 

Loading the Gadget skin

This is the tricky bit and depends very much on how complicated you want your skins to be.  It can also get very complicated if you're supporting docked/undocked states and left/right handed sidebars.  For sanity, I've assumed we have only one Gadget state in the following examples.

Option 1 -  The skin contains graphics only, in which case you just need to update the URL's of your images. eg.

var defaultSkin = "Default";
function loadSkin() {
    // use the default skin if there's no saved setting
    if (System.Gadget.Settings.readString("gSkin") != "")
        var skinPath = "themes/" + System.Gadget.Settings.readString("gSkin") + "/";
    else
        var skinPath = "themes/" + defaultSkin + "/";

    // update the Gadget background and images
    System.Gadget.background = "url(" + skinPath + "background.png)";
    img1.src = skinPath + "img1.png";
    img2.src = skinPath + "img2.png";
    ...
}

 

Option 2 - As well as containing images, the skin also has a stylesheet called skin.css. To see this in action, look at Media Player

var defaultSkin = "Default";
function loadSkin() {
    // use the default skin if there's no saved setting
    if (System.Gadget.Settings.readString("gSkin") != "")
        var skinPath = "themes/" + System.Gadget.Settings.readString("gSkin") + "/";
    else
        var skinPath = "themes/" + defaultSkin + "/";

    // create the stylesheet if it doesn't exist
    var x = document.getElementsByTagName('link');
    if (x.length > 0)
        x[0].href = skinPath + "skin.css";
    else {
        var oLink = document.createElement("link");
        oLink.href = skinPath + "skin.css";
        oLink.rel = "stylesheet";
        oLink.type = "text/css";
        document.body.appendChild(oLink);
    }
    // update the Gadget background and images
    System.Gadget.background = "url(" + skinPath + "background.png)";
    img1.src = skinPath + "img1.png";
    img2.src = skinPath + "img2.png";
    ...
}

Note that when applying a stylesheet, due to bugs in Sidebar, several style settings aren't applied.  Body background images, body width and body height to name but a few.  If you skins change the Gadget size, you'll have to code around this by reading and applying the stylesheet settings yourself.

 

Option 3 - The skin contains code.  To see this in action, look at Spectrum Analyser.

This is ironically the easiest option, as you can leave the skin to initialise the Gadget's graphics etc.  Simply append the following code immediately before the closing </html> statement in your gadget.html file:

<script language="javascript" type="text/javascript">
    try{
        if (System.Gadget.Settings.readString("gSkin") != "")
            var skinPath = "themes/" + System.Gadget.Settings.readString("gSkin") + "/";
        else
            var skinPath = "themes/" + defaultSkin + "/";

        var scriptInclude = "<script src=\"" + skinPath + "skin.js\" language=\"javascript\" type=\"text/javascript\"></script" + ">";
        document.body.appendChild(document.createElement(scriptInclude));
    } catch(err) {System.Debug.outputString("loadSkin: " + err.name + " - " + err.message)}
</script>

Your skins should then use the skinPath variable for all local file references.