Saturday 2 June 2012

Creating a New Silverlight Application using VS 2008


Creating a New Silverlight Application using VS 2008

We'll start our Digg application by selecting the File->New Project menu item within Visual Studio 2008 or Visual Web Developer 2008 Express (which is free) and use the New Project dialog to create a "Silverlight Application" (note: you will need to download and install the Silverlight Tools for VS 2008 release to get this support):

We'll name the project "DiggSample".  When we click the "OK" button Visual Studio will prompt us with an additional dialog that allows us to choose whether we want to create just a Silverlight Application project, or optionally also add a server-side ASP.NET Web project to our solution to host the Silverlight Application within.  For this sample we'll choose to add an ASP.NET Web Application project to the solution as well and name it "DiggSample_WebServer".  When we click "ok" Visual Studio will create a solution for us that has both a Silverlight client application and an ASP.NET web server application in it:

When we do a "build" Visual Studio will automatically copy the compiled Silverlight application to our web server project (no manual step or configuration required).  The default web server project that VS created for us contains both an ASP.NET page and a static HTML page that we can use to run and test our Silverlight Application within. 
Note: Silverlight Applications can be used with any web-server (including Apache on Linux) and hosted within static HTML files or any server-side generated page (including PHP, Java, Python, Ruby, etc).  For this Digg sample we won't be writing any server-side code - we'll instead use the cross-domain networking feature of Silverlight to access the Digg service API directly.  I chose to create the ASP.NET web server project mainly to get automatic deployment and use its built-in web-server for testing.

Understanding What Is In a Silverlight Application

By default a newly created Silverlight application project contains a Page.xaml and App.xaml file, as well as code behind class files (which can be written in VB, C#, Ruby or Python) that are associated with them:

XAML files are XML text files that can be used to declaratively specify the UI of a Silverlight or WPF application. XAML can also be used more broadly to declaratively represent .NET objects.
The App.xaml file is typically used to declare resources, such as brush and style objects that are shared across the application.  The Application code-behind class for the App.xaml file can be used to handle application level events - like Application_Startup, Application_Exit and Application_UnhandledException.
The Page.xaml file is by default the initial UI control that is loaded when the application activates. Within it we can use UI controls to define our user interface, and then handle events off of them within the Page code-behind class (much more on this later).
When we build our DiggSample project, Visual Studio will by default compile the code and .XAML markup into a standard .NET assembly file, and then package it and any static resources (like images or static files we want to include in it) into a "DiggSample.xap" file on disk: 

".xap" files (pronounced "zap") use the standard .zip compression algorithm to minimize client download size. A "hello world" .NET Silverlight application (built using VB or C#) is about 4KB in size.
To host and run a Silverlight 2 application, you can add an <object> tag into any standard HTML page (no JavaScript required) that points to the .xap file.  Silverlight will then automatically download the .xap file, instantiate it, and host it within that HTML page in the browser.  This works cross browser (Safari, FireFox, IE, etc) and cross platform (Windows, Mac, and Linux).
Test HTML and ASP.NET pages (containing the <object> tag reference that points to our Silverlight application) were automatically added for us when we created our project - which means we can just hit F5 to build, run and test it.

Learning how to Add Controls and Handle Events

Right now our Digg application doesn't do anything, and when it is run it brings up an empty page.
We can change this by opening up the Page.xaml file in the project and adding some content to it: 

We'll begin by changing the background of the grid and by declaring a Button control within it.  We'll give the button an "x:Name" attribute value of "MyButton" - which will allow us to programmatically reference it within our code-behind class.  We'll also set its Content, Width and Height properties:

When we run the application our button will then show up in the middle of the page with "Push Me" content text like below:
 
To add behavior to our button we can add a "Click" event handler to it.  We can do this in source view by typing the event name:

Which will then prompt us for the event handler in our code-behind class to use:

We can then either type a new event handler method name to use, or optionally just press the enter key to name the event handler method using the default naming convention:

VS will then automatically create a stub event handler implementation in our code-behind class file.  We can use this event handler to update the Button's content with a new message when it is clicked:

After making the change above we can re-run the application and push the button again, and now its content is updated with a "Pushed!" message:













Understanding Layout Management

Silverlight and WPF support a flexible layout management system that enables developers and designers to easily coordinate how controls are positioned within a UI surface.  This layout system supports both a fixed position model where controls are positioned using explicit coordinates, as well as a more dynamic position model where layout and controls can be automatically sized and flowed as the browser resizes.
Developers using Silverlight and WPF use layout panels to coordinate the position and resizing of controls contained within them.  The built-in layout panels in Silverlight 2 include three of the most commonly used ones in WPF:
  • Canvas
  • StackPanel
  • Grid
Canvas Panel
The Canvas panel is a pretty basic layout panel that supports positioning controls contained within it using explicit coordinates.
You position elements in a Canvas using a XAML feature called "Attached Properties" - which allow you to specify a control's position relative to its immediate parent Canvas control's Left, Top, Right or Bottom coordinates.  Attached properties are useful as they allow a parent panel to extend the property set of a control contained within it. Canvas, by defining an attached property for “Top” and ”Left” basically adds the ability to define left and top attachment on Button (or any other UI element that is added to the Canvas), without any need to actually add a property to the Button class, or modify the Button class in any way.
We could add two buttons to a Canvas container, and position them both 50 pixels from the left of the Canvas, and 50 and 150 pixels from the top using XAML like below (the Canvas.Left and Canvas.Top attributes are examples of the attached property syntax):

This would then render our buttons like below:

While the Canvas is useful for scenarios where the UI elements contained within it never move, it tends not to be very flexible as you add more controls into it or handle scenarios where the UI needs to resize or move.  In cases like these you end up having to manually write code yourself to move things around inside the Canvas (which is a pain).  A better solution for these dynamic scenarios is typically to use an alternative layout panel that has built-in semantics to-do this for you - like the StackPanel and Grid.
StackPanel
The StackPanel control is a simple layout panel that supports positioning its contained controls in either a row or column layout.  StackPanels are typically used in scenarios where you want to arrange a small subsection of the UI on your page.
For example, we could use the StackPanel to vertically arrange three buttons on our page using XAML markup like below:

At runtime the StackPanel would then automatically arrange the Button controls in a vertical stack for us like below:

We could alternatively set the "Orientation" property of the StackPanel to be "Horizontal" instead of Vertical (which is the default):

This will then cause the StackPanel to automatically arrange the Button controls in a horizontal row like below:

Grid Panel
The Grid control is the most flexible layout panel, and supports arranging controls in multi-row and multi-column layouts.  It is conceptually similar to an HTML Table element.
Unlike an HTML Table, you don't embed controls within column and row elements.  Instead you specify a Grid's Row and Column definitions using <Grid.RowDefinitions> and <Grid.ColumnDefinitions> properties that are declared immediately under the <Grid> control.  You can then use the XAML "Attached Property" syntax on controls contained within the grid to indicate which Grid row and column they should be populated within.
For example, we could declare a Grid layout has three rows and three columns, and then position 4 buttons within it using XAML like below:

The Grid layout panel would then position the Button elements for us like so:

In addition to supporting absolute sizing (for example: Height="60") the Grid's RowDefinition and ColumnDefinition controls also support an AutoSizing mode (Height="Auto"), which automatically sizes the Grid or Row based on the size of the content contained within it (you can also optionally specify maximum and minimum size constraints - which can be very useful).
The Grid's Row and ColumnDefinitions also support a feature called "Proportional Sizing" - which enables the size of a Grid's Rows and Columns to be spaced proportionally relative to each other (for example: you could have the second row grow at 2x the rate of the first one).
You'll find that the Grid provides a ton of power and flexibility - and it will probably be the most common layout panel control you'll end up using.

Using Layout Panels to Arrange our Digg Page

Remember that the goal when building our Digg sample is to create a page that looks like the one below:

To create this layout we'll begin by adding a root Grid panel that has two RowDefinitions within it.  The first Row will be 40 pixels high, and the second will fill the remaining space (Height="*"):

Tip: Notice above how I've set the Grid's "ShowGridLines" property to "True".  This will enable us to easily visualize the Row and Column boundaries within the Grid when we test it at runtime:

We'll then embed a second Grid panel control as a child control within the first row of the root Grid panel container, and use it to arrange the top row (the header).  We'll create three columns within it - one for the Title, one for the Search TextBox, and one for the Search Button:

Once this is done we have the basic layout arrangement of our Digg search page in place:

Note: As an alternative to nesting Grids, we could have alternatively had one Grid with 3 columns and 2 rows and used the ColSpan/RowSpan feature of the Grid to merge content across multiple columns (similar to how you can do this with HTML tables).  I chose to use the nested Grid approach instead because I thought it would be simpler to follow along.
Now that we have the layout setup all we need to-do is add controls to it. 
For the header row we'll use the built-in <Border> control (with a CornerRadius of 10 to get a nice rounded edge) and add some text inside it to create the Title.  We'll use the <TextBox> control in the second column for the search textbox.  And we'll put a search <Button> in the third column.  We'll then put some placeholder text in the second row where we are later going to display the search results.
Note: Below I'm embedding style information (FontSize, Colors, Margins, etc) directly on the controls.  Later in this tutorial series I'll show how to use Styles to extract and encapsulate these settings in a separate file (ala CSS) which can then be re-used across the application. 
 
And now when we run our application it looks like below:

Dynamically Resizing our Application
One thing you might have noticed in the XAML above is that our top-level control is currently set to be a fixed width and height:

When set this way our Silverlight Application will always remain that fixed size.  Expand the browser and this becomes apparent:

While constraining an embedded application to be a fixed size within a region of an HTML page is useful in some scenarios, for our Digg search application we really want the application experience to automatically flow and resize with the browser - just like an HTML page would.
The good news is that this is easy to implement.  Just remove the Width and Height attributes on the root control:

Our Silverlight application will then automatically expand (or shrink) to fill the HTML container it is embedded within.  Because the SilverlightTestPage.html file that we are testing our Silverlight application within hosts our Silverlight control in a HTML <div> element with a 100% width and height CSS setting on it, our Digg application will now fill the entire browser:

Notice how the content within the header of the application automatically resizes and flows based on the width of the browser:

When we shrink the browser, the watermark textbox and search button stay the same size because their Grid container columns are a fixed width.  The <Border> control containing our "Digg Search" title automatically resizes when we shrink the browser because the Grid column it is in is configured to be Width="*".
We did not need to write a single line of code to enable this layout behavior - the Grid container and the layout system took care of dynamically resizing and flowing everything for us.

Next Steps

We now have the basic layout structure of our Digg application created, and have our header row defined.
Our next step will be to implement the searching behavior of the application - and have it actually retrieve story content from Digg.com when an end-user using the application searches on a topic.

Using Networking to Retrieve Digg Stories

Silverlight 2 has built-in networking APIs that enable Silverlight clients to call remote REST, SOAP/WS*, RSS, JSON and XML HTTP services.  Silverlight 2 also includes a built-in sockets API (System.Net.Sockets) that enables Silverlight clients to communicate over non-HTTP protocols (ideal for scenarios like chat servers, etc).
Cross Domain Network Access
Silverlight 2 applications can always call back to their "origin" server when making network calls (meaning they can call URLs on the same domain that the application was downloaded from).  Silverlight 2 applications can also optionally make cross-domain network calls (meaning they can call URLs on different domains from where the application was downloaded from) when the remote web server has an XML policy file in place that indicates that clients are allowed to make these cross-domain calls. 
Silverlight 2 defines an XML policy file format that allows server administrators to precisely control what access a client should have.  Silverlight 2 also honors the default Flash cross domain policy file format - which means that you can use Silverlight 2 to call any existing remote REST, SOAP/WS*, RSS, JSON or XML end-point on the web that already enables cross-domain access for Flash clients.
Digg.com has a pretty cool set of Digg APIs that they publish over HTTP.  Because they have a Flash cross-domain policy file in place on their server, we can call them directly from our Silverlight Digg client application (and not require us to tunnel back through our web-server to reach their APIs).
Digg.com Topic Feed API
We want to enable end-users using our application to type in a search topic (for example: "Programming") and then click the "Search" button to pull back the top stories from Digg.com that match it:

We can use the Digg.com List Stories REST API feed API to-do this.  It takes a topic parameter in its URL (for example: GET /stories/topic/programming), and then returns back an XML payload of Digg stories that match that topic.  Click here to see an example of what this XML looks like.
Using System.Net.WebClient to Asynchronously Call the Digg REST Feed
When the SearchButton is clicked above, we'll handle its "Click" event, retrieve the topic string to search for from the TextBox control, and then initiate a network call to Digg to retrieve the XML listing for that topic.
Silverlight includes the WebClient helper class within the System.Net namespace (this is also in the full .NET Framework).  We can use this class to asynchronously download content from a URL.  The benefit of downloading our Digg stories asynchronously is that our UI will not block or become unresponsive while waiting on the remote server (allowing us to have a very fluid user experience). 
All we need to-do to perform an async download with the WebClient class is to register a "DownloadStringCompleted" event handler method that will be invoked once the requested content has been downloaded, and then call the WebClient.DownloadStringAsync(url) helper method to initiate the download:

And with the code above we can now asynchronously retrieve a string of XML data that contains the Digg stores about any topic a user wants.

Using LINQ to XML to Parse Digg XML Stories into Story Classes

Now that we can retrieve an XML snippet back of Digg story data, our next step will be to parse and convert it into "DiggStory" objects that we can manipulate and databind our controls against.
We'll do this by first defining a "DiggStory" class that has properties that map to the XML content from Digg (we'll take advantage of the new C# "automatic properties" feature to-do this):

We can then use LINQ (which is built-into Silverlight 2) and LINQ to XML (which is an extra library we can include with our Silverlight application) to easily parse and filter the XML document that was returned from Digg, and translate it into a sequence of "DiggStory" objects using the code below:

Notice above how we now have strongly typed DiggStory objects from our XML that we can work against.

Displaying our Digg Stories in a DataGrid Control

We'll use the Silverlight DataGrid control to display the Digg stories in our application.  To enable this we'll reference the Silverlight Data controls assembly, and then replace our previous "Todo" text on the page with a DataGrid control declaration (note: you can double click on the "datagrid" control in the toolbox to automatically add one to the page):

The DataGrid allows you to explicitly configure column declarations and display types (for maximum control). Alternatively if you do not specify these, the DataGrid control will use reflection on its datasource to create default columns for you based on the schema of your objects. 
We'll then update our code-behind class to programmatically bind the "ItemSource" property of the DataGrid to the sequence of stories we receive back from Digg when the search button is clicked:

And now when we run our Silverlight application and do a search, we'll see a listing of live topic story data pulled from Digg:

The Silverlight Datagrid supports all the standard features you expect with a client-side grid control: two way in-place editing, selection, scrolling, column resizing, column re-ordering, etc.  It also supports auto-flow layout, which means it can dynamically expand or shrink to fill the content container it is in.  The DataGrid also has a rich templating model that allows you to customize both the display and editing of column data. 

Next Steps

We can now retrieve Digg story data from Digg.com and display the story data within our application.
Our next step will be to go back to our Page.xaml markup and remove the in-line style declarations we are currently using there. 

Using Style Elements to Better Encapsulate Look and Feel

WPF and Silverlight support a Style mechanism that allows us to encapsulate control property values as a reusable resource.  We can store these style declarations in separate files from our pages, and re-use them across multiple controls and pages in an application (as well as re-use them across multiple applications).  This is conceptually similar to using CSS with HTML when doing basic customization scenarios.
Note: In addition to defining basic property settings (Color, Font, Size, Margins, etc), styles in WPF and Silverlight can also be used to define and re-use Control Templates - which enable super rich skinning and adaptation of control structure (and support customization scenarios not possible with CSS in HTML today).  I discuss Control Templates in Part 7 of this series.
For our Digg sample application we'll define our Style declarations within the App.xaml file of our project.  This will enable them to be reused across all pages and controls in the application:

Let's start by encapsulating styles for the <Border> control (and the <TextBlock> title contained within it) of our Digg page:


We can create two Style elements within our App.xaml file that encapsulate the <Border> and <TextBlock> settings we were previously declaring inline using the markup below:

Note how we are giving each Style a unique "Key" value above.  We can then update our <Border> and <TextBlock> controls to reference the Styles using these keys.  We'll be using a XAML feature called "markup extensions" to do this.  Markup extensions are used when there are non-literal values that we want to set (another example of where we'll use this feature is with databinding expressions).

When we update the other controls within our Page.xaml file to use styles as well, we are left with a file that looks like below:

Encapsulating the style settings this way allows developers to better focus on the behavior semantics of the application, and also enables us to re-use styles across other controls/pages we build.

Next Steps

Now that we've cleaned up the markup of our Page.xaml file using Style references, let's go one step further and customize the appearance of our Story data more.

Displaying our Digg Stories using the ListBox and DataBinding

Previously we've been using the DataGrid control to display our Digg stories.  This works great when we want to display the content in a column format.  For our Digg application, though, we probably want to tweak the appearance a little more and have it look less like a DataGrid of stories and more like a List of them.  The good news is that this easy - and it doesn't require us to change any of our application code to accomplish this.
We'll start by replacing our DataGrid control with a <ListBox> control.  We'll keep the control name the same as before ("StoriesList"):
 
When we run our application again and search for stories, the ListBox will display the following results:

You might be wondering - why is each item "DiggSample.DiggStory"?  The reason for this is because we are binding DiggStory objects to the ListBox (and the default behavior is to call ToString() on them).  If we want to display the "Title" property of the DiggStory object instead, we can set the "DisplayMemberPath" property on the ListBox:

When we do this the Title will be what is displayed in the ListBox:

If we want to show more than one value at a time, or customize the layout of each item more, we can override the ListBox control's ItemTemplate and supply a custom DataTemplate. Within this DataTemplate we can customize how each DiggStory object is displayed.
For example, we could display both the DiggStory Title and NumDiggs value using a DataTemplate like below.  

We can databind any public properties we want from our DiggStory object within the DataTemplate.  Notice above how we are using the {Binding Path=PropertyName} syntax to accomplish this with the two TextBlock controls.
With the above DataTemplate in place, our ListBox will now display its items like below:

Let's then go one step further and change our DataTemplate to the one below.  This DataTemplate uses two StackPanels - one to stack row items horizontally, and one to stack some textblocks together vertically:

The above DataTemplate causes our ListBox to display items like the screen-shot below:
 
when we define the following Style rules in our App.xaml (note how we are using a LinearGradientBrush to get the nice yellow gradient background on the DiggPanel):
 
One important thing to notice about our ListBox - even though we have customized what the items in it look like, it still automatically provides support for hover and item selection semantics.  This is true both when using the mouse and when using the keyboard (up/down arrow keys, home/end, etc):

The ListBox also supports full flow resizing - and will provide automatic scrolling of our custom content when necessary (notice how the horizontal scroll bar appears as the window gets smaller):

Next Steps

We've now switched our data visualization to be List based, and cleaned up the content listing of it.
Let's now complete the last bits of the functionality behavior in the application - and implement a master/details workflow which allows end-users to drill into the specifics of a story when they select an article from the list.

Understanding User Controls

A fundamental design goal of Silverlight and WPF is to enable developers to be able to easily encapsulate UI functionality into re-usable controls.  Developers can implement new custom controls by deriving a class from one of the existing Control classes (either a Control base class or from a control like TextBox, Button, etc).  Alternatively they can create re-usable User Controls - which make it easy to use a XAML markup file to compose a control's UI, and which are easy to implement.
For our Digg application, we want to implement a master/details scenario where the application allows an end-user to search on a topic, populate a list of stories related to that topic, and then enable them to select a story to bring up details about it.  For example, selecting the below story in the list:

would bring up this detailed view about the story:

We are going to implement this details view by building a "StoryDetailsView" UserControl that we'll display when a story is selected from our ListBox.

Creating a StoryDetailsView User Control

We'll start by right-clicking on our DiggSample project node in Visual Studio and by selecting "Add New Item".  This will bring up a new item dialog.  We'll select the UserControl item template and name the new control we want to build "StoryDetailsView":

This will add a new UserControl with this name to our DiggSample project:

Building a Basic Modal Dialog Using a User Control

We are going to use our StoryDetailsView control to effectively display a dialog containing story details.  When our story details user control displays we are going to want to have it appear on top of the other content on the page, and ensure that an end-user can't do other things with the page until they close the details view.
There are a couple of different ways we could implement this modal dialog-like behavior.  For this particular scenario we are going to start by opening up the StoryDetailsView.xaml user control and adding the below XAML content to it:

The first <Rectangle> control above is configured to stretch to take up all of the available space on the screen.  Its background fill color is a somewhat transparent gray (because its Opactity is .765 you can see a little of what is behind it).  The second <Border> control will then be layered on top of this Rectangle control, and take up a fixed width on the screen.  It has a blue background color, and contains a Close button.
When visible, our StoryDetailsView user control will currently display a UI like below:

We can implement the "CloseBtn_Click" event handler method in the code-behind file of the user control.  When pressed, the close button event handler will set the Visibility of the UserControl to "Collapsed" - which will cause it to disappear from the screen and return the user to the content below it:
 

Displaying our StoryDetailsView Control

An easy way to cause our StoryDetailsView user control to appear on the screen would be to simply add it to the bottom of our Page.xaml file, and set its default visibility to Collapsed (which means it is not visible when the application first loads):

We can then handle the "SelectionChanged" event from our ListBox control within the Page.xaml's code-behind class:

When a user selects a particular story in the list, we can use the ListBox SelectionChanged event handler to set the Visibility property of our ShowDetailsView user control to "Visible" :

This will cause our modal user control dialog to appear.  When the user clicks its "Close" button, it will disappear, and the user will be free to select another story and repeat the process.

Passing Story Data to our StoryDetailsView User Control

Ultimately we want our StoryDetailsView UserControl to display detailed information about the story that the end-user selected from the stories ListBox. 
Within our ListBox's "SelectionChanged" event handler (which is inside our page's code-behind class), we can gain access to the DiggStory data object that corresponds to the selected ListBox row by accessing the ListBox's "SelectedItem" property. 
One approach we could use to pass this DiggStory object to our StoryDetailsView UserControl would be to simply set the "DataContext" property on the User Control to the selected DiggStory data object immediately before making the user control visible:

We could then write code within our UserControl to procedurally use the DataContext to display results.  Or alternatively we could use databinding expressions to bind against its values. 
For example, we could update our StoryDetailsView XAML to display the Title of the selected story using a databinding expression like below:

And now when a user clicks a story in the list:

Our ListBox event handler will handle the selection, set the DataContext of the UserControl to the selected DiggStory object, and then make the user control visible:

Notice how the DiggStory title now appears in the user control because of the databinding expression we added to it.

Finishing our User Control Layout

Our sample above demonstrates the basics of how we can put together a simple master/details dialog workflow.  We can complete the StoryDetailsView display by adding more controls and databinding expressions to the User Control. 

We could update the StoryDetailsView user control to look like above by updating its <Border> control to have the following inner content:

No code changes are required after this.  Because we are using databinding to "pull" the values from the DataContext, we don't need to write any additional code.

Next Steps

We now have all of the core functionality and interaction workflow implemented for our Digg application.
The last step we are going to take is to tweak the UI of the application a little more.  Specifically we want to add a slightly more polished and customized look to the ListBox and Button controls.

How to Customize the Look and Feel of Controls

One of the most powerful features of the WPF and Silverlight programming model is the ability to completely customize the look and feel of the controls used within it.  This allows developers and designers to sculpt the UI of controls in both subtle and dramatic ways, and enables a tremendous amount of flexibility to create exactly the user experience desired.
In this tutorial segment we'll look at a few ways you can customize controls, and then close out by polishing up the user interface of our Digg application using these techniques.

Customizing the Content of Controls

In Part 1 of our tutorial we added a simple button control to the page and demonstrated how to set a custom "Push Me!" text string for its content.  We then wired up a "Click" event handler that executed code when it was clicked:

This caused the button to render like below within the browser:

One of the things that might surprise you about the Button control is that its "Content" property does not have to be a simple string like "Push Me!".  We can in fact set the "Content" property to be any sequence of Shapes or Controls we want. 
For example, we could embed a StackPanel with an <Image> and <TextBlock> control within it:
 
This will cause our Button to look like below at runtime.  Note that it still retains the same functionality behavior (push it and the button will still depress, and the click event handler will fire like before):

We could alternatively use Shape controls (like the Ellipse control below) to create custom vector graphics inside the button. 

Notice above how I'm filling the Ellipse control with an offset RadialGradientBrush to add a nice reflective polish to it:

We could even get wacky and embed interactive controls like a Calendar control within the Button:

In the above sample the Calendar is fully interactive - meaning end-users can actually page back and forward through the months and choose a date on the Calendar, and then push its containing button to trigger the "Click" event handler: (note: I'm not sure why this would be a good user experience - but it does show off the flexibility of what you can do!).

These types of content customization scenarios I outlined above work not just for Button controls, but likewise for all other controls that derive from the ContentControl base class.

Customizing Controls using Control Templates

The control model used by Silverlight and WPF allows you to go much further than just customizing the inner content of a control. It optionally allows you to completely replace a control's visual tree with anything you want - while still keeping the control behavior intact.
For example, let's say we don't want our buttons to have the default rectangle push button look, and instead we want them to have a custom round button look like below:

We could accomplish this by creating a "RoundButton" style in our App.xaml file.  Within it we'll override the Button's "Template" property, and provide a ControlTemplate that replaces the Button's default Rectangle visual with an Ellipse control and a TextBlock inside it: 

We can then have a <Button> control reference this Style resource to use this "RoundButton" look and feel:

Incorporating Content within Our Control Templates
One thing you might have noticed in our "RoundButton" control template above is that the size of the Button, and the Content displayed within it, are hard-coded (it always says "Push Me!"). 
The good news is that WPF and Silverlight make it possible for us to have these settings be customizable as well.  We can do this using a {TemplateBinding ControlProperty} markup extension syntax inside the control template to bind to properties of the control.  This will allow our control template to adapt as properties are set on the Control by an external developer:

Notice above how instead of adding a <TextBlock> control to display the content, we are instead using the <ContentPresenter> control.  That will enable us to have the button display not just text strings, but any custom content as well (just like we did earlier in this tutorial).
We can then use the above Style on three buttons below (each with different content and property settings):

The above Buttons will then display like below (and yes - the scaled Calendar control still supports paging and date selection!):

If we wanted to go further we could optionally add states to the ControlTemplate (to handle button states like "hover", "focus" and "pushed").  This capability enables us to create really polished user interaction scenarios, and enable scenarios not possible with HTML.  It also allows designers to nicely integrate animation and other powerful concepts within the experience.
Developers working with controls in an application can remain oblivious to all of this style and control interaction customization.  They can handle events off of the controls and manipulate the control's object model as normal, and have a designer own sculpting and customizing the look and feel separately using styles and templates.

Polishing up our Digg Application

Now that we've covered some of the basics of how Control Templates work, let's use them in a few places to add a bit more UI polish to our Digg application.
Right now there is one obvious place in the application that clearly needs some work - the "Close" button on our User Control:

The good news is that this is easy for either us (or a designer working with us) to fix.  We could add a ControlTemplate to the "CloseButton" style in our App.xaml file and add some custom vector shapes to provide a more attractive close button (note: a more competent designer than me could also add hover and animation behavior to the vector graphic shapes and make it look even better):

When we run the application again the button will now look like it does below:

What is cool is that we did not have to change any code in our application, nor modify the XAML markup on our actual controls, in order to make this look and feel change.  This code/design separation enables a great developer and designer workflow when working on Silverlight and WPF applications.  Expression Blend and the overall Expression Studio products take these control design capabilities to the next level, and will deliver a rich designer toolset that makes this customization easy.

Next Steps

We've now finished implementing our Digg application in Silverlight.
Our last step will be to implement a desktop application version of it as well.  The good news is that doing this is not too difficult - since Silverlight is a subset of the full WPF and .NET Framework, and so the concepts, code, and content are pretty easy to move. 

Creating a Digg Desktop Application Using WPF

Our goal with this last tutorial is a little different than the previous seven tutorials.  We actually aren't going to be running code in Silverlight with this tutorial - instead we will be using WPF and .NET 3.5.  We'll be taking the existing Digg application code we've written to run inside Silverlight in the browser, and re-use it to also run as a Windows desktop application.
Silverlight ships with a compatible API subset of what is in the full version of the .NET Framework.  One goal with this is to enable developers to learn a common programming model and tool-set and be able to re-use skills, code and content across RIA web applications, rich Windows desktop applications, and Office solutions.
Below are the steps I took to re-use our existing Digg Silverlight application code (which runs in the browser) to build a Windows desktop application version (which runs outside the browser).

Step 1: Create a New WPF Desktop Application

We'll start by creating a new WPF Desktop Application using VS 2008.  We'll name it "DiggDesktopSample":

This will create a project inside VS with two files - an App.xaml, and a Window.xaml:

Note that this project structure is very similar to the Silverlight one we created in Tutorial 1 of this series (which had an App.xaml and a Page.xaml file).

Step 2) Copy Existing Digg Application Code into the WPF Application

We'll copy/paste our existing DiggApplication Silverlight code into our new DiggDesktopSample Windows project:

Step 3) Fix Up a Few Issues

I had to make two changes to get our existing Digg sample code to compile:
1) I needed to add a line to the networking code to manually specify a "user-agent" header.  The Digg REST API will return an error if no user-agent header is sent - and by default Windows applications don't send this if you use the WebClient API (whereas browser hosted applications will send it). 
2) I had to change the <HyperlinkButton> control in our user control to be a <TextBlock>.  This is a new control in Silverlight 2 that isn't in the full WPF yet (we will add them in the future though).  I did not have to change any of the code that worked with these controls, though, nor my network, LINQ to XML, nor databinding code.
Once I made these small tweaks the project compiled clean.

Step 4) Hosting the Digg Application in a Desktop Window

I then opened up the Windows1.xaml file in the desktop project (which is the default Window that loads when the application starts). 
I updated the Window title to be "Digg Desktop Version" and expanded the default Width and Height of the Window.
I then added our Page.xaml user control from our previous Digg Silverlight project as the root control in the Window.  This will cause it to be visible and loaded when the Window loads.  I did not have to change the Page class code or rename anything in it.  Because it derives from UserControl it can be hosted just fine inside any WPF Window or Control. 

Step 5) Running the Application

I was then able to run our new Digg Desktop Application. All functionality worked the same as the Silverlight version and the application behaved the same:

And when a story is selected from the list, the details user control is displayed:

There are a few cosmetic styling differences between the browser and desktop version.  This is primarily because WPF by default inherits default styles (fonts, colors, scroll bars, etc) based on the operating system theme a user currently has selected, whereas Silverlight has a default theme that we use for all operating systems.  If we wanted to make the desktop version and the browser version absolutely identical we could do so by being more explicit with our Styles and Control Templates - otherwise the desktop version will adapt slightly based on the user's OS theme.

Summary

We'll be coming out with more detailed notes and suggested best practice guidance for sharing code across Silverlight and WPF projects in the future.  I think you'll find that the skills and knowledge you learn when building Silverlight applications will transfer very well to full WPF projects.  We are also striving for a very high compatibility bar that will enable good code re-use across solutions, and enable controls, content and code to be easily shared and leveraged.

No comments:

Post a Comment