Saturday, July 31, 2010

Silverlight 4.0 Tutorial (5 of N): More Blend!

Read Previous Posts: Part1, Part2, Part3, Part4

Continuing our series for building a registration booth application using Silverlight 4.0, in this post we will continue working with Expression Blend

- Open Home.xaml, select the LayoutRoot grid in the Objects and Timeline window, set the Width and Height properties to Auto so that the grid takes all the screen size.

Set to Auto

- Now we will change the background color of the page, select the LayoutRoot grid, go to the Background property and select the Gradient Brush option.

Gradient Brush Editor

- The Gradient brush we will use will have three gradient stops, you can click on the gradient bar (below the color mixer) to add a gradient stop at the specified offset, to remove a gradient stop click, hold, and drag your mouse off the gradient bar. for each gradient stop you can specify the color (RGB, Alpha).

Creating Gradient Brush

- The xaml for the grid background will be as follows.

  1. <Grid.Background>
  2.        <LinearGradientBrush EndPoint="0.5,0.998" StartPoint="0.5,0">
  3.             <GradientStop Color="#FF052744" Offset="0"/>
  4.             <GradientStop Color="#FF052744" Offset="0.02"/>
  5.             <GradientStop Color="#7F256FB7" Offset="1"/>
  6.        </LinearGradientBrush>
  7.     </Grid.Background>

- We will do another change, we will change the font of the TextBlocks, to do this add a TextBlock to the Home.xaml page, right click the TextBlock, select  Edit Style-> Create Empty a dialog box will appear to allow you to change the properties of the new style resource, Choosing the Apply to all means that this style will be applied to all the text blocks (this is a new feature in Silverlight called implicit styles).

Create Style

- change the font family to Freestyle script and the font size to 30pt.

Font

- Run the application, you will see that the TextBlocks inside the ListBox didn’t change, this is due to a limitation design issue related to the DateTemplate (more details in this post).

- To resolve this we need to define the implicit style inside the data template, so move the style definition from the Page.Resources to the Grid.Resources section inside the data template.

DateTemplate Implicit Style

- We need also to change the style of the labels inside the DataForm, the DataField control that we use inside the DataForm NewItemTemplate renders a Label control for the display name not a TextBlock so we will add a style with the same properties that we used in the previous style but we will set the TargetType property to the value “dataInput:Label” which represent the Label control defined in the System.Windows.Controls.Data.Input assembly (don’t forget to add xml prefix “dataInput” in the page tag), the NewItemTemplate definition should be as follows.

NewItemTemplate Style

That’s it for this post, see you in the next post

You can download the code from Here

Monday, July 26, 2010

Silverlight 4.0 Implicit Styles: Not so implicit after all!

Silverlight 4.0 allows you to define implicit styles that are applied automatically to all controls of the type specified in the Style TargetType property, you can create an implicit style by creating a Style without any key, for example here is an implicit style for the TextBlock control

  1. <Style TargetType="TextBlock">
  2.                         <Setter Property="FontFamily" Value="Freestyle Script" />
  3.                         <Setter Property="FontSize" Value="30" />
  4.                     </Style>

This style will be applied to all the TextBlocks that don’t define an explicit style, excellent.. right?, unfortunately this will not work for the controls defined inside a Template, for example if you have a  DataTemplate that contains a TextBlock, the TextBlock will not pick the implicit style, Microsoft explains the behaviour

“Templates are viewed as an encapsulation boundary when looking up an implicit style for an element which is not a subtype of Control.”

This explains why this doesn’t work for the TextBlocks (not a subtype of Control), the same settings will work for the Button control because it is a subtype of Control.

The solution is to use explicit styles or move the implicit style to be inside the template.

For more details please refer to the following links

http://www.11011.net/archives/000692.html

http://www.sharpfellows.com/post/Automated-Tool-Tip-for-TextBlocks-in-DataTemplates.aspx

Sunday, July 18, 2010

Silverlight 4.0 Tutorial (4 of N): ListBox ItemTemplate

Read Part 1

Read Part 2

Read Part 3

Continuing our RegistrationBooth Application, if you take a look at what we ended with you will find that the attendees data grid that we have is not user friendly, our application should be more attractive to the users, so we will get rid of the data grid and we will use a List box that has a custom template for displaying attendees, in this post we will start using Blend to assist in designing the application UI

- Right click the xaml file in Visual Studio and choose Open in Expression Blend, this will open your solution in Blend.

Open in Epression Blend

- Delete the attendeeDataGrid from the Home.xaml file.

- We will change the layout of the form we will use a grid with two columns the width of the first one is about third of the screen width and we will put the list box in this column, and the second column take the remaining screen width and this is where we will put the attendees data form.

- To do this, go to the Objects and Timeline window (if not visible go to Menu Window->Objects and Timeline), select the LayoutRoot Grid, the grid will be highlighted on the design surface (or the art board in the designers lingo), you can see also a small grid icon located in the top left corner of the grid, and you can see two semi-transparent lines one horizontal and one vertical, if you move the mouse over any of these lines, a line will be drawn across the grid and once you click.. a row/column definition will be added to the grid, so you can easily add columns/rows using this way. so we will add a column to this grid, the column should take about the third of the grid width.

Grid Rows and Columns

- We will move the StackPanel that contains the new attendee data form (dfAttendee) and the button to the right column, to do this select the StackPanel in the Objects and Timeline window, go to the properties window and find the Column property under the Layout category and set it to 1, this will move the StackPanel to the right column.

To easily find properties in the properties window you can use the  search textbox in that window which allows you to type any text and the properties window will be filtered to show the properties that start with the text you wrote in the search text box

Properties window search textbox

- To add a ListBox, expand the assets window by clicking on the right arrow located in the bottom of the tools window, drag and drop a ListBox control into the left hand side column

Assets Window

- Select the ListBox, by pressing the “V” key or selecting the Selection tool from the tools window.

5

- From the tools window, set the Name of the ListBox to be lstAttendees, then right click the ListBox Auto Size->Fill to make the ListBox fills the whole column area.

Change Size to Fill

- To add the data binding, right click the ListBox, select “Data bind ItemsSource to Data…”, the data binding dialog will appear, go to the Element Property tab, select the attendeeDomainDataSource from the scene elements list on the left, and select the Data property from the Properties list on the right.

Create Data Binding

- If you run the application now, you will find the items in the ListBox is rendered as string that contains the name of the class and the ID, this is because we didn’t specify to the ListBox how we want to render the ListBox Item, to do this we need to set the ItemTemplate property.

- To do this, right click the ListBox Edit Additional Template –> Edit Generated Items (ItemTemplate)-> Create Empty

Edit ListBox ItemTemplate

- In the Create DataTemplate Resource Dialog, name it attendeeDateTemplate, once you click Ok, Blend will allow you to design the DataTemplate which will determine the UI used to display the Attendee object.

Create DataTemplate Resource

- A new empty design surface will be shown that represents the DataTemplate, initially there’s only empty grid as you can in the Objects and Timeline window.

Empty DataTemplate

You can go back to the page design by clicking on the Up arrow in the Objects and Timeline window or by clicking on the ListBox name (lstAttendees) in the breadcrumb menu at top of the design surface

Editing the DataTemplate

- We will create a simple UI for the attendee, where we'll display the image, last name, first name and company, the xaml for the DataTemplate is shown below

  1. <DataTemplate x:Key="attendeeDataTemplate">
  2.         <Grid d:DesignWidth="320" d:DesignHeight="112">
  3.             <Grid.RowDefinitions>
  4.                 <RowDefinition Height="0.3*"/>
  5.                 <RowDefinition Height="0.3*"/>
  6.                 <RowDefinition Height="0.3*"/>
  7.             </Grid.RowDefinitions>
  8.             <Grid.ColumnDefinitions>
  9.                 <ColumnDefinition Width="0.35*"/>
  10.                 <ColumnDefinition Width="0.65*"/>
  11.             </Grid.ColumnDefinitions>
  12.             <Image Source="{Binding Path=Photo,Mode=TwoWay, Converter={StaticResource imageConverter}}" Grid.Column="0" Grid.RowSpan="3" MaxWidth="110" MaxHeight="110" />
  13.             <TextBlock Grid.Column="1" Grid.Row="1" TextWrapping="Wrap" Margin="0" HorizontalAlignment="Center" Text="{Binding Company}" />
  14.             <TextBlock Grid.Column="1" Grid.Row="2" TextWrapping="Wrap" HorizontalAlignment="Center" Text="{Binding Email}" />
  15.             <StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Center">
  16.                 <TextBlock Margin="0,8" TextWrapping="Wrap" Text="{Binding FirstName}" />
  17.                 <TextBlock Margin="0,8,0,16" TextWrapping="Wrap" Text=" "/>
  18.                 <TextBlock Margin="0,8" TextWrapping="Wrap" Text="{Binding LastName}" />
  19.             </StackPanel>
  20.         </Grid>
  21.     </DataTemplate>

- Note how the text blocks are bound to the different properties of the Attendee class, and the Image control is bound to the Photo property using the ImageConverter we used before.

-  Run the application and see how the list box looks like.

The ListBox in action

See you in the next post :)

You can download the code from Here

Tuesday, July 06, 2010

Silverlight 4.0 Tutorial (3 of N): Working with the DataForm Control

Read Part 1

Read Part 2

Continuing our RegistrationBooth Application, in the previous post we ended having a data grid that displays all the attendees, and a user control to add a new attendee.

The user control for adding a new attendee didn’t contain any validation logic, of course this is a bad practice, in this post we will add the necessary validation when creating adding new attendees.

- First we will need to define our validation logic for the Attendee entity, if you remember our first post when we created the Domain Service class we choose to create metadata classes for our entities, you can read more about metadata classes in the RIA services documentation (How To Add Metadata Classes).

3

- In the RegistrationBooth.Web project the file RegistrationDomainService.metadata.cs contains the metadata classes, we will change the AttendeeMetadata class to add some validation attributes to the different Attendee properties, you can read more about validation attributes at How To Validate Data, the final AttendeeMetadata should be as follows

  1. internal sealed class AttendeeMetadata
  2. {
  3. private AttendeeMetadata()
  4. {
  5. }
  6. [Required(ErrorMessage = "You must enter company name", AllowEmptyStrings = false)]
  7. [Display( Name="Company Name",Description="Name of your company")]
  8. public string Company { get; set; }
  9. public DateTime CreationDate { get; set; }
  10. [Required(ErrorMessage = "You must enter email", AllowEmptyStrings = false)]
  11. [Display( Name = "Email",Description="your Email Address")]
  12. [RegularExpression(@"\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*", ErrorMessage="Invalid email format")]
  13. public string Email { get; set; }
  14. [Required(ErrorMessage="You must enter first name",AllowEmptyStrings=false)]
  15. [Display( Name = "First Name",Description="Your First Name")]
  16. public string FirstName { get; set; }
  17. public int Id { get; set; }
  18. [Required(ErrorMessage = "You must enter last name", AllowEmptyStrings = false)]
  19. [Display( Name = "Last Name",Description="Your Last Name")]
  20. public string LastName { get; set; }
  21. public byte[] Photo { get; set; }
  22. public EntityCollection<SessionAttendee> SessionAttendees { get; set; }
  23. }

- We added RequriedAttribute for the required properties and specified the error messages that should appear if a required property is missing, the DisplayAttribute is used to provide the name (that is used as the label of the corresponding control) and the description (which appears as a ToolTip against the corresponding control), we also used a RegularExpressionAttribute for the email property to make sure that the users enter only valid email addresses.

- These metadata classes are automatically copied to the Silverlight project and compiled as part of the Silverlight application, to see this go to the RegistrationBooth project and show all the files, you will find a folder called Generated_Code, inside this folder you will find a file RegistrationBooth.Web.g.cs that contains all the classes defined on the server and shared by the client, this sharing of code allows the validation that we specified by the attributes to run directly on the client side.

- Now our entity class has the necessary metadata required for validation, let’s see how we will make use of this in creating our UI.

- We will use the DataForm control, the DataForm control can read the data annotations specified on the object class that this control is bound to.

- So to start open Home.xaml and delete the user control and the Save button, also delete the btnSave_Click method.

- From the toolbox drag and drop a DataForm control on the page, name it dfAttendee.

- Set the ItemsSource property to a binding to the element attendeeDomainDataSource and the Path is the Data property

DataForm ItemsSource Binding

- Because we will use the DataForm only for adding new items, we need to show only the save button, we can control what buttons (Add, Edit, Delete, Navigation, etc) should appear on the DataForm using the CommandButtonsVisibility property, so we will set this property to Commit.

- To make the DataForm open in “AddNew” mode, write the following line of code in the Page_Loaded event handler

  1. private void Page_Loaded(object sender, System.Windows.RoutedEventArgs e)
  2. {
  3. dataForm1.AddNewItem();
  4. }

- The DataForm control can automatically create a UI based on the properties of the entity that it’s bound too, but we will not use this default template because for the Photo property of the Attendee class we need to make use of the Web Cam to capture the image, so we will create a custom template for Adding Items, in the DataForm control you can specify different template for each mode so you can have a template for the view, template for the edit and a template for adding new item, in our case we need only the new item template.

- We will copy the same controls we had in the RegisterAttendee user control, we will bound the text boxes to the corresponding attendee properties, we will put each text box inside a DataField.

- The complete markup of the DataForm is shown below

  1. <toolkit:DataForm x:Name="dfAttendee" CommandButtonsVisibility="Commit"
  2. ItemsSource="{Binding ElementName=attendeeDomainDataSource, Path=Data}"
  3. Height="273" Width="545" >
  4. <toolkit:DataForm.NewItemTemplate>
  5. <DataTemplate>
  6. <Grid>
  7. <Grid.RowDefinitions>
  8. <RowDefinition Height="10*" />
  9. <RowDefinition Height="10*" />
  10. <RowDefinition Height="10*" />
  11. <RowDefinition Height="10*" />
  12. <RowDefinition Height="25*"/>
  13. </Grid.RowDefinitions>
  14. <toolkit:DataField Grid.Row="0" >
  15. <TextBox Height="23" HorizontalAlignment="Left" Margin="0,12,0,0" Text="{Binding FirstName,Mode=TwoWay}"
  16. VerticalAlignment="Top" Width="120" />
  17. </toolkit:DataField>
  18. <toolkit:DataField Grid.Row="1" >
  19. <TextBox Height="23" HorizontalAlignment="Left" Margin="0,12,0,0" Text="{Binding LastName,Mode=TwoWay}"
  20. VerticalAlignment="Top" Width="120" />
  21. </toolkit:DataField>
  22. <toolkit:DataField Grid.Row="2" >
  23. <TextBox Height="23" HorizontalAlignment="Left" Margin="0,12,0,0" Text="{Binding Email,Mode=TwoWay}"
  24. VerticalAlignment="Top" Width="120" />
  25. </toolkit:DataField>
  26. <toolkit:DataField Grid.Row="3" >
  27. <TextBox Height="23" HorizontalAlignment="Left" Margin="0,12,0,0" Text="{Binding Company,Mode=TwoWay}"
  28. VerticalAlignment="Top" Width="120" />
  29. </toolkit:DataField>
  30. <toolkit:DataField x:Name="dfPhoto" Grid.Row="4" >
  31. <Grid>
  32. <Grid.ColumnDefinitions>
  33. <ColumnDefinition Width="20*" />
  34. <ColumnDefinition Width="10*" />
  35. <ColumnDefinition Width="20*" />
  36. </Grid.ColumnDefinitions>
  37. <Rectangle x:Name="videoStream" Grid.Column="0" ></Rectangle>
  38. <ToggleButton Grid.Column="1" Height="30" Margin="0,41,0,62" Name="btnStart" Click="btnStart_Click">Start</ToggleButton>
  39. <Button Grid.Column="1" Height="30" Margin="0,77,0,26" Name="btnCapture" Click="btnCapture_Click">Capture</Button>
  40. <Image x:Name="capturedImage" Source="{Binding Path=Photo,Mode=TwoWay, Converter={StaticResource imageConverter}}" Grid.Column="2" ></Image>
  41. </Grid>
  42. </toolkit:DataField>
  43. </Grid>
  44. </DataTemplate>
  45. </toolkit:DataForm.NewItemTemplate>
  46. </toolkit:DataForm>

- if you examine the Image control at the end of the template you can see that this control is bound to the Photo property and it uses the ImageConverter class to do the conversion, we introduced the ImageConverter class in the first post and we implemented the Convert method that converts the Photo property which is a byte array to a bitmap that can be displayed in the Image control, now when we are adding a new item there will be already an image in the Image control (that we will capture using the web cam) so now we need to convert the bitmap from the Image control to a byte array that will be assigned to the Photo property, so we need to implement the second method of the Converter which is the ConvertBack method

  1. public object ConvertBack(object value, Type targetType, object parameter,
  2. System.Globalization.CultureInfo culture)
  3. {
  4. var bitmap = value as WriteableBitmap;
  5. var img = bitmap.ToImage();
  6. byte[] ba;
  7. var encoder = new PngEncoder();
  8. MemoryStream stream = new MemoryStream();
  9. encoder.Encode(img, stream);
  10. ba = stream.ToArray();
  11. stream.Close();
  12. return ba;
  13. }

- Now we need to write the event handlers for the two buttons (btnStart, btnCapture) which are inside the new item template, the code is shown below.

  1. private void btnStart_Click(object sender, RoutedEventArgs e)
  2. {
  3. ToggleButton btnStart = sender as ToggleButton;
  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 CaptureSource cs = null;
  16. private void btnCapture_Click(object sender, RoutedEventArgs e)
  17. {
  18. cs.CaptureImageAsync();
  19. }
  20. private void StartWebcam()
  21. {
  22. if (CaptureDeviceConfiguration.AllowedDeviceAccess
  23. CaptureDeviceConfiguration.RequestDeviceAccess())
  24. {
  25. VideoCaptureDevice vcd = CaptureDeviceConfiguration.GetDefaultVideoCaptureDevice();
  26. if (null != vcd)
  27. {
  28. cs = new CaptureSource();
  29. cs.VideoCaptureDevice = vcd;
  30. cs.Start();
  31. VideoBrush videoBrush = new VideoBrush();
  32. videoBrush.Stretch = Stretch.UniformToFill;
  33. videoBrush.SetSource(cs);
  34. Rectangle videoStream = dfAttendee.FindNameInContent("videoStream") as Rectangle;
  35. videoStream.Fill = videoBrush;
  36. cs.CaptureImageCompleted += new EventHandler<CaptureImageCompletedEventArgs>(cs_CaptureImageCompleted);
  37. }
  38. else
  39. {
  40. MessageBox.Show("Error initializing Webcam");
  41. }
  42. }
  43. }
  44. private void StopWebcam()
  45. {
  46. if (null != cs)
  47. {
  48. cs.Stop();
  49. }
  50. }
  51. void cs_CaptureImageCompleted(object sender, CaptureImageCompletedEventArgs e)
  52. {
  53. Image capturedImage = dfAttendee.FindNameInContent("capturedImage") as Image;
  54. capturedImage.Source = e.Result;
  55. }

- The code is almost identical to the code that we had inside the user control, except for the way we access the controls inside the template, for example the rectangle that displays the live stream of the web cam (videoStream) to get a reference to this rectangle we use the DataForm method FindNameInContent.

- As we mentioned previously to push the changes done locally to the server we need to call the domain data source SubmitChanges method, so whenever we click the Ok button in the DataForm we will call the SubmitChanges method to immediately submit the new attendee to the server, we can do this by adding an event handler to the DataForm EditEnded event, as shown below

  1. private void dfAttendee_EditEnded(object sender, DataFormEditEndedEventArgs e)
  2. {
  3. if (e.EditAction == DataFormEditAction.Commit)
  4. {
  5. attendeeDomainDataSource.SubmitChanges();
  6. }
  7. }

- Run the application and examine the labels of the controls and the tooltip that shows the description of each property.

Automatic Description Tooltip

- Try to save without entering any data to see the client side validation in action.

Automatic Validation

- The only thing missing is printing the name tag for the attendee, to do this add event handler for the domain data source SubmittedChanges event, this event is called after the SubmitChanges method is executed at the server, in this event handler we check to see if the attendee was successfully saved then we print the name tag, the code is shown below

  1. Attendee newAttendee;
  2. private void attendeeDomainDataSource_SubmittedChanges(object sender, SubmittedChangesEventArgs e)
  3. {
  4. if (e.ChangeSet.AddedEntities.Count > 0 && e.HasError ==false)
  5. {
  6. newAttendee = e.ChangeSet.AddedEntities[0] as Attendee;
  7. if (newAttendee != null)
  8. {
  9. PrintDocument document = new PrintDocument();
  10. document.PrintPage += new System.EventHandler<PrintPageEventArgs>(document_PrintPage);
  11. document.Print("Attendee_" + newAttendee.Id);
  12. }
  13. }
  14. }

- Unfortunately this code will not work, you will get Exception “Dialogs must be user-initiated” this is a Silverlight 4 built-in security measure (read more here), to resolve this issue we will change our code to show a button once the attendee is saved and we will move the printing code to this button Click event handler, the modified code is shown below

  1. <Button Content="Print Name Tag" Width="200"
  2. Click="btnPrintNameTag_Click" x:Name="btnPrintNameTag"
  3. Visibility="Collapsed" ></Button>

  1. Attendee newAttendee;
  2. private void attendeeDomainDataSource_SubmittedChanges(object sender, SubmittedChangesEventArgs e)
  3. {
  4. if (e.ChangeSet.AddedEntities.Count > 0 && e.HasError ==false)
  5. {
  6. newAttendee = e.ChangeSet.AddedEntities[0] as Attendee;
  7. if (newAttendee != null)
  8. {
  9. btnPrintNameTag.Visibility = Visibility.Visible;
  10. }
  11. }
  12. }
  13. private void btnPrintNameTag_Click(object sender, RoutedEventArgs e)
  14. {
  15. PrintDocument document = new PrintDocument();
  16. document.PrintPage += new System.EventHandler<PrintPageEventArgs>(document_PrintPage);
  17. document.Print("Attendee_" + newAttendee.Id);
  18. btnPrintNameTag.Visibility = Visibility.Collapsed;
  19. }

That’s all for this post, next time we will continue :).

You can download the code from here.