Jun 30, 2008

Vista gadget transparent background

  1. Introduction
  2. Single part background
  3. Multi part background
  4. Conclusion

As you may know, i've been working on Vista sidebar's gadgets for a while now, and i had to deal with a lot of specifics problems. Indeed, gadget development is very similar to IE web browser programing, but Microsoft thought it was smart to add some specificities, just as if they had no way to modify the IE7 rendering engine nor the win32 API to fit the gadget needs.
Well, now it is the way it is, the only solution is to deal with the given platform.

If you ever tried to create a partially transparent background gadget, you may have experienced one of following problem:

  • pink effect: the pink outline around images or text over transparent parts
  • black background: a black background coming from nowhere

If you didn't, you're lucky, if you did and didn't find a workaround, i'll try to give a method in this article.

Table of content

To have a single part background, you'll need an empty gadget and a png image representing your gadget background. I assume you already have all this.

First to show your gadget background, you need to define an HTML element to receive the image. This element is a Microsoft specific tag called g:background. You can find the specifications and documentation in this page.

Be carefull, as we use the g:background tag, don't use a doctype and especialy no xhtml doctype !

So, now you page should look like this:

<html>
 <head>
 </head>
 <body style="width:150px;height:200px;" >
  <g:background id="background" align="center" style="z-index:999;position:absolute;top:0;left:0;width:150px;height:200px;" src="./path/to/your/background" />
  <div id="content">
   
  </div>
 </body>
</html>

This should be enough to have it working.
You may have note that it I fixed the background and the body sizes, it is mandatory to avoid any unwanted effect I mentioned in the introduction. Please make sure that those sizes are the same as your background image.

If you added some content, you should have noticed that the result isn't the expected one. That's because we didn't position the content correctly. let's add some rules in the css stylesheet:

#content{
width:100%;
height:100%;
padding:0;
margin:0;
position:absolute;
top:0;
left:0;
}

Well, it is now exactly as we expected, isn't it ?
NO ? Damn, why does the text over the transparent area has a pink border ?
Well, I don't know, and the only way I found to add some text on the transparent area is to use the g:background addTextObject method. Otherwise, just try not to write anything in the transparent area :).

Ok, we now have a fixed size and single part background, but what can I do if I want to resize my gadget ? should I embed a background image per size ? let's go further to play around a bit.

Table of content

I assume that if you reached this paragraph you want extensible transparent background, so let's dive into the subject.
First of all, you'll need a blanc gadget with the same code as previous paragraph.
As the background image isn't the same anymore, let's replace the g:background source with a transparent image of 1px square. You may now have following index html file:

<html>
 <head>
  <script type="text/javascript">//<![CDATA[
   /**
    * @description
    * gadget initialization function
    */
   function init()
   {
    /* your initialization code */
   }
   /**
    * @description
    * changes the background size and images
    *
    * @param {Number} height the new gadget height
    * @param {String} headerUrl the new gadget header image url
    * @param {String} midleSliceUrl the new gadget middle repeat slice 1px height
    * @param {String} footerUrl the new gadget footer image url
    */
   function setBackground( height, headerUrl, midleSliceUrl, footerUrl )
   {
    /* code given in the article will come here */
   }
  //]]></script>
 </head>
 <body onload="init()">
  <g:background id="background" align="center" style="z-index:999;position:absolute;top:0;left:0;width:150px;height:200px;" src="./path/to/transparent.png" />
  <div id="content">
   
  </div>
 </body>
</html>

So, let's see how to fill in those functions.

Table of content

To achieve our goal, we'll separate the background into 3 parts, the top one, a repeating slice and the bottom. An improvement (for speed reason) could be to introduce a 4th image, that is a 10 or 20 pixel height image for the middle slice you will repeat, and for the last part use the 1px height slice. I'll let you do this improvement.

  • An image for the header, i'll call it header.png
  • A 1px height middle slice i'll call middleSlice.png. It will be repeated as many time as needed
  • A footer image, i'll call it footer.png

Table of content

So, everything is ready to code, let's go.
First of all we'll code the resizing part, as we've seen before the body and the background has to be the same size, and most important the same size as the images.

// resize part

// parses the height as integer, just in case
var _nHeight = parseInt(height, 10);
var _body = document.body || document.documentElement;
var _background = document.getElementById('background');

_body.style.height = _nHeight + 'px';
_background.style.height= _nHeight + 'px';

So, now we've got the gadget at the right size, next step is to add the images to fill the background:

// fill the background image

//load the images to get the sizes
var _headerHeight, _sliceHeight, _footerHeight,
    _headerImg = new Image(), _sliceImg = new Image(), _footerImg = new Image();

_headerImg.onerror=function(){System.Debug.outputString('header not loaded')};
_sliceImg.onerror=function(){System.Debug.outputString('slice not loaded')};
_footerImg.onerror=function(){System.Debug.outputString('footer not loaded')};

_headerImg.src= headerUrl;
_sliceImg.src= midleSliceUrl;
_footerImg.src= footerUrl;

// set the sizes
_headerHeight = _headerImg.height;
_sliceHeight = _sliceImg.height;
_footerHeight = _footerImg.height;

var _top = 0;

// add the header
_background.addImageObject(headerUrl, 0, _top);
_top+= _headerHeight;

//calculate the middle part height
var _middlepartHeight = _nHeight - _headerHeight - _footerHeight;

// and append the slice as many time as needed
for(var _i=0; _i<_middlepartHeight; _i+=_sliceHeight)
{
   _background.addImageObject(headerUrl, 0, _top);
   _top+= _sliceHeight;
}

// now add the footer
_background.addImageObject(footerUrl, 0, _top);

// That's all folk !

Here we are, we've got our resize function ! let's do the call into the init function:

 setBackground( 300, './path/to/header.png', './path/to/middleSlice.png', './path/to/footer.png')

Table of content

As usual i'll give some documentation link for fast find:

And also an hint, add a bigger slice (for instance 30px) to avoid too much iteration, and use the 1px slice to fill the difference. have fun !

Table of content

5 comments:

Anonymous said...

Thanks a lot for the solution!

But there is a typo in your code:

_sliceImg.src= footerUrl;
_footerImg.src= midleSliceUrl;

must be

_sliceImg.src= midleSliceUrl;
_footerImg.src= footerUrl;

MoUeTtE said...

thanks for feedback, I corrected that :)

Anonymous said...

Interesting story as for me. I'd like to read a bit more concerning this topic. The only thing it would also be great to see here is a few pictures of any gadgets.
Jeff Karver
Cell phone jammers

gadget said...

I've got an interesting problem with the currency sidebar gadget. For the past 2 months now it hasn't updated the exchange rates. Its stuck on the data of roughly half way through August. I'm particularly interested in the US-Australian dollar exchange rate. Right now it should be showing roughly 1.10AUD for 1USD, but its showing 1.25. The strange thing is its only affected my user account, as I've seen on another account that it has the up to date exchange rate.

Ovidiu said...

Hi.

Great post.

Thanks!

Signature - https://mirapic.com/