One of my coworkers, Rob Wilson, did a fantastic interview with me that will be appearing on our new Channel9 show, "Communicating", as soon as I can get it edited, encoded, and uploaded. In exchange, he asked for some Silverlight help. I learned a few hard lessons along the way and thought I would share them (as well as the resources where I found the answers).
Let's start by looking at the final product.
What's cool about this is that the images are round and float seamlessly in front of the center image. I also like how the gel buttons turned out. This article is going to show how to achieve both effects.
First, let's see how we did the gel buttons. I have to completely give credit on this one to Jacob Sanford... he created a really great screencast that demonstrates how to do this, "Creating a Gel (Glass) Button in Silverlight, Part 1". The real trick here is to create 2 rectangles with a LinearGradientBrush that overlap each other. Both rectangles set the RadiusX and RadiusY properties to round their corners. The part that I added to Jacob's demo code is setting the Name property of the containing Canvas as well as the darker color in the button. I also added handlers for MouseEnter, MouseLeave, and MouseLeftButtonDown events.
<!-- Gel button --> <Canvas Height="40" Width="100" Cursor="Hand"
Name="button1" MouseEnter="buttonEnter"
MouseLeave="buttonLeave" MouseLeftButtonDown="buttonDown"> <Rectangle Height="40" Width="100" RadiusX="15" RadiusY="15" Stroke="#FF0011FF" StrokeThickness="2"> <Rectangle.Fill> <LinearGradientBrush StartPoint="0,0" EndPoint="0,1"> <GradientStop Color="#4444FF" Offset="0" Name="color1"/> <GradientStop Color="#6DACFF" Offset="1"/> </LinearGradientBrush> </Rectangle.Fill> </Rectangle> <Rectangle Canvas.Left="4" Width="92" Height="20" Canvas.Top="2"
RadiusX="13" RadiusY="13" Opacity="0.9"> <Rectangle.Fill> <LinearGradientBrush StartPoint="0,0" EndPoint="0,1"> <GradientStop Color="White" Offset="0"/> <GradientStop Color="Transparent" Offset="1"/> </LinearGradientBrush> </Rectangle.Fill> </Rectangle> <TextBlock Text="AT&T" FontFamily="Arial Black"
Canvas.Left="25" Canvas.Top="10" Foreground="White" /> </Canvas>
We want to be able to respond to mouse events with our button in a way that users are familiar with. When you hover over the button, you expect a visual cue that it is a button. We can do that by handling the MouseEnter and MouseLeave events for the canvas. I am using Silverlight 1.0 here, but you could do the same thing with Silverlight 2.0... just handle the mouse event and change the darker color in your button to the lighter color, then change it back when the mouse leaves. When the mouse is pressed, you can do whatever you want (here, I just open up a new browser window that points to AT&T's web site).
function buttonEnter(s) { var idx = s.name.replace("button",""); s.findName("color" + idx).color = "#6DACFF"; } function buttonLeave(s) { var idx = s.name.replace("button",""); s.findName("color" + idx).color = "#4444FF"; } function buttonDown(s) { window.open("http://www.att.com"); }
You can see that the code is pretty simple... you handle the mouse events on the containing canvas, I just use that to locate and change the color in the LinearGradientBrush.
The next part took me awhile to figure out. The samples that I have seen using carousels in Silverlight all used square shapes. I wanted to use round shapes. I talked with my teammate and Silverlight guru, Michael Scherotter, and he suggested trying either a transparent GIF created by using Paint.NET (mad props, I haven't used this before... what an excellent program!), or using an ImageBrush with an elipse. Silverlight threw errors when I tried to use a transparent GIF, so back to the image brush. A JPG can't have a transparent area, so I needed to use Silverlight to only show part of the image. For instance, look at the image against a black background:
Sure, I could just color the image background black, but I might want to use this on a different color canvas.
A quick search on Live.com yielded an article by my friend Dan Wahlin, "Silverlight XAML Primer 3: Working with Image Brushes" that was straightforward enough. Create an elipse that is the same as the target area and set the Stretch property to None.
<Canvas Name="imagesHolder" Width="300" Canvas.Top="0"
Canvas.Left="0"> <!-- Center logo --> <Canvas Name="mainImageHolder" Canvas.ZIndex="-3" Opacity="1.0"
Canvas.Left="130" Canvas.Top="60"> <Ellipse Width="125" Height="125" Canvas.ZIndex="-2"> <Ellipse.Fill> <ImageBrush ImageSource="images/logo.jpg" Stretch="None"/> </Ellipse.Fill> </Ellipse> </Canvas> </Canvas>
Notice that the Z-Index is set to -2. I am doing this because I am going to rotate other images in front of the center logo. Which is a nice segue to the next topic... dynamically adding the images.
I explained dynamically adding canvas elements in my article, "Microsoft Silverlight and Truveo Video Search, Part Two". This bit of code uses the same technique, simply creating the Canvas elements and their containing elements within the JavaScript itself and using the CreateFromXaml API call. You can see that I use the same Ellipse + ImageBrush technique here for rendering the circular images that will rotate around the statically placed center image.
function buildImages() { for(i=0;i<num_imgs;i++) { cur_img=i+".jpg" img_url="images/"+cur_img left_pos=0 xaml_str='<Canvas Name="imageHolder_'+i+'" Canvas.Left="'+
left_pos+'" Canvas.Top="0" MouseLeftButtonDown="buttonDown">' xaml_str += '<Ellipse Width="'+img_width+'" Height="'+
img_height+'" Canvas.ZIndex="20">' xaml_str += ' <Ellipse.Fill>' xaml_str += ' <ImageBrush ImageSource="'+
img_url+'" Stretch="None"></ImageBrush>' xaml_str += ' </Ellipse.Fill>' xaml_str += '</Ellipse>' xaml_str +=' <Canvas.RenderTransform><ScaleTransform
Name="st_'+i+'" ScaleX="1" ScaleY="1" CenterX="50" CenterY="50"/>
</Canvas.RenderTransform>' xaml_str+='</Canvas>' xamlTags=plugin.content.createFromXaml(xaml_str) imagesHolder.children.add(xamlTags) objsArr["image_"+i]=new Object() objsArr["image_"+i].angle=i*((Math.PI*2)/num_imgs) } positionItems() moveItemsInt=setInterval("positionItems()",25) }
The next part to look at is how to get the pictures to rotate around each other. Again, I can't claim much credit here, I downloaded and modified an example from VectorForm's blog, "Image Carousel". First, we'll set up some constants to control things like rotation radius, position on the X and Y axes, speed of rotation, and the number of images to rotate.
var plugin var main var objsArr=new Array() //Constants to control number of images, //screen realestate, radius of the spin, //and speed of spin var img_width=75 var img_height=75 var num_imgs=5 var radiusX=150 var radiusY=75 var centerX=150 var centerY=75 var speed=-0.01
The rest of the code boils down to moving the images left and right according to a sin wave and moving up and down based on a cos wave. It also uses a ScaleTransform to grow and shrink the image according to the current sin and cos calculations. There was also handling in the original source for controlling the spin speed based on clicking on the left or right of the canvas. To simplify for my needs, I wanted it to continually spin at a constant rate. Here's the result.
function positionItems() { for(i=0;i<num_imgs;i++) { var my_x=Math.cos(objsArr["image_"+i].angle)*radiusX + centerX var my_y=Math.sin(objsArr["image_"+i].angle)*radiusY + centerY main.findName("imageHolder_"+i)["Canvas.Left"]=my_x main.findName("imageHolder_"+i)["Canvas.Top"]=my_y var stRef=main.findName("st_"+i) var sc = (my_y - stRef.scaleY) / (centerY+radiusY-stRef.scaleY) stRef.scaleX=sc stRef.scaleY=sc objsArr["image_"+i].angle+=speed main.findName("imageHolder_"+i)["Canvas.ZIndex"]=parseInt(my_y) } }
This function is called from the buildImages() method with a setInterval() call that fires once every 25 milliseconds. To the end user, it looks like the ball is continuously moving in orbit around the center logo.
As you can see, I was simply modifying the work of others to achieve an effect. All the props to the original authors for sharing their knowledge and source.
PingBack from http://www.biosensorab.org/2008/02/22/creating-a-silverlight-carousel-and-gel-buttons-through-live-search/
Apologies to Michael... I just got a clarifying IM message that he never said transparent *GIF*. What he said was to create a transparent area, and said Silverlight supports PNG and JPG. You would create a transparent PNG and possibly relieve yourself of the Ellipse + ImageBrush steps, although I think that was good learning away.