Update: I updated the code to work with Silverlight 2 RTW.
So with the release of the Silverlight 2 beta bits I finally decided to see what all the talk was about. I suppose I would consider myself an ASP.NET guy by trade. Although I have been building WPF applications for the past year or so and generally I am enjoying NOT building web applications. However, I can't hide the fact that I spent the last seven odd years building mostly ASP.NET applications.
So when I first heard of Silverlight and heard it was in the same vain as Flash I was skeptical to say the least. I would say I was somewhere closer to outright disgust. I spend so much time focusing on a lightweight client that offloads it's work to a controlled and decidedly beefier web server. There is no way I want to push a plug-in to a client and THEN pay the price of shoving all the assest's down the pipe and hoping their machine can handle it. but alas, I will give it a fair shake. I do like WPF after all.
So with that said I wanted to see what the development experience is like and what kind of things I could do with the WPF skills I had amassed.
Cue PhotoStackr (note the gratuitous web 2.0 spelling)
One of the things I really like with WPF, and the main thing that makes WPF so compelling to me, is that it gives you the ability to focus on user interaction and user experience and less on implementation details. This allows you to build incredible applications with minimal effort (compared to building the equivalent in WinForms). So before I show the gory details of my Silverlight application I will say that I was trying to build an application that models a known collection of data (in this case a set of photos from the flickr website) in a new / interesting way.
To that end Silverlight, really XAML in general, gives you the tools to visual your data quickly. For the photo stackr application I wanted to give a interesting way to view and organize multiple search results on a single page.
What I like about Silverlight is it changes the development paradigm on the web. With the plug-in architecture you have a stateful environment to develop to. You can write fully managed code to do things previously done on the client in JavaScript (like drag and drop)
The basic design idea of PhotoStackr is to have a "Photo Table", which is the main canvas of the application. On the table you will have multiple "Photo Stacks." These stacks are the result of searching the Flickr web service for a specified tag. The application will give you the ability to "un-stack" your search results so you can view all results at once. You can then zoom in on any one of the results to view the photo in more detail.
There is a link at the bottom of this post where you can get the entire project to view the code in more detail so I will just focus on the main points.
The main canvas is pretty simple I put a text block to show the title in the top left and then two custom controls, one for the search box and one for the about window. I am toggling the visibility of these controls on the button click event of the respective buttons. This brings me to my first annoyance with Silverlight. The current build doesn't support triggers. Triggers are fundamental (IMHO) to WPF. Since I am approaching Silverlight with a WPF background the lack of triggers is painfully obvious. However, Sivlerlight is still in beta so some features just aren't there. I don't know if there is a plan to support triggers in Silverlight. Personally, I think they have to be supported if they want Silverlight to follow the "Do it in markup, not in code" mantra.
<Canvas Background="Black" x:Name="Table" Loaded="Table_Loaded" SizeChanged="Table_SizeChanged" >
<TextBlock x:Name="Title"Text="Photo Stackr"FontSize="16" Canvas.Left="100" Canvas.Top="50" Opacity=".4"/>
<Button x:Name="AddButton"Click="AddButton_Click" Template="{StaticResource AddStackButtonTemplate}" Canvas.Left="100"Canvas.Top="650"/>
<Button x:Name="AboutButton"Click="AboutButton_Click" Template="{StaticResource AboutButtonTemplate}" Canvas.Left="125" Canvas.Top="650"/>
<local:MessageControl x:Name="About"Canvas.Left="250"Visibility="Collapsed"/> <local:SearchPopup x:Name="SearchPopup"Canvas.Left="100"Visibility="Collapsed" />
</Canvas>
I like the control architecture that Silverlight drives you towards. I have built the SearchPopup and the MessageControl as separate UserControls in it's own XAML file. This is nothing revolutionary, you could do this in ASP.NET with custom controls, but with the stateful environment it makes managing multiple controls much easier. The controls are more aware of the application domain data and don't need state management code to work with the rest of the application. This gives you a development experience much closer to desktop development. This is a big plus to me.
When the user enters a search tag and clicks the search button I create a PolaroidStack control. This control is a canvas that has the logic to manage multiple Polaroid controls and perform the stack and un-stack behaviors. This brings me to the first programming feature that I really like about Silverlight. On the PolaroidStack control I added a static dependency property called SelectedTag
public static readonly DependencyProperty SelectedTagProperty = DependencyProperty.Register("SelectedTag", typeof(string), typeof(UserControl),OnSelectedTagChanged);
Dependency properties come from WPF and are a key component to control development. Since I have a dependency property on the PolaroidStack all I have to do in code is set the property and the change handler code in the stack control will take care of the rest.
PolaroidStack s = new PolaroidStack();
s.SetValue(PolaroidStack.SelectedTagProperty, SearchPhrase.Text);
Change handler for the PolaroidStack.SelectedTag property:
private static void OnSelectedTagChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
//Reload the Polaroid Stack
((PolaroidStack)d).LoadStack(e.NewValue.ToString());
}
The nice thing about the DependencyProperty is that it can also be set in markup. If I created the PolaroidStack in markup I could set the SelectedTag property in the markup as well which would cause the stack load itself when the control is loaded (and this is exactly what I did to test the LoadStack functionality)
In the LoadStack method I make a call the the Flickr Rest based Webservice passing the tag that the user passed in as the search term. The only thing to note here is that I am processing the result of the web service using LinqToXml which makes processing the XML document leaps and bounds easier then before.
XDocument doc = XDocument.Parse(e.Result);
var photos = from results in doc.Descendants("photo")
select new
{
id = results.Attribute("id").Value.ToString(),
farm = results.Attribute("farm").Value.ToString(),
server = results.Attribute("server").Value.ToString(),
secret = results.Attribute("secret").Value.ToString()
};
This snippet of code basically creates a dynamic collection and I am defining the members of the objects in the collection by selecting the attributes from the XML document. So I have a collection of Photos, the items in the collection are of type Photo. The Photo type (which is a dynamic type created by LINQ when I call select new) has string members id,farm,server, and secret. And this ends my LINQToXML lesson :), LinqToXML is really cool, for more info check here
After processing the web service response into a collection of Photo objects, I iterate the collection and build a new Polaroid control. The Polaroid control is a custom user control composed of an Image control, a button to control the zoom, and a border to give the look and feel of a Polaroid.
<Canvas Height="110" Width="91" Background="#FFFFFFFF" x:Name="PolariodCanvas">
<Button x:Name="Zoomer" Canvas.Left="70" Canvas.Top="95.666" Click="Button_Click" />
<Image x:Name="imageControl" Height="75" Width="75" Stretch="Fill" Canvas.Top="8" Canvas.Left="8"/>
<Border Height="75.75" Width="76.006" Canvas.Left="8" Canvas.Top="8" BorderBrush="#FF000000" BorderThickness="1,1,1,1"/>
</Canvas>
I am also creating a rotate transform in code to randomly set the angle of the Polaroid control to give it the messy look when stacked on top of each other. Again, the control development approach works nicely here because I can keep the logic to zoom the Polaroid in the Polaroid control.
The last thing to point out here is how I implemented drag and drop logic. I wanted to give the ability to drag and drop the PolaroidStack control so I hooked into the mouse button events of the Polaroid control. The actual drag and drop code is nothing new but the ability to do it all in managed code is the big difference. Previously this type of thing would have to be done in javascript. With Silverlight you can do it all in managed C# code
private void PolariodStack_MouseMove(object sender, MouseEventArgs e)
{
// only execute this code when the user has started dragging
if (startDrag)
{
Point movePos = e.GetPosition(null);
double currentX = double.Parse(this.GetValue (Canvas.LeftProperty).ToString()) + (movePos.X - beginPos.X);
double currentY = double.Parse(this.GetValue(Canvas.TopProperty).ToString()) + (movePos.Y - beginPos.Y);
this.SetValue(Canvas.LeftProperty, currentX);
this.SetValue(Canvas.TopProperty, currentY);
beginPos = movePos;
}
}
<Borat> Very Nice </Borat>
You can get the code for this project here. NOTE: you will have to sign up for a Flickr account and get your own API key to get this to work. I purposely forced a compile error to show you were to enter your API key.
So in the end, I like what Silverlight offers so far but it isn't all the way there yet. The lack of support for DataTemplates and Triggers are glaring issues. I am also still not sold on the plug-in approach. Solving the satefulness issue is huge and I think that is the overriding win with the plug-in approach but I am still concerned with pushing that much on to the client machine. The temptation to abuse the technology is high and pushing really intensive applications down the wire to the client can lead to bloated applications and serious support issues.
To see the application in action visit our Silverlight page silverlight.interknowlogy.com
Tell me what you think