Wednesday, June 30, 2010

Silverlight 4.0 Tutorial (2 of N)

Read Part 1 here

Continuing our RegistrationBooth Application, in the previous post we ended having a data grid that displays all the attendees, let’s now implement the interface for registering a new attendee

- We will create a user control for registering a new attendee, right click the Views folder in the RegistrationBooth project, select add new item, choose Silverlight User Control and name it RegisterAttendee.

- Use the VS designer to add two columns for the LayoutRoot grid, one for the labels of the controls and the other for the controls, create also five rows for the different properties.

- Add the four labels and four text boxes to the first four rows of the grid, these controls will be used for the FirstName, LastName, Email and company properties.

- The fifth row will be for the photo, will use the Silverlight 4.0 new feature that allows us to access the webcam, so add a “Photo” label in the first column of this row, and in the second column add a Grid with three columns, put a rectangle in the first column, the second column should contain a ToggleButton (Start) and Capture button, and put image control in the third column.

- What we will do is that:

1- When the user clicks the Start button, we will start displaying the video stream coming from the default web cam attached and display this video stream in the left rectangle.

2- When the users clicks the Capture button we will take a snapshot of the web cam video stream and display it in the image control to use it as a picture of the attendee

- The final UI should look like the following

Register Attendee UI

- Add events handlers for the Start and Capture buttons click events

- Modify the code to be as follows

  1. private CaptureSource cs = null;
  2. private void btnStart_Click(object sender, RoutedEventArgs e)
  3. {
  4. if (btnStart.IsChecked.Value)
  5. {
  6. StartWebcam();
  7. btnStart.Content = "Stop";
  8. }
  9. else
  10. {
  11. StopWebcam();
  12. btnStart.Content = "Start";
  13. }
  14. }
  15. private void btnCapture_Click(object sender, RoutedEventArgs e)
  16. {
  17. cs.CaptureImageAsync();
  18. }
  19. private void StartWebcam()
  20. {
  21. if (CaptureDeviceConfiguration.AllowedDeviceAccess
  22. CaptureDeviceConfiguration.RequestDeviceAccess())
  23. {
  24. VideoCaptureDevice vcd = CaptureDeviceConfiguration.GetDefaultVideoCaptureDevice();
  25. if (null != vcd)
  26. {
  27. cs = new CaptureSource();
  28. cs.VideoCaptureDevice = vcd;
  29. cs.Start();
  30. VideoBrush videoBrush = new VideoBrush();
  31. videoBrush.Stretch = Stretch.UniformToFill;
  32. videoBrush.SetSource(cs);
  33. videoStream.Fill = videoBrush;
  34. cs.CaptureImageCompleted += new EventHandler<CaptureImageCompletedEventArgs>(cs_CaptureImageCompleted);
  35. }
  36. else
  37. {
  38. MessageBox.Show("Error initializing Webcam");
  39. }
  40. }
  41. }
  42. private void StopWebcam()
  43. {
  44. if (null != cs)
  45. {
  46. cs.Stop();
  47. }
  48. }
  49. void cs_CaptureImageCompleted(object sender, CaptureImageCompletedEventArgs e)
  50. {
  51. capturedImage.Source = e.Result;
  52. }

- The code is pretty easy,first we request access to the device this will prompt the user requesting his approval for the webcam, after that we initialize a new CaptureSource object and set its VideoCaptureDevice to the default device, then we create a new VideoBrush that uses the capture source we created, then we use this brush to paint the rectangle, we also attach an event handler to the CaptureImageCompleted event so that we set the our image control Source to the image that we will capture (when we click the capture button).

- Include the user control in the Home.xaml page, put it below the data grid.

- Build and run the application, you will be able to start/stop the web cam and capture an image to be displayed in the image control.

Capturing Webcam

- Let’s start writing the code that will actually saves the attendee to the database, add the following code to the RegisterAttendee user control, this code define some properties that retrieves the values of the controls on the user control.

  1. public string FirstName { get { return txtFirstName.Text; } }
  2. public string LastName { get { return txtLastName.Text; } }
  3. public string Company { get { return txtCompany.Text; } }
  4. public string Email { get { return txtEmail.Text; } }
  5. public byte[] Photo
  6. {
  7. get
  8. {
  9. if (capturedImage.Source == null)
  10. return null;
  11. var bitmap = capturedImage.Source as WriteableBitmap;
  12. var img = bitmap.ToImage();
  13. byte[] ba;
  14. var encoder = new PngEncoder();
  15. MemoryStream stream = new MemoryStream();
  16. encoder.Encode(img, stream);
  17. ba = stream.ToArray();
  18. stream.Close();
  19. return ba;
  20. }
  21. }

- the Photo property make use of the ImageTools for Silverlight library, so make sure to download these dlls and reference them in the project.

- Back to the Home.xaml page, add a button Save below the user control.

- Double click the Save button and add the following code to the Click event handler

  1. private void btnSave_Click(object sender, System.Windows.RoutedEventArgs e)
  2. {
  3. Attendee newAttendee = new Attendee();
  4. newAttendee.FirstName = ucNewAttendee.FirstName;
  5. newAttendee.LastName = ucNewAttendee.LastName;
  6. newAttendee.Company = ucNewAttendee.Company;
  7. newAttendee.Email = ucNewAttendee.Email;
  8. newAttendee.Photo = ucNewAttendee.Photo;
  9. newAttendee.CreationDate = DateTime.Now;
  10. (attendeeDomainDataSource.Data as DomainDataSourceView).Add(newAttendee);
  11. attendeeDomainDataSource.SubmitChanges();
  12. }

- Build and run the application, try to register a new attendee with a photo, click the Save button, the attendee should be saved and added to the data grid.

- Another thing we need to do is to print a name tag for the newly registered attendee, we will use the new Silverlight 4.0 printing features.

- What we have to do is to specify the UI that will be printed, here we will add a new user control to the Views folder, will call it PrintNameTag, in this user control we will display the Name of the user, the company and a barcode that we will be generated based on the attendee id. the markup of the user control is shown below

  1. <Grid x:Name="LayoutRoot" Background="White">
  2. <Grid.RowDefinitions>
  3. <RowDefinition Height="60*" />
  4. <RowDefinition Height="60*" />
  5. <RowDefinition Height="120*" />
  6. </Grid.RowDefinitions>
  7. <StackPanel Grid.Row="0" Orientation="Horizontal" d:LayoutOverrides="Height">
  8. <TextBlock FontSize="24" Text="{Binding Name}" FontWeight="Bold" />
  9. </StackPanel>
  10. <TextBlock Grid.Row="1" FontSize="24" TextAlignment="Center" Text="{Binding Company}" FontWeight="Bold"/>
  11. <TextBlock Grid.Row="2" Text="{Binding Barcode}" FontSize="96" FontFamily="Free 3 of 9" TextAlignment="Center"/>
  12. </Grid>

- As you can see the TextBlocks are bounded to the attendee’s different properties, the barcode is generated by using the font “Free 3 of 9” which can be downloaded for free, this font will automatically render the barcode stripes, you better use Expression Blend to change the TextBlock font family to use “Free 3 of 9”, this way Blend will include the necessary build actions that pack the font in the xap file.

- We are now done with the UI that will be printed, to invoke the actual printing, go back to Home.xaml, change the btnSave_Click event handler as follows

  1. Attendee newAttendee;
  2. private void btnSave_Click(object sender, System.Windows.RoutedEventArgs e)
  3. {
  4. newAttendee = new Attendee();
  5. newAttendee.FirstName = ucNewAttendee.FirstName;
  6. newAttendee.LastName = ucNewAttendee.LastName;
  7. newAttendee.Company = ucNewAttendee.Company;
  8. newAttendee.Email = ucNewAttendee.Email;
  9. newAttendee.Photo = ucNewAttendee.Photo;
  10. newAttendee.CreationDate = DateTime.Now;
  11. (attendeeDomainDataSource.Data as DomainDataSourceView).Add(newAttendee);
  12. attendeeDomainDataSource.SubmitChanges();
  13. PrintDocument document = new PrintDocument();
  14. document.PrintPage += new System.EventHandler<PrintPageEventArgs>(document_PrintPage);
  15. document.Print("Attendee_" + newAttendee.Id);
  16. }
  17. void document_PrintPage(object sender, PrintPageEventArgs e)
  18. {
  19. PrintDocument document = sender as PrintDocument;
  20. e.PageVisual = new PrintNameTag() {
  21. DataContext = new {
  22. Name =newAttendee.FirstName+" "+newAttendee.LastName,
  23. Company=newAttendee.Company,
  24. Barcode = "ATT" + newAttendee.Id.ToString("00000")
  25. }
  26. };
  27. }

- First we create an instance of the PrintDocument class which represents a document to be sent to the printer, we attach a handler to the event PrintPage.

- The PrintPage is called for each page that is going to be printed, in the handler we create an instance of the user control (which is the UI that will be printed) and set its DataContext to an anonymous object that contains the needed properties (refer to this post to see how to enable binding to anonymous types in Silverlight), we assign the created user control to the property PageVisual to indicate that this user control will be the source of the print UI.

- Build and run the application, register a new attendee, you should get the print dialog to choose the printer, you can use the Microsoft XPS Document Writer for testing

The name tag

in the next post we will continue building our application

Download the application from here

Sunday, June 27, 2010

Silverlight 4.0 Tutorial (1 of N)

I will start writing a set of posts that can be used as a tutorial  for using Visual Studio 2010 and Blend 4.0 to build Silverlight 4.0 applications.

so what is the application we are going to build??

Problem Statement

as part of our community activities we hold many offline events, at the event we need to gather the attendees information(sort of onsite registration), give them feedback forms so that they can evaluate the sessions and also use these forms to select winners for the raffle draw.

so i tried to create a simple application that we will host on a PC/Laptop at the event venue, we will call it the RegistrationBooth Application.

Solution 

the RegistrationBooth Application will:

- Allows the user to register his/her information through a simple easy interface.

- Automatically print a name tag for the user once registered.

- displays a list of all the registered users.

- displays the event agenda.

- allows the users to easily evaluate the sessions they attended.

- allows the user to print attendance certificate.

Building the Application

- So let’s create a new Silverlight project using the “Silverlight Business Application” template, let’s call it RegistrationBooth.

- The first thing to do is to create our database, right click the App_Data folder in the RegistrationBooth.Web project and add new item of type SQL Server Database, name it RegistrationBoothDB.

- The database will contain tables for attendees, speakers, sessions and the intermediate tables that represent the n-n relationships between the different tables, the database diagram should be as follows

Database

- After creating the database we should create our data access layer, we will use ADO.NET Entity Framework for that, so let’s go to our RegistrationBooth.Web project and add a new item of type ADO.NET Entity Data Model and name it RegistrationsModel.

- Walk through the wizard to select the database we created earlier and select to include all the tables in the database, use RegistrationBoothDBModel for the model name, the model should look like the following:

ADO.NET Entity Model

- We now have a way to access our database (through the ADO.NET Entity Model) but we can use this only on the server side,  to be able to work with our database entities on the client side (Silverlight) we have to expose our ADO.NET Entity Model using WCF RIA Services, RIA Services is a special type of WCF Services that simplifies the development of n-tier Rich Internet Applications. 

- In Silverlight you can create a Domain Service class which is a RIA service that exposes CRUD operations over your domain model.

- So add a new Domain Service class to the RegistrationBooth.Web project, name it RegistrationDomainService, the wizard will allow you to choose the DataContext/ObjectContext class, in our case we will select the DataContext created by our ADO.NET Entity Model (you should see it in the drop down list once you built the application)

- You will see a list of the data entities exposed through our data model, select all the entities and enable editing for all of them.

New Domain Service Class

- Examine the RegistrationDomainService.cs file, you will find that there are four methods (Get, Insert, Update, Delete) generated for each entity in the domain model.

RegistrationDomainService.cs

- At this point we have a database, data access layer (ADO.NET Entity Model) and a Domain Service class that exposes our model to the Silverlight client

- Now let’s move to the client side and start to create our Silverlight application.

- The first thing we want to display on our home page is a list of the attendees, we can do this easily using VS2010, open the Data Sources windows (from the menu Data->Show Data Sources).

- You will see the RegistrationDomainContext which represents the RegistrationDomainService we added earlier, as you can see inside RegistrationDomainContext  you have all the different entities exposed by the domain service, and below each entity you can see the properties of this entity

Data Sources Window 

- One important thing to take care of is the small down arrow beside each entity 6 , when you click this arrow you get a menu that allows you to:

1. choose how to render this entity on the Page (DataGrid or DetailsView)

2. Choose the query (or the Domain Service method) that you will use to retrieve this entity

- Now to display our attendees click the down arrow, make sure that DataGrid is selected, we have only one option for the query (GetAttendeesQuery) cause we only have one method in the Domain Service (GetAttendees) that returns Attendee entities.

- Click the Down arrow beside the CreationDate property and select TextBox to make sure that this property is rendered into a DataGridTextColumn.

- Click the Down arrow beside the Photo property and select Image.

- Drag and Drop the Attendee entity on the Home.xaml page (located in the Views folder), VS will create a DataGrid that contains columns for all the attributes of the Attendee.

- From the properties window, go to the Columns property of the DataGrid and click the 3 dots button to open the Columns Editor dialog.

- Re-order the columns (to be in he same order as shown below), and also increase the widths of the columns.

Collection Editor: Columns

- We need to change the format of the CreationDate column, so select the column and go to the Binding property, expand the Options panel and select the appropriate string format.

Binding String Fomat

- Run the application now, you will find the rows are displayed (supposing that you had populated the database manually), but the photos are not displayed, if you examine the xaml code for the Photo column you will find that this template column contains Image control whose Source property is bound directly to the Photo attribute, there’s no direct conversion from byte arrays to bitmaps or images in Silverlight, so we need to write a converter to do this

- A converter is a class that implements the IValueConverter interface, so let’s add a new class to the Silverlight project (under the Helpers folder), and call it ImageConverter, the implementation is straightforward (as shown below)

ImageConverter

- To use this converter, we will instantiate an instance from this class as application resource, so in the App.xaml file inside the Application.Resources property, define an instance of the converter and give it a key name

ImageConverter Resource

- Now back to Home.xaml, change the markup of the Image control inside the Photo column to be as follows

11

- If you run the application now you should be able to see the photos

The Attendees DataGrid

in the next post we will continue building our application

Download the application from here

Saturday, June 26, 2010

Data Binding to Anonymous Types in Silverlight

Trying to bind Silverlight controls to anonymous typed objects will not work, this is because anonymous types are internal, and Silverlight doesn't support reflecting against internal types.

Sources:

http://dotnetslackers.com/Community/blogs/bmains/archive/2009/04/28/silverlight-accordion-doesn-t-like-anonymous-types.aspx

http://blogs.msdn.com/b/mark/archive/2008/11/18/anonymous-types-and-silverlight-databinding.aspx

A solution to this is to expose your internals to the Binding framework as illustrated here: http://stackoverflow.com/questions/2684954/silverlight-4-data-binding-with-anonymous-types