Contents
OK so we've all got the hang of WPF right, so now we have another beast to slay, it's Silverlight. Silverlight is an internet browser plugin that rivals Adobe Flash for capabilities, and uses a cut down version of Windows Presentation Foundation (WPF) which used to be known as WPF/e (WPF everywhere).
Silverlight currently comes in two flavours, Silverlight 1.0 which is JavaScript based, and the slightly newer 1.1 Alpha which can use managed .NET code. I personally am not that fond of JavaScript so I have been putting the Silverlight mission off until version 1.1.
So now that the 1.1 alpha is out, I thought it high time that I got down and started writing a Silverlight article. I have to say I am fairly pleased with this article, and I think it covers most aspects of what Silverlight can currently do. Of course this may change over time, but let's just assume that things are the way they are right now.
Before we get started, there is one thing that you will really need in order to run the demo project, and that's Visual Studio 2008 Beta 2, which is available here, I went for the Professional edition, and it works a treat. Downloaded, installed and worked first time, unlike all other VS2008 versions I went for. Professional edition works, use that.
That the reader is .NET proficient and at least an average level web user, and is fairly familiar with ASP.NET and Visual Studio.
One of the first things that you will notice when you start VS2008, is that there is a new project type available, yep you guessed it - a Silverlight project.
Remember that Silverlight applications are web based, so the best place to start is probably with a ASP.NET web project. If we then proceed to add a Silverlight project to our existing Web project, we end up with two projects as shown below. In this example (the demo app) the projects are Web: ASP.NET web site, and SilverlightProject1 which is the Silverlight project. One thing to notice in the screen shot below is that the Web project can be configured to run on a certain port within Visual Studio. This setting tells Cassini (the inbuilt VS web browser) to use a static or dynamic port, there will be more mentioned about this later.
When we add a new Silverlight project we end up with some boiler plate files, much the same way one does when creating a new project using one of the other Visual Studio templates. Consider the figure below which is a brand new Silverlight project.
The Silverlight DLLs are installed to the Program Files\Silverlight location.
There are three files for a new Silverlight project:
- Page.xaml /Page.Xaml.cs (or vb if you are using VB.NET)
- TestPage.html which holds the Silverlight object
- Silverlight.js which is the JavaScript to create the Silverlight object within the page (TestPage.html in this case)
I'm just going to show you a bit about each of these three files, so you get the demo app file structure.
1. Page.xaml
Is made up of two files, the XAML and the code behind. Let's start with the XAML.
As can be seen from the figure above, the Page is simply a Canvas
, in fact most controls/page areas in Silverlight 1.1 start out as a Canvas
as there are few actual controls in Silverlight, but we'll talk about that a bit later as well.
The code behind for page is simply enough:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace SilverlightProject1
{
public partial class Page : Canvas
{
public void Page_Loaded(object o, EventArgs e)
{
InitializeComponent();
}
}
}
One thing you won't find is the InitializeComponent()
method, that's in the automatically created Page.g.cs.
2. TestPage.html
Testpage.html is the page that holds the Silverlight object.
<!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>
<title>Silverlight Project Test Page </title>
<script type="text/javascript" src="Silverlight.js"></script>
<script type="text/javascript" src="TestPage.html.js"></script>
<style type="text/css">
.silverlightHost { width: 640px; height: 480px; }
</style>
</head>
<body>
<div id="SilverlightControlHost" class="silverlightHost" >
<script type="text/javascript">
createSilverlight();
</script>
</div>
</body>
</html>
This also has a JavaScript code behind file, which is called within the createSilverlight()
function call. Let's see that JavaScript, shall we (Testpage.html.js).
function createSilverlight()
{
Silverlight.createObjectEx({
source: "Page.xaml",
parentElement: document.
getElementById("SilverlightControlHost"),
id: "SilverlightControl",
properties: {
width: "100%",
height: "100%",
version: "1.1",
enableHtmlAccess: "true"
},
events: {}
});
document.body.onload = function() {
var silverlightControl = document.
getElementById('SilverlightControl');
if (silverlightControl)
silverlightControl.focus();
}
}
3. Silverlight.js
Silverlight.js is a rather large helper script that is MSFT authored and is used to perform additional functions in order to setup the Silverlight object within the host browser.
When I first started looking at Silverlight (about one week now) I was very disappointed that there were no input controls, such as Buttons, TextBoxes, Tabs, ScrollViewers etc. I mean jeez no input controls, come on now.
You have to create these sort of things yourself, which is a bit time consuming but doable. There are some free controls available as mentioned in the resources section at the bottom of this article. One bit of good news is that the Silverlight 1.1 Alpha Refresh does include some basic controls. Have a look there before you start.
When dealing with Silverlight one must remember it is still Alpha, so things will change and get included. I have to say I started out quite negative (cos I like Flash) but by the end I loved Silverlight.
If we look at the Silverlight 1.1 Tutorials and Code Starts one can see that the following appears to be a list of what we can reasonably expect a Silverlight 1.1 application to be able to do (at the moment):
- Getting Started with Silverlight Development
- Building Dynamic User Interfaces with Silverlight
- Networking and Communication in Silverlight
- Sending and Receiving Plain XML Messages
- Using a Proxy to Call an ASP.NET Web Service
- Interaction Between HTML and Managed Code
- Accessing the DOM from Managed Code
- Additional Programming Tasks
The attached demo app will demonstrate and discuss each of these areas.
So what does the demo app look like, well it looks like this:
What Does it Do / What are the Requirements
Ok so that's a screen shot of what it looks like. But what does it do, what were my requirements?
When I started (one week ago) I decided that I wanted to write a Silverlight application that would utilise most of the current Silverlight functions available. So I gave it a thought, and I came up with this: I wanted an application that would use a Web service that would query Flickr for images using a RSS feed, and would show these images in a scrollable panel from where the user may select an image to be viewed in the main page area. When an image was in the main area, I wanted it to be able to be dragged/resized and rotated (like MSFT surface) and removed from the main page area. The user may also get the next/previous page of results from Flickr, so it would have some sort of paging mechanism. I also wanted it to look cool.
This application has a lot of scope, it does indeed cover a lot of Silverlight functionality, and by the end of this article, I hope that you lot will get what you can do with Silverlight right now.
Here is another diagram for the finished demo app with some callouts showing which of the items shown are created by which classes in the attached application.
As I stated earlier, this demo app covers a lot of ground, it covers the following areas:
- Using Web Services
- Using XML (XLINQ cos I simply love it, and after all, this is a VS2008 article, so hey why not)
- Using third party domains (Flickr in my case)
- Using custom controls
- Events
- Using XAML/WPFe
- Using the DOM from Silverlight
This image may help a little to see the dependencies and how all the classes and even projects relate to each other
It can be seen that there are two projects in the attached demo application, the Web project and the Silverlight project. The Silverlight project consists of the Silverlight page object and the hosting HTML page and setup JavaScript, along with code for all the custom controls that have been authored in order to carry out the demo application functionality.
The Web project has the compiled Silverlight project's DLL, and a copy of the Silverlight page object and the hosting HTML page and setup JavaScript. But the web project also has other classes, which are used by the Silverlight project. The rule of thumb with Silverlight 1.1 is that it can ONLY use localhost (127.0.0.1) resources, so that's why there are some extra items in the web project, basically they are used by the Silverlight project in one way or another. Don't worry, these will be described in detail in the following sections.
I'll briefly describe some of the extra files:
- PhotoInfo.cs: A small class that represents a Photo
- RSSFeed.cs: A RSS Feed parser for a Flickr RSS feed
- WebService.asmx / WebService.cs: A web service that is used by the Silverlight Page.xaml.cs object
- DisplayImage.ashx: A simple ASP.NET handler that allows remote (cross domain, Flickr) images to be fetched by Silverlight
Getting Started with Silverlight Development
This was already discussed above.
Networking and Communication in Silverlight
There are several different options for getting data into Silverlight, you can use either of the following approaches:
I'll talk about each of these a bit and then I'll show you what the demo application actually does to get its data.
XML Files
As I don't do this in my demo application (attached), I'm going to simply use the quickstart code from here to illustrate how to read XML data in your Silverlight applications.
The relevant MSFT quick start is located here. What they do is use a local XML file called rssfeed.xml, which they then parse and use within a Silverlight application. The format of the rssfeed.xml file is as shown below:
="1.0" ="ISO-8859-1"
='text/xsl' ='RssPretty.xslt' ='1.0'
<rss version="2.0">
<link rel="alternate" type="application/rss+xml" title="RSS FEED TITLE"
href="RSS FEED URL" />
<channel>
<title>Microsoft Windows Vista</title>
<link>http://www.microsoft.com/windowsvista/</link>
<description>Get new content and articles about Windows
Vista directly from Microsoft.</description>
<copyright>?2006 Microsoft Corporation. All rights reserved.
</copyright>
<language>en-us</language>
<ttl>1440</ttl>
<item>
<title>Make Beautiful Music with Windows Vista�at Home
or On the Go</title>
<pubDate>Wed, 24 Jan 2007 11:00:00 GMT</pubDate>
<description>Columnist S.E. Slack explains how to use
Windows Media Center in Windows Vista to experience
digital music on your PC and portable device.
</description>
<link>http://www.microsoft.com/windowsvista/community/
beautifulmusic.mspx</link>
</item>
....
....
</channel>
</rss>
And then they parse this XML file within their Silverlight application as follows:
using System;
using System.Linq;
using System.Xml;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.IO;
using System.Net;
using System.Text;
using System.Windows.Browser;
using System.Windows.Browser.Net;
namespace BasicRss
{
public partial class Page : Canvas
{
public void Page_Loaded(object o, EventArgs e)
{
InitializeComponent();
string url = GetXmlRssUrl();
try
{
HttpWebRequest request = new
BrowserHttpWebRequest(new Uri(url));
HttpWebResponse response = request.GetResponse();
Stream content = response.GetResponseStream();
StringBuilder xamlString = new StringBuilder();
using (XmlWriter writer = XmlWriter.Create(xamlString))
using (XmlReader reader = XmlReader.Create(new
StreamReader(content)))
{
writer.WriteStartElement("Canvas");
int canvasTop = 40;
while (reader.Read())
{
if ((reader.IsStartElement()) &&
("item" == reader.LocalName))
{
using (XmlReader itemReader =
reader.ReadSubtree())
{
while (itemReader.Read())
{
if (itemReader.IsStartElement())
{
if ("title" ==
itemReader.LocalName)
{
writer.WriteStartElement
("TextBlock");
writer.WriteAttributeString
("Canvas.Top",
canvasTop.ToString());
writer.WriteAttributeString
("Foreground", "Blue");
canvasTop = canvasTop + 20;
writer.WriteAttributeString
("Text",
"Title: " +
itemReader.
ReadElementContentAsString());
writer.WriteEndElement();
}
else if ("link" ==
itemReader.LocalName)
{
writer.WriteStartElement
("TextBlock");
writer.WriteAttributeString
("Canvas.Top",
canvasTop.ToString());
writer.WriteAttributeString
("Foreground", "Blue");
canvasTop = canvasTop + 40;
writer.WriteAttributeString
("Text",
"Link: " +
itemReader.
ReadElementContentAsString());
writer.WriteEndElement();
}
}
}
}
}
}
writer.WriteEndElement();
writer.Flush();
}
response.Close();
System.Windows.DependencyObject controls =
XamlReader.Load(xamlString.ToString());
this.Children.Add(controls as Visual);
}
catch (Exception ex)
{
TextBlock error = (TextBlock)this.FindName("error");
error.Text = " Error: " + ex.Message;
}
}
private string GetXmlRssUrl()
{
string path = HtmlPage.DocumentUri.AbsolutePath;
int lastSlash = path.LastIndexOf("/");
path = path.Substring(0, lastSlash + 1);
return "http://" + HtmlPage.DocumentUri.Host + path +
"rssfeed.xml";
}
}
}
Whilst this certainly does the job just fine, a better approach may be to use a Web service to return a nice collection of some .NET objects that the Silverlight app could use, without having to do all this parsing. Enter Web Services / Silverlight communications.
Web Services and Silverlight
Silverlight can actually call an ASP.NET web service no problem. It's simply a question of creating a web reference in the Silverlight project. One thing that is important to note, is that cross domain calls (not localhost) aren't currently allowed using certain Silverlight objects, as I'll discuss later on when we get to the Downloader
object explanation. And if like me, you don't have IIS installed and rely on Cassini (the Visual Studio web browser) you may find that it allocates a dynamic port each time it runs. You may think this is fine, as it's all on the same domain, but it's not. A quick check on Google assured me that the port is actually included as part of the checks that is made when checking for cross domain access. So that's a bit annoying. Luckily there's an easy fix, either host your Silverlight on IIS proper using fixed port (usually 80 I believe), or use Cassini and configure it to use a static port. The latter is what I have done for the demo application.
I'm going to go through the steps to get an ASP.NET web service up and running with Silverlight in VS2008.
If you want a good link to get you started with Silverlight and ASP.NET web services in VS2008, check out this excellent entry, by MSFTs Mike Taulty. This is what got me up and running. I've copied his post here for folk that just want to read everything in one article.
Steps as below:
- File->New->Web Site. I've been working with Cassini so I just put the website into the file system
- Delete default.aspx as we're building a service here
- Add a new web service (WebService.asmx)
- Implement your service (e.g.)
[WebService(Namespace = "http://tempuri.org/")]
[ScriptService]
public class WebService : System.Web.Services.WebService {
public WebService ()
{
}
[WebMethod]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public string HelloWorld() {
return "Hello World";
}
Quite an important part to note is that the web methods should be marked with the [ScriptMethod(ResponseFormat = ResponseFormat.Json)]
attribute. I believe this is the only format that Silverlight 1.1 works with, so that's why it is needed.
Now we have a service, let's build something from Silverlight to call it:
- File->Add->New Project->Silverlight Project
- Now, add a web reference to the web service from the Silverlight project. Note: If you're using Cassini then I'd suggest setting it up to run on a fixed port at this point by changing your web site properties otherwise your web reference will break from time to time. Property sheet below shows dynamic ports is set to false and port number is set to 65534.
- Add a web reference from the Silverlight Project to the web service (for me this is to http://localhost:65534/WebSite2/WebService.asmx?wsdl) - you'll perhaps have to spin up your web service project first in order to get Cassini sitting and listening for traffic (i.e. setting your ASMX file as the start page and pressing CTRL F5 should do that).
- Write some code into your Silverlight app (e.g.):
<Canvas x:Name="parentCanvas"
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Loaded="Page_Loaded"
x:Class="SilverlightProject1.Page;assembly=
ClientBin/SilverlightProject1.dll"
Width="640"
Height="480"
Background="White"
>
<TextBlock x:Name="myText" Width="192" Height="48" FontSize="24"/>
</Canvas>
public partial class Page : Canvas
{
public void Page_Loaded(object o, EventArgs e)
{
// Required to initialize variables
InitializeComponent();
WebService service = new WebService();
myText.Text = service.HelloWorld();
}
}
Now, when it comes to debugging it seems that running a Silverlight project just loads the page from the file system and I'm not sure whether that's going to work when your page is calling a service that comes from elsewhere, so what I've been doing is:
- Add a link from the Web Site project to the Silverlight Project using the "Add Silverlight Link" menu option off the right mouse menu when you right click on your web project. This will get your assembly, PDB and XAML files copied to the web site and kept in sync (be careful not to edit the XAML file in the wrong place after doing this - it's bitten me more than once and if you edit the one in the web folder then it'll get overwritten on each build).
With that in place:
- Copy TestPage.html, TestPage.html.js, Silverlight.js from your Silverlight project to your web site project.
- Set TestPage.html (in the web site project) as your start page.
- Hit F5 (take care to note that your Web Site project is running and the file is coming from http://localhost and not the file system which it will do if you run the Silverlight project).
You should be in business. If you want to make your call to the web service survive a redeployment, then do something like:
public void Page_Loaded(object o, EventArgs e)
{
InitializeComponent();
WebService service = new WebService();
service.Url = (new Uri(HtmlPage.DocumentUri,
"WebService.asmx")).ToString();
myText.Text = service.HelloWorld();
}
The Demo App Implementation
Ok so I've now shown you that you can use XML and Web Services from within your Silverlight app, which is cool, huh. I also stated that I was not that keen on using XML file parsing in my Silverlight application. So what do I do. Well I have a fairly simply ASP.NET web service that I reference, and call just like I described above, and I am using XML too. It's just how the XML is dealt with that's different. Let's have a look at what I do. Recall, I wanted to use Flickr. Flickr actually has a RssFeed which is available using the following URL: "http://www.flickr.com/services/rest/?method=flickr.interestingness.getList&api_key=" + _FLICKR_API_KEY;
When I bash this URL into the browser, the following XML is returned.
<rsp stat="ok">
<photos page="1" pages="5" perpage="100" total="500">
<photo id="1809392098" owner="37451064@N00"
secret="9ddb3512d4" server="2012" farm="3"
title="Day 278/365......This is about as Halloween as I get"
ispublic="1" isfriend="0" isfamily="0"/>
<photo id="1807982526" owner="51107197@N00" secret="1dd4e67541"
server="2241" farm="3"
title="Polihale Sunset" ispublic="1" isfriend="0" isfamily="0"/>
<photo id="1807122211" owner="55217458@N00" secret="4c333e73e4"
server="2112" farm="3"
title="waiting" ispublic="1" isfriend="0" isfamily="0"/>
<photo id="1808703464" owner="76743095@N00" secret="85d3010d77"
server="2131" farm="3"
title="Cyclops" ispublic="1" isfriend="0" isfamily="0"/>
....
....
</photos>
</rsp>
So I thought why not use XLINQ to do something cool. There may be some of you who have read some of my other WPF articles, and have seen this before. But I am a creature of habit, and really like Flickr, so sorry if this is old to you guys.
But with this XML feed available I am able to use XLINQ to select the data I'm interested in, into new .NET types, which are returned via the web service to the Silverlight application. Let's have a look shall we.
In my Page.xaml.cs Silverlight file, I called the web service like:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Collections.Generic;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Browser;
using SilverlightProject1.Controls;
namespace SilverlightProject1
{
public partial class Page : Canvas
{
private SilverlightProject1.FlickrService.PhotoInfo[] photos;
private FlickrService.WebService websservice;
public void Page_Loaded(object o, EventArgs e)
{
InitializeComponent();
websservice = new FlickrService.WebService();
photos = websservice.GetFlickr(pageIndex,
Constants._NUM_OF_PHOTOS_FROM_FLICKR);
}
}
}
It can be seen that this call to the web service actually returns an array of SilverlightProject1.FlickrService.PhotoInfo[]
which come straight from the web service call. So let's follow this chain of events. The web service looks like:
using System;
using System.Linq;
using System.Web;
using System.Collections;
using System.Collections.Generic;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Xml.Linq;
using System.Web.Script.Services;
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[System.Web.Script.Services.ScriptService]
public class WebService : System.Web.Services.WebService
{
private RSSFeed _Feed;
public WebService () {
_Feed = new RSSFeed();
}
[WebMethod]
[ScriptMethod(ResponseFormat = ResponseFormat.Json)]
public List<PhotoInfo> GetFlickr(int pageIndex,
int numOfImageToFetch)
{
return _Feed.LoadPictures(pageIndex, numOfImageToFetch); ;
}
}
So it can also been seen that the web service holds an instance to an RssFeed
object, which it calls on the GetFlickr()
method, which calls the RssFeed
objects LoadPictures()
method. Mmm. Let's look at that.
using System;
using System.Data;
using System.Configuration;
using System.Linq;
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;
using System.Xml.Linq;
using System.Collections.Generic;
public class RSSFeed
{
#region Instance fields
private const string _FLICKR_API_KEY =
"c705bfbf75e8d40f584c8a946cf0834c";
private const string _MOST_RECENT =
"http://www.flickr.com/services/rest/?method=flickr.
photos.getRecent&api_key=" + _FLICKR_API_KEY;
private const string _INTERESTING = "http://www.flickr.com/
services/rest/?method=flickr.interestingness.getList&
api_key=" + _FLICKR_API_KEY;
private string _url = _INTERESTING;
public RSSFeed()
{
}
public List<PhotoInfo> LoadPictures(int pageIndex, int numOfImageToFetch)
{
try
{
var xraw = XElement.Load(_url);
var xroot = XElement.Parse(xraw.ToString());
var photos = (from photo in xroot.Element("photos").
Elements("photo")
select new PhotoInfo
{
_Id = (string)photo.Attribute("id"),
_Owner = (string)photo.Attribute("owner"),
_Title = (string)photo.Attribute("title"),
_Secret = (string)photo.Attribute("secret"),
_Server = (string)photo.Attribute("server"),
_Farm = (string)photo.Attribute("farm")
}).Skip(pageIndex * numOfImageToFetch).
Take(numOfImageToFetch);
return photos.ToList<PhotoInfo>();
}
catch
{
return null;
}
}
}
Notice the cool XLINQ stuff, all too easy, no nasty XML file parsing. Sold that. Just use XLINQ and create a collection of new CLR objects, (PhotoInfo
) in my case. Neat. And then return that to the web service as a list of PhotoInfo
where the web service simply returns these to the Silverlight application. Once in the Silverlight application, we can use these PhotoInfo
objects any way we like. They are in fact used to obtain images from Flickr as we'll discuss later. Now isn't that neater than messing around with XML parsing. XLINQ rocks. In fact LINQ rocks, MSFT have got it well right if you ask me.
Building Dynamic User Interfaces with Silverlight
Silverlight development is all about custom controls, there are very few native controls the way it stands at the moment. But it's only an alpha after all. That said there aren't any input controls, which I mentioned earlier. You have to write most of the stuff yourself. The Silverlight 1.1 reference poster quite clearly shows you which controls are available to you. It's basically the following controls:
- Canvas
- Control
- Image
- MediaElement
- OpenFileDialog
- TextBlock and a few more
So how are people creating such RIAs using Silverlight. Well it's down to hard work, and to be honest it's not that hard, and Silverlight is actually fun you know, so you get into it. I think the best place to start is to show you how to create a custom control or two. I'm going to show you how to create a button and then an image which is rotated and has an alpha applied to it when you mouse over it. I'll also briefly show screen shots and offer simple (yes, you'll have to investigate further) explanations of all other controls in the attached demo application.
Custom Button Control
I like to start with a screen shot.
This is what we are going to be making. A very simply button that glows when it has mouse focus, and can be disabled which will use a different disabled color brush. So that's what we are trying to achieve. So let's begin.
Most controls in Silverlight will either inherit from Control
or from Canvas
. In fact even Control
ends up being a Canvas
as you shall see. The first step is to decide if your control will have any markup (XAML) associated with it. If you want to do animations etc. you might like to use XAML (you can of course, do it in code too). If you want to use some XAML you probably want to inherit from Control
, which you can do by deciding to add a new control is VS2008. If you do this, the new control will indeed inherit from Control
, but it will also have an XAML file so that you can add elements to the XAML. So there will be two files, the xxxx.XAML and the xxxx.XAML.cs/vb which holds the controls logic. So you need some way of binding the two together, which is the first thing that must be done. This XAML must be loaded from a stream, into an actual element that can be used as a datum point (root element) for all other elements. The following code shows the XAML file for the button being loaded and a FrameworkElement
being declared to hold the newly constructed element read from the XAML.
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Browser;
namespace SilverlightProject1.Controls
{
public class GrayButton : Control
{
private FrameworkElement _root;
public GrayButton()
{
System.IO.Stream s = this.GetType().Assembly.
GetManifestResourceStream(
"SilverlightProject1.Controls.GrayButton.xaml");
_root = this.InitializeFromXaml(new
System.IO.StreamReader(s).ReadToEnd());
...
...
}
}
}
So once you've loaded the XAML and obtained a reference to the actual element (FrameworkElement
), you can try and find objects within the XAML DOM. The complete code for the GrayButton
is shown below to give you a flavour of what you need to do in order to create a custom control with events and XAML. This is a very simple button implementation, but it does the job.
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Browser;
namespace SilverlightProject1.Controls
{
public class GrayButton : Control
{
#region Instance fields
private FrameworkElement _root;
private Storyboard _animate;
private TextBlock _text;
private Rectangle _background, _highlight;
private bool _mouseDown;
private bool _IsEnabled = true;
private Brush _EnabledBrush;
private Brush _DisabledBrush;
public event EventHandler MouseDown, MouseUp, Click;
#endregion
#region Ctor
public GrayButton()
{
System.IO.Stream s = this.GetType().Assembly.
GetManifestResourceStream(
"SilverlightProject1.Controls.GrayButton.xaml");
_root = this.InitializeFromXaml(new
System.IO.StreamReader(s).ReadToEnd());
_animate = (Storyboard)_root.FindName("animate");
_text = (TextBlock)_root.FindName("text");
_background = (Rectangle)_root.FindName("background");
_EnabledBrush = _background.Fill;
_DisabledBrush = new SolidColorBrush(Colors.LightGray);
_highlight = (Rectangle)_root.FindName("highlight");
_mouseDown = false;
MouseEnter += new MouseEventHandler(OnMouseEnter);
MouseLeave += new EventHandler(OnMouseLeave);
MouseLeftButtonDown += new MouseEventHandler(
OnMouseLeftButtonDown);
MouseLeftButtonUp += new MouseEventHandler(
OnMouseLeftButtonUp);
CenterText();
}
#endregion
#region Public methods/properties
public String Text
{
get { return _text.Text; }
set
{
_text.Text = value;
CenterText();
}
}
public double Left
{
get { return (double)GetValue(Canvas.LeftProperty); }
set { SetValue(Canvas.LeftProperty, value); }
}
public double Top
{
get { return (double)GetValue(Canvas.TopProperty); }
set { SetValue(Canvas.TopProperty, value); }
}
public new double Width
{
get { return _background.Width; }
set
{
_background.Width = value;
_highlight.Width = value;
CenterText();
}
}
public new double Height
{
get { return _background.Height; }
set
{
_background.Height = value;
_highlight.Height = value;
CenterText();
}
}
public double RadiusX
{
get { return _background.RadiusX; }
set
{
_background.RadiusX = value;
_highlight.RadiusX = value;
}
}
public double RadiusY
{
get { return _background.RadiusY; }
set
{
_background.RadiusY = value;
_highlight.RadiusY = value;
}
}
public bool IsEnabled
{
get { return _IsEnabled; }
set
{
_IsEnabled = value;
_background.Fill = _IsEnabled ? _EnabledBrush :
_DisabledBrush;
}
}
#endregion
#region Private methods/properties
private void CenterText()
{
_text.SetValue(Canvas.LeftProperty,
_background.Width / 2 - (_text.ActualWidth / 2));
_text.SetValue(Canvas.TopProperty,
_background.Height / 2 - (_text.ActualHeight / 2));
}
private void OnMouseLeftButtonUp(object sender, MouseEventArgs e)
{
if (_IsEnabled)
{
if (_mouseDown)
{
Click(this, new EventArgs());
_mouseDown = false;
}
}
}
private void OnMouseLeftButtonDown(object sender, MouseEventArgs e)
{
if (_IsEnabled)
{
_mouseDown = true;
MouseDown(this, new EventArgs());
}
}
private void OnMouseLeave(object sender, EventArgs e)
{
if (_IsEnabled)
{
_animate.Stop();
}
}
private void OnMouseEnter(object sender, MouseEventArgs e)
{
if (_IsEnabled)
{
_animate.Begin();
}
}
#endregion
}
}
And here's the associated XAML for the GrayButton
<Canvas xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="15" Height="50">
<Canvas.Resources>
<Storyboard x:Name ="animate">
<DoubleAnimation Storyboard.TargetName="highlight"
Storyboard.TargetProperty="Opacity"
AutoReverse="True" RepeatBehavior="Forever"
Duration="0:0:0.5" From="0" To="0.5"/>
</Storyboard>
</Canvas.Resources>
<Rectangle x:Name="background" Width="130" Height="55"
StrokeThickness="1" RadiusX="2" RadiusY="2"
Stroke="#000000">
<Rectangle.Fill>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<LinearGradientBrush.GradientStops>
<GradientStop Color="#ffffff" Offset="0"/>
<GradientStop Color="#d2dfe8" Offset="0.2"/>
<GradientStop Color="#0475bc " Offset="0.50"/>
<GradientStop Color="#d2dfe8" Offset="0.8"/>
<GradientStop Color="#ffffff" Offset="1"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
<TextBlock x:Name="text" FontSize="14" Foreground="#000000"
Canvas.Top="17" Canvas.Left="33" Text=""
FontFamily="Arial Black" FontWeight="Bold"/>
<Rectangle x:Name="highlight" Width="15" Height="50"
StrokeThickness="4" Stroke="#ffffff"
Fill="#ffffff" Opacity="0"/>
</Canvas>
You can see that the root element is actually a Canvas
, in fact nearly everything is actually a Canvas
in Silverlight. Well, for the mean time anyway. I guess new controls will come with subsequent releases.
So that's one way of creating a custom control. But suppose you don't want any XAML and you just need code. Can you do that? Well sure you can, you can just inherit from Canvas
directly. I do exactly this for one of my own controls, the ScrollCanas
, which is a scrollable control which holds a page (using the LINQ Skip() Take()
operations to perform paging) worth of SmallPhoto
controls and looks like this. You can scroll the images by dragging with the mouse. The buttons can be used to fetch next/previous pages worth of images from Flickr, which is really just a call to the web service as I discussed earlier.
The code for this is shown below:
public class ScrollCanvas : Canvas
{
#region Instance fields
private SilverlightProject1.FlickrService.PhotoInfo[] _Photos;
private Point _PrevPoint = new Point();
public delegate void photoClickedDelegate(
SilverlightProject1.FlickrService.PhotoInfo file);
public event photoClickedDelegate PhotoClickedEvent;
public bool _IsScrolling=false;
#endregion
#region Ctor
public ScrollCanvas()
{
this.SetValue(Canvas.BackgroundProperty, Colors.LightGray);
this.SetValue(Canvas.WidthProperty,
Constants.SCROLL_CANVAS_WIDTH);
this.SetValue(Canvas.HeightProperty,
Constants.SCROLL_CANVAS_HEIGHT);
this.MouseMove += new MouseEventHandler(ScrollCanvas_MouseMove);
this.MouseLeftButtonDown += new MouseEventHandler(
ScrollCanvas_MouseLeftButtonDown);
this.MouseLeftButtonUp += new MouseEventHandler(
ScrollCanvas_MouseLeftButtonUp);
}
#endregion
#region Public properties/methods
public SilverlightProject1.FlickrService.PhotoInfo[] Photos
{
get { return _Photos; }
set
{
_Photos = value;
if (_Photos.GetUpperBound(0) > 0)
{
this.Children.Clear();
int xOffset = 30;
foreach (SilverlightProject1.FlickrService.PhotoInfo
file in _Photos)
{
SmallPhoto sp = new SmallPhoto(this, file);
sp.Left = xOffset;
sp.Top = 20;
sp.Width = Constants.SMALL_PHOTO_HEIGHT;
sp.Height = Constants.SMALL_PHOTO_HEIGHT;
sp.PhotoClickedEvent += new
SmallPhoto.photoClickedDelegate(
sp_PhotoClickedEvent);
this.Children.Add(sp);
xOffset += ((int)sp.Width + 30);
}
}
}
}
public double Left
{
get
{
return (double)GetValue(Canvas.LeftProperty);
}
set
{
SetValue(Canvas.LeftProperty, value);
}
}
public double Top
{
get
{
return (double)GetValue(Canvas.TopProperty);
}
set
{
SetValue(Canvas.TopProperty, value);
}
}
public void OnPhotoClickedEvent(SilverlightProject1.FlickrService.
PhotoInfo file)
{
if (PhotoClickedEvent != null) PhotoClickedEvent(file);
}
#endregion
#region Private properties/methods
private void sp_PhotoClickedEvent(SmallPhoto sender)
{
OnPhotoClickedEvent(sender.File);
}
private void ScrollCanvas_MouseLeftButtonUp(object sender,
MouseEventArgs e)
{
_IsScrolling = false;
}
private void ScrollCanvas_MouseLeftButtonDown(object sender,
MouseEventArgs e)
{
_PrevPoint = new Point(0, 0);
_IsScrolling = true;
}
private void ScrollCanvas_MouseMove(object sender,
MouseEventArgs e)
{
if (_IsScrolling)
{
int deltaX;
if (_PrevPoint.Equals(new Point(0, 0)))
{
deltaX = 0;
}
else
deltaX = (int)(e.GetPosition(this).X -
_PrevPoint.X);
double leftEdge = (double)this.GetValue(Canvas.
LeftProperty);
leftEdge += deltaX;
if (leftEdge < ((this.Width / 2) * -1))
return;
if (!(leftEdge > 0))
{
this.SetValue(Canvas.LeftProperty, leftEdge);
}
_PrevPoint = new Point(e.GetPosition(this).X,
e.GetPosition(this).Y);
}
}
#endregion
}
So I hope that's enough to get you started with custom controls. I did mention that I would briefly discuss all the custom controls in the demo app, so here we go.
The ScrollCanvas
mentioned here hosts SmallPhoto
controls.
SmallPhoto
It is a small control that holds an image and is randomly rotated and has mouse events for selecting, and has a selected event which is used by the parent (ScrollCanvas
) control. It looks like this. The SmallPhoto
control uses one of the PhotoInfo
objects as returned by the web service, which we talked about earlier.
The ImageCanvas
control is the main control that occupies the main Silverlight area and hosts Photo
controls.
Photo
It is a control that holds an image and is randomly rotated and has mouse events for dragging/selecting/scaling/rotating. It looks like this. The combination of the ImageCanvas
control and the hosted Photo
controls offers an experience similar to that of MSFT surface. I should point out that I wanted to do this myself, but I found such a good example of this at Delays Blog and I couldn't have done it better, so I used it...sorry this is not good I know, it was just so cool already. But I feel I have added to it by getting the images from a different domain, I also introduced a close button and a caption. I also had to understand the code in the first place to change it. The Photo
control uses one of the PhotoInfo
objects as returned by the web service, which we talked about earlier.
The ImageCanvas
control also hosts another control called ArtyCanvas
which is just an image. However the ArtyCanvas
itself also holds several flashing Ring
controls. The Ring
controls are just pulsing rings that I think brighten the place up. They are for aesthetic reasons. I like them, so should you.
Interaction between HTML and Managed Code
Silverlight has the ability to interact with the page that is hosting it. As mentioned above this may be one approach, where Silverlight could talk to ASP.NET input controls (remember Silverlight 1.1 doesn't have these as standard) that are actually not part of the Silverlight object that exist within the page hosting the Silverlight object. The reason I have not done this is that even if you go down the route of using ASP.NET web controls, they can't be positioned over the Silverlight object even if they are positioned using absolute positioning. Well that didn't work for me at any rate. But if you want them to be available outside of the Silverlight control area, this is entirely feasible. But I just think a better approach is to author the custom controls you think you need in .NET code. This way the controls are re-usable and are packaged into the Silverlight projects output DLL.
More typically what you may want to do is, set or get a value from the DOM for the page that is hosting the Silverlight object, so I'm going to talk you through how to do that. It's ever so easy, let's have a look at it.
So firstly let's look at the hosting pages HTML (the DOM):
<!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>
<title>Silverlight Project Test Page </title>
<script type="text/javascript" src="Silverlight.js"></script>
<script type="text/javascript" src="TestPage.html.js"></script>
<style type="text/css">
body { background-color:White;}
.silverlightHost { width: 1000px; height: 800px; }
.table { border-color : #d2dfe8; border-style:dashed;
border-width:thin;}
.bottomDiv
{
width:100%;
font-family:Tahoma;
font-size:9px;
background: #d2dfe8;
color:Black;
}
</style>
</head>
<body>
<center>
<table cellpadding="0" cellspacing="0" class="table">
<tr>
<td>
<div id="SilverlightControlHost" class="silverlightHost" >
<script type="text/javascript">
createSilverlight();
</script>
</div>
</td>
</tr>
<tr class="">
<td>
<div id="bottomDiv" class="bottomDiv">
<label id="bottomText" >This will get changed by silverlight</label>
</div>
</td>
</tr>
</table>
</center>
</body>
</html>
That's the entire HTML/CSS for the hosting page for the attached demo application. The particular element to note is the label whose ID is set to bottomText
, this is the one that I will be setting from Silverlight. One thing to note is that the GetElementByID()
method is probably the most commonly used DOM method, so it's probably best to give the elements IDs. You could always use the GetElementsByTagName()
but I just find it easier to use the GetElementByID()
method.
Now let's have a look at how Silverlight gets and manipulates this element:
using System;
using System.Windows;
using System.Windows.Controls;
using System.Collections.Generic;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Browser;
namespace SilverlightProject1
{
public partial class Page : Canvas
{
public void Page_Loaded(object o, EventArgs e)
{
InitializeComponent();
try
{
HtmlDocument document = HtmlPage.Document;
HtmlElement bottomText = document.GetElementByID("bottomText");
bottomText.SetProperty("innerText", "I created this demo to
try out Silverlight V1.1, and I hope it may be useful for other
trying to learn. It uses Web Services | XLINQ |
Custom Controls | Timers | Animations | Silverlight ->
HTML DOM");
}
catch (Exception ex)
{
}
}
}
}
It's really very simple, ensure that you have the using System.Windows.Browser;
directive, and then you can create a new Document
by asking the HtmlPage
for its Document
and then it's all fairly standard DOM stuff. You can even add event handlers to the DOM elements that will call Silverlight code. Let's have a look at an example of that. This example is simply a trimmed version of one of the MSFT Silverlight quickstarts.
using System;
using System.Windows;
using System.Windows.Media;
using System.Windows.Controls;
using System.Windows.Browser;
namespace Samples.Silverlight.CS
{
public class BasicCanvas : Canvas
{
private HtmlDocument document;
public BasicCanvas() { }
private void OnEchoTextClicked(object sender, HtmlEventArgs e)
{
HtmlElement input = document.GetElementByID("txtInput");
HtmlElement output = document.GetElementByID("txtOutput");
string buttonText = e.SourceElement.GetAttribute("innerText");
output.SetAttribute("value", "The button text is: " + buttonText + ".
+ Echoed text is: " + input.GetAttribute("value"));
}
private void OnGetPropertiesClicked(object sender, EventArgs e)
{
try
{
string outputText = HtmlPage.DocumentUri.AbsolutePath + " - " +
HtmlPage.CurrentBookmark;
document.GetElementByID("txtOutputProperties").SetAttribute("value",
outputText);
}
catch (Exception ex)
{
string err = ex.Message;
}
}
public void OnLoaded(object sender, EventArgs e)
{
WebApplication.Current.ApplicationUnhandledException +=
new EventHandler<ApplicationUnhandledExceptionEventArgs>(ExceptionHandler.
OnUnhandledException);
try
{
document = HtmlPage.Document;
HtmlElement btnProps = document.GetElementByID("btnGetProperties");
HtmlElement btnEchoText = document.GetElementByID("btnEchoText");
bool ec1 = btnEchoText.AttachEvent("onclick",
new EventHandler<HtmlEventArgs>(this.OnEchoTextClicked));
bool ec2 = btnProps.AttachEvent("onclick", new EventHandler(
this.OnGetPropertiesClicked));
}
catch (Exception ex)
{
string err = ex.Message;
}
}
}
public class ExceptionHandler
{
public static void OnUnhandledException(Object sender,
ApplicationUnhandledExceptionEventArgs e)
{
string err = e.ExceptionObject.Message;
e.Handled = false;
}
}
You can also create new elements and append these to the DOM using the document.CreateElement()
and document.DocumentElement.AppendChild(...)
methods.
Additional Programming Tasks
Some other typical tasks may be to upload a file to the web server or download a file from the web server. This sounds a little tricky at first, but luckily MSFT has actually been kind to us and provided us devs with one nice class called Downloader
, which as you could probably guess, is used to do a download of a file.
In the demo application, I am using the Downloader
to download images from Flickr, which I'll talk about in just a minute, but let's just have a quick look at how to upload a file to the web server.
Uploading Files
Here is the relevant code to do an upload.
You would use something like this is a C#/VB code behind file within the Silverlight project.
using System;
using System.Linq;
using System.Xml;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Text;
using System.IO;
using System.Windows.Browser;
using System.Windows.Browser.Net;
using System.Net;
namespace ImageUploadCS
{
public partial class Page : Canvas
{
public void Page_Loaded(object o, EventArgs e)
{
InitializeComponent();
StatusCanvas.Visibility = Visibility.Collapsed;
UploadButton.MouseLeftButtonDown += OnUpload;
}
private void UploadImage(string fileName, string fileContents)
{
Uri uploadService = new Uri("Service.asmx/UploadImage",
UriKind.Relative);
BrowserHttpWebRequest _request = new BrowserHttpWebRequest(
uploadService);
_request.Method = "POST";
_request.ContentType = "application/x-www-form-urlencoded";
string formBody = "fileName=" + HttpUtility.UrlEncode(fileName)
+ "&" + "fileContents=" + HttpUtility.UrlEncode(
fileContents);
UTF8Encoding encoding = new UTF8Encoding();
byte[] formBytes = encoding.GetBytes(formBody);
Stream body = _request.GetRequestStream();
body.Write(formBytes, 0, formBytes.Length);
HttpWebResponse response = (HttpWebResponse)_request.GetResponse();
body.Close();
}
private void OnUpload(object sender, EventArgs e)
{
StatusCanvas.Visibility = Visibility.Collapsed;
OpenFileDialog ofd = new OpenFileDialog();
ofd.EnableMultipleSelection = false;
ofd.Filter = "Image files (*.jpg;*.png)|*.jpg;*.png";
ofd.Title = "Select an image to upload";
if (ofd.ShowDialog() == DialogResult.OK)
{
string fileName = ofd.SelectedFile.Name;
Stream f = ofd.SelectedFile.OpenRead();
byte[] fileBytes = new byte[f.Length];
int byteCount = f.Read(fileBytes, 0, (int)f.Length);
string fileContents = Convert.ToBase64String(fileBytes);
UploadImage(fileName, fileContents);
}
}
}
}
This code would then call a web service which could then use the file locally (i.e. it could save the file in a web server's directory).
using System;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Web.Caching;
using System.IO;
[WebService(Namespace = "http://tempuri.org/")]
public class SampleUploadServiceCS : System.Web.Services.WebService
{
public SampleUploadServiceCS ()
{
}
[WebMethod]
public void UploadImage(string fileName, string fileContents)
{
}
}
So that is how you would do an upload.
Downloading Files
As I stated earlier, MSFT has been kind to us and has actually provided a nice little object called the Downloader
, which we can use to download files. It's simple enough to use this Downloader
class, simply create a new Downloader
and tell it what to get, and subscribe to its Completed
event, by which time the downloaded file should be ok to use. This is accessible on the Downloader
which was created. Let's see a very simple example.
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Net;
using System.Text;
using System.IO;
using System.Xml;
using System.Windows.Browser;
using System.Windows.Browser.Net;
using System.Collections.Generic;
namespace ImageViewerCS
{
public partial class Page : Canvas
{
private Downloader _downloader = new Downloader();
public void Page_Loaded(object o, EventArgs e)
{
InitializeComponent();
_downloader.Completed += DownloaderCompleted;
GetImage(defaultImageName);
}
public void GetImage(string imageName)
{
_downloader.Open("GET", new Uri(imageName, UriKind.Relative));
_downloader.Send();
}
public void DownloaderCompleted(object o, EventArgs e)
{
ImageFrame.SetSource(_downloader, null);
}
}
}
It's very simple to use the Downloader
really. But, and there is a but, you can't (currently) use the Downloader
to download files that are not on the same domain. And guess what, the cross domain check also includes port number. But wait, the attached demo application is getting images from Flickr, so how is it doing that, Flickr is indeed cross domain. Mmmm, curious. I also stated that I am using the Downloader
to download files, so how is that.
Let's consider the following image, this is what we would like to do. We'd like a Silverlight application that can talk to both localhost resources and third party resources/services etc. But like I said, this isn't possible using the Downloader
.
So what do we do about this. Well let's consider the following image, it can be seen that what actually happens is that we can still talk cross domain to Flickr, but we have to go via some proxy object. In my case as I only wanted to download images, I used an ASP.NET handler object, called DisplayImage.ashx. I found this trick on the internet, at Peter Kellners web site, so thanks for that Peter, top job.
By using the ASP.NET handler DisplayImage.ashx, which is a local resource, we are able to delegate the fetching of cross domain images from Flickr to the handler, and as each image is treated like this they are loaded asynchronously. Neat huh. So what does the code look like to do all this.
Well, like I stated earlier, both the Photo
and SmallPhoto
controls use images that come from a third party domain (Flickr) so how do they do that. Let's consider the code for the SmallPhoto
control, it's the same principle for the Photo
control. The SmallPhoto
uses the following XAML:
<Canvas xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="40" Height="40" Background="White" Opacity="0.5">
<Canvas.RenderTransform>
<TransformGroup>
<RotateTransform x:Name="rotateTransform" CenterX="20" CenterY="20"/>
<ScaleTransform x:Name="scaleTransform" CenterX="20" CenterY="20" />
</TransformGroup>
</Canvas.RenderTransform>
<Image x:Name="image" Canvas.Left="5" Opacity="0.2" Canvas.Top="5"
Width="30" Height="30" Stretch="Fill" />
</Canvas>
You can see that there is an Image
element there in the XAML. So that's where our image from Flickr (third party external domain) image will go. So how does the image end up there. Well let's look at the code behind for the SmallPhoto
control.
internal class SmallPhoto : Control
{
#region Instance fields
private FrameworkElement _root;
private Image _image;
private string _imageUrl;
private Downloader _Downloader;
private SilverlightProject1.FlickrService.PhotoInfo _file;
public SmallPhoto(ScrollCanvas parent,
SilverlightProject1.FlickrService.PhotoInfo file)
{
System.IO.Stream s = this.GetType().Assembly.
GetManifestResourceStream("SilverlightProject1.Controls.
SmallPhoto.xaml");
_root = this.InitializeFromXaml(new System.IO.StreamReader(s).
ReadToEnd());
_parent = parent;
_root.Visibility = Visibility.Collapsed;
_file = file;
_Downloader = new Downloader();
_Downloader.Completed += new EventHandler(Downloader_Completed);
_image = _root.FindName("image") as Image;
this.ImageUrl = GetPhotoUrl(_file);
}
public SilverlightProject1.FlickrService.PhotoInfo File
{
get { return _file; }
}
public string ImageUrl
{
get { return _imageUrl; }
set
{
_imageUrl = value;
if (!String.IsNullOrEmpty(_imageUrl) && _imageUrl.IndexOf
("http") >= 0)
{
try
{
string url = value;
string fullImageUrl =
"http://localhost:49505/WebSite1/DisplayImage.ashx?
URL=" + url + "&Width=250";
Uri uri = new Uri(fullImageUrl, UriKind.Absolute);
_Downloader.Open("GET", uri);
_Downloader.Send();
}
catch (Exception ee)
{
string str = ee.ToString();
}
}
}
}
private string GetPhotoUrl(SilverlightProject1.FlickrService.
PhotoInfo file)
{
return "http://farm" + file._Farm + ".static.flickr.com/" +
file._Server + "/" + file._Id + "_" +
file._Secret + "_m.jpg";
}
private void Downloader_Completed(object sender, EventArgs e)
{
_image.SetSource(_Downloader, null);
_root.Visibility = Visibility.Visible;
lock(this)
{
Page.NumberOfRequestedImagesLoaded++;
}
}
#endregion
}
You can see that there is a Downloader
object, which I talked about earlier. But the Downloader
object is actually calling the ASP.NET handler DisplayImage.ashx, and the job of fetching the cross domain image is actually done by the ASP.NET handler DisplayImage.ashx. Also of note, is that the SilverlightProject1.FlickrService.PhotoInfo
is a new object that was created within the RSSFeed
class which I talked about earlier. The SilverlightProject1.FlickrService.PhotoInfo
was creating using the XLINQ selection technique that we saw earlier. So all that is left to discuss, is the ASP.NET handler itself. So let's have a look at that, shall we. It's important to remember that the ASP.NET handler DisplayImage.ashx is not part of the Silverlight project but is actually part of the associated web project, where the Silverlight object is actually hosted. But there is no issue with the Silverlight code talking to the ASP.NET handler DisplayImage.ashx. It's the same domain and port, so it's considered to be the same domain.
<%@ WebHandler Language="C#" Class="DisplayImage" %>
using System;
using System.Web;
/// <summary>
/// This handler is used to load the images used by Silverlight asynchronously
/// this handler is also on the same domain as the Downloader Silverlight
/// object which is vital, as currently the Downloader object does not allow
/// cross domain calls. So you have to make calls to the same IP address and
/// port.
/// So this is a handy way of delegating the gathering of 2rd party images
/// (Flickr) to a handler, AKA this DisplayImage.ashx
/// </summary>
public class DisplayImage : IHttpHandler
{
public void ProcessRequest (HttpContext context) {
try
{
context.Response.ContentType = "image/jpeg";
context.Response.Cache.SetCacheability(HttpCacheability.Public);
context.Response.Cache.SetExpires(DateTime.Now.AddSeconds(5));
string URLLocationEncoded = context.Request.QueryString["URL"] ==
null ? string.Empty : context.Request.QueryString["URL"];
int maxWidth = context.Request.QueryString["Width"] ==
null ? 0 : Convert.ToInt32
(context.Request.QueryString["Width"]);
System.Drawing.Image img = GetComputedImage(URLLocationEncoded,
maxWidth);
img.Save(context.Response.OutputStream,
System.Drawing.Imaging.ImageFormat.Jpeg);
}
catch (Exception ex)
{
}
finally
{
}
}
public System.Drawing.Image GetComputedImage(string URLLocationEncoded,
int maxWidth)
{
string url = URLLocationEncoded;
System.Net.WebClient webClient = new System.Net.WebClient();
System.IO.Stream streamBitmap = webClient.OpenRead(url);
System.Drawing.Image imgReturn =
new System.Drawing.Bitmap(streamBitmap);
if (imgReturn.Width > maxWidth)
{
int heightNew = GetScaledHeightFromWidths(maxWidth,
imgReturn.Height, imgReturn.Width);
imgReturn = new System.Drawing.Bitmap(imgReturn, maxWidth,
heightNew);
}
return imgReturn;
}
public bool IsReusable
{
get { return false; }
}
private int GetScaledHeightFromWidths(int maxWidth, int imgReturnHeight,
int imgReturnWidth)
{
double scaleFactor = Convert.ToDouble(maxWidth) /
Convert.ToDouble(imgReturnWidth);
int heightNew = Convert.ToInt32(Convert.ToDouble(imgReturnHeight) *
scaleFactor);
return heightNew;
}
}
The following is a list of the best resources that I found whilst constructing this application:
I would just like to ask, if you liked the article please vote for it, and leave some comments, as it lets me know if the article was at the right level or not, and whether it contained what people need to know.
I have enjoyed writing this article and I hoped that I've explained some useful information regarding Silverlight development. In closing, I would say I quite like this Silverlight stuff, I was pretty convinced that I was not going to like it at the start, just because I enjoyed Flash so much. But something strange happened during this last week, I actually did a complete U-Turn and found out I love Silverlight, and think that when the pukka fully released version of Silverlight comes out, it will be excellent, and will be a true competitor to Flash. And if MSFT listens to the feedback I'm sure future versions will be even better. What would be nice to see is some of the more powerful WPF features such as Templates/Styles and Binding and some user input controls/data controls may be nice too.