Chapter 20 Taking Silverlight 2 One Step Further

In This Chapter

Creating a Default Template for the MediaInfoDisplay Control

Using the MediaInfoDisplay Control

Scrolling the Items

Creating a New RepeatButton Template

Exporting Classes to an External Assembly and Refactoring

Talking About the Application Object

Using Generics

We worked a lot on our Thumbnails application in Chapter 19, “Creating User Controls and Custom Controls,” and started creating controls to “pack” the functionality, make it easier to manage and maintain, modify the look and feel, and so on. In this chapter, we continue this task. This will not take the whole chapter, however, because Silverlight, Blend, and Visual Studio enable us to work in an efficient and comfortable way. In the remaining time, we begin a discussion of advanced Silverlight and .NET topics.

Creating a Default Template for the MediaInfoDisplay Control

When a developer wants to use our control in an application, he should see something—even if it’s not the look and feel that he will use later, even if a designer will re-template the control. This is also what happens when you add a Silverlight button to your application: Even though the Button (like all Silverlight controls) is lookless, you can actually see something because a default look and feel is included.

We will do now what the Silverlight framework’s developers and designers also did with the standard controls set, and define the generic look and feel for our MediaInfoDisplay control with the following steps:

1. In Visual Studio, open the Thumbnails application that we last edited in Chapter 19. Right-click on the MediaInfo project and select Add, New Folder. Name this new folder Themes.

2. Right click on the Themes folder, and choose Add, New Item. Select a Text file and enter the name generic.xaml.

The generic file must be a ResourceDictionary. We talked about this specialized collection in Chapter 17, “Using Resources, Styling, and Templating.” Back then, you were told that Silverlight doesn’t handle “loose” ResourceDictionary files yet, and that you always have to add resources in the main XAML file or in App.xaml. Well guess what: It was a lie, or almost. The file generic.xaml is an exception. It can (and should) be a loose file included in the assembly with the controls you want to create default styles for. Because its name and location (the Themes folder) are reserved, the Silverlight framework knows how to handle it. You can implement the default look and feel with the following steps:

1. Enter a ResourceDictionary tag to generic.xaml as in Listing 20.1:

Listing 20.1. Empty ResourceDictionary

<ResourceDictionary
     xmlns=″http://schemas.microsoft.com/winfx/2006/xaml/presentation″
     xmlns:x=″http://schemas.microsoft.com/winfx/2006/xaml″>
</ResourceDictionary>

2. Add a namespace in the ResourceDictionary tag pointing to the MediaInfo namespace in the local assembly:

         xmlns:controls=″clr-namespace:MediaInfo″

3. Then add the default style for the MediaInfoDisplay control inside the ResourceDictionary as shown in Listing 20.2:

Listing 20.2 Default Style for MediaInfoDisplay

  1     <Style TargetType=″controls:MediaInfoDisplay″>
  2          <Setter  Property=″Template″>
  3               <Setter.Value>
  4                    <ControlTemplate TargetType=″controls:MediaInfoDisplay″>
  5                         <Border  BorderBrush=″{TemplateBinding BorderBrush}″
  6                                             BorderThickness=″{TemplateBinding BorderThickness}″
  7                                             Background=″#FFDDDDDD″
  8                                             Padding=″10″ x:Name=″DescriptionPanel″>
  9                              <StackPanel Cursor=″Hand″>
10                                   <TextBlock Text=″{TemplateBinding ShortDescription}″ />
11                                   <TextBlock Text=″{TemplateBinding LongDescription}″
12                                                             TextWrapping=″Wrap″/>
13                              </StackPanel>
14                         </Border>
15                    </ControlTemplate>
16               </Setter.Value>
17          </Setter>
18     </Style>

Image On line 1, we create the new Style, and we set its TargetType. This Style, even though it is included in a ResourceDictionary, does not have a key! Remember before, when we set the property DefaultStyleKey = typeof(MediaInfoDisplay);. This line enables us to specify a generic style by setting the TargetType only. This Style is automatically applied to all MediaInfoDisplay instances (unless of course the designer specifies something different).

Image Lines 4 to 15 specify the template for our control.

Image Note the use of TemplateBinding for the properties BorderBrush and BorderThickness and in the TextBlocks for ShortDescription and LongDescription. This default template is rather simple, and doesn’t specify any transitions. But it allows the developer to position, size, and test the control in an application before a designer creates a new template.

Image We test this default look and feel later in this chapter, when we add the control to our Thumbnails application!

Image To make sure that all changes are visible, build your application in Visual Studio before going further.

Using the MediaInfoDisplay Control

Because we created a default template, we can manipulate the control in its generic look and feel. Later, we wear our designer’s hat and create a template that looks better than the default one. We add smooth transitions between the states. When we do this, we see how we can add test data again, as we did in Chapter 19, to make the work in Blend even easier. Then, we connect the ThumbnailsViewerControl and the MediaInfoDisplay to have a functional application.

Adding Test Data

Adding test data is useful to make the workflow easier in Blend. This is easy with the following steps:

1. Open Page.xaml in Visual Studio.

2. In the UserControl.Resources section, add the instance of the MediaEx class shown in Listing 20.3. This is what we will use as test data for the styling and templating exercise.

Listing 20.3 Default Style for MediaInfoDisplay

<data:MediaEx  x:Key=″TEMPMediaInfo″
                                  Description=″[Your description here]″
                                  d:IsDataSource=″True″ />

3. Enter your own description instead of the [Your description here]. You should make it longer than 30 characters to see whether the “trimming” works.

4. Choose File, Save all from the menu.

Adding the Control to the Scene

The next step is to include and position the control in our application’s page. We will position it so that it takes as little space as possible, but that it can be expanded above the two title TextBlocks if needed, with the following steps:

1. Open Page.xaml in Blend.

2. Select the Grid LayoutRoot’s first row by clicking next to the small lock (reminder: The Grid must be in Grid Layout Mode; to do that, see Figure 5.7). Then set the row’s Height to 380 pixels.

3. Select the MediaInfoDisplay control from the Asset library (use the Custom Controls tab). If you cannot see the control in the library, try rebuilding your solution in Blend first.

4. Add an instance of the MediaInfoDisplay in the same cell as the two title TextBlocks. You should see the default template that we defined before in generic.xaml.

5. To see some text, wire the new control to the test data: Using the Search text box in the Properties panel, find the MediaInfo property. Note that even though this property is a custom DependencyProperty (DP) that we added, it appears in Blend’s Properties panel anyway.

6. Click on the small square next to the property and select Data Binding.

7. From the Create Data Binding dialog, select the TEMPMediaInfo data source and click on Finish. You should now see the text that you just entered.

8. Remember that we data bound the BorderBrush and BorderThickness in generic.xaml, so let’s try and set them to see whether that works. Set the BorderBrush to #FF000B70 and the BorderThickness to 2,2,2,2.

9. Then do a little layout: Set the Width to 340, the Height to 200, the HorizontalAlignment to Left, VerticalAlignment to Top, and the Margin to Left = 30, Top= −65, Right= Bottom= 0. Also make sure that the Row is set to 1 and the Column to 0. The result should look like Figure 20.1.

Figure 20.1 Default look and feel

Image

Creating a Template

Now we create a better template and add some transitions with the following steps:

1. Right-click on the MediaInfoDisplay control and select Edit Control Parts (Template), Create Empty.

2. Name the new template MediaInfoDisplayControlTemplate and place it in This Document.

3. Make sure that the Grid fills the whole space.

4. Inside the Grid, place a Border. Set its Width to Auto and its Height to 50. Set the Margin to 0, reset the HorizontalAlignment. Set the VerticalAlignment to Top. Set the Cursor to Hand and the Background to #FFDDDDDD. Set the Padding to Left= Right= 10 pixels. Leave the Top and Bottom Padding set to 0.

5. Make a TemplateBinding between the Border’s BorderBrush and BorderThickness and the equivalent properties in the control. Note how the display is updated according to the properties that you set just before on the MediaInfoDisplay control in the Page.

6. Continue designing the ControlTemplate according to your fantasy, for example, set the CornerRadius to 16.

7. In the Border, add a TextBlock and set its HorizontalAlignment to Center and the VerticalAlignment to Top. Set the Top Margin to 10 pixels. Leave the other Margins set to 0. Reset the TextWrapping property to NoWrap.

8. Make a TemplateBinding between the TextBlock’s Text property and the ShortDescription.

9. In the Grid, add another Border. It will be positioned on top of the ShortDescription one. However, we don’t want it to interfere with the rest of the UI, so set its IsHitTestVisible property to False.

10. Create a TemplateBinding for the BorderBrush and BorderThickness. Set the Width and Height of this Border to Auto; reset the HorizontalAlignment and the VerticalAlignment. Also set its Background to #FFDDDDDD and the CornerRadius to 16 pixels, Padding to Left= Right= Bottom= 10 pixels and leave the Top Padding to 0 pixels.

11. Add a TextBlock to the second Border. Set the HorizontalAlignment to Center, and VerticalAlignment to Stretch. Set the Top Margin to 10 pixels. Create a TemplateBinding between the Text property and the LongDescription.

Making Transitions

Now we add transitions to our control. Remember that we have two states groups (one with Normal and MouseOver, the other with DescriptionExpanded and DescriptionNormal). These states should be visible in the Interaction panel, in the States category.

Also, remember that the event handlers triggering the transitions are set when the DescriptionPanel part is found. So let’s name one of the elements in the template accordingly! Follow the steps:

1. Rename the template’s main Grid to DescriptionPanel.

2. With the Base state selected in the States category, set the second Border’s Opacity to 0%.

3. Select the MouseOver state. Blend changes in State recording mode. Select the first Border and set its Background to #FFF3FF2C. Do the same for the second Border too.

4. Set the transition time for the CommonStates group to 0.2 seconds.

5. Then click on the DescriptionExpanded state.

6. Select the second Border (the invisible one) and set its Opacity to 100%.

7. Add a transition from DescriptionExpanded to DescriptionNormal and set the time to 0.2 seconds.

8. Then add a transition from DescriptionNormal to DescriptionExpanded and set this time to 0.5 seconds (see Figure 20.2).

Figure 20.2 States and Transitions

Image

9. Stop the State recording mode and set the scope back to the Page.

At this stage, you can test your application to see whether the states and transitions are working. The MediaInfoDisplay should turn to yellow when the mouse is over the ShortDescription border. Because we set the IsHitTestVisible to False for the bigger Border, it shouldn’t react to the mouse.

If you click on the panel when it’s yellow, you should see the LongDescription appear. Click again to make it fade. Notice that the “expanding” time is slightly longer than the “fading” time, which is what we wanted.

Removing the Test Data

Now we can remove the test data; we don’t need it anymore. Simply delete TEMPMediaInfo from the resources in Page.xaml in Studio.

Wiring Up

And now for the last step: We connect the item currently displayed with the MediaInfoDisplay control. Remember that the ThumbnailsViewerControl has a SelectedItem property, and this is a DP. So we can create a data binding between that DP and the MediaInfo DP on the MediaInfoDisplay control.

However, in Silverlight 2 (unlike in Windows Presentation Foundation), you cannot bind two UI elements together directly. You must do this through a data object (this is cleaner anyway, but that’s another story).

1. In Visual Studio, add a class to the Data folder in the Thumbnails project. Name this class Settings.cs.

2. Implement the class shown in Listing 20.4:

Listing 20.4 Settings Class

public class Settings : INotifyPropertyChanged
{
     #region INotifyPropertyChanged Members
     public event PropertyChangedEventHandler PropertyChanged;
     #endregion

     private MediaEx _currentMediaInfo = null;
     public MediaEx CurrentMediaInfo
     {
          get { return _currentMediaInfo; }
          set
          {
               _currentMediaInfo  =  value;
               if (PropertyChanged != null)
               {
                    PropertyChanged(this,
                        new PropertyChangedEventArgs(″CurrentMediaInfo″));
               }
          }
     }
}

One property only! When it changes, the PropertyChanged event is raised.

3. Click on File, Save All and build the application. You will have to add one using statement.

4. In Blend, build the application. Then, in the Data panel, click on +CLR Object and select the Settings class. Set the Data Source Name to Settings and click OK.

5. Select the ThumbnailsViewer and create a binding between its SelectedItem property and the Settings data source. In the Fields panel of the Create Data Binding dialog, choose the CurrentMediaInfo property.

6. Expand the Advanced Properties section and set the Binding direction to TwoWay. Then click Finish.

7. Select the MediaInfoDisplay control and set a binding between its MediaInfo property and the same CurrentMediaInfo property on the Settings data source. You can leave this binding set to OneWay.

Our application is functional now. Try it: Run the application and select a thumbnail. The description should appear. For some of them, the ShortDescription might be truncated, but it’s okay; you can click on the panel and expand the LongDescription.

In Chapter 22, “Connecting to the Web,” we remove all hard-coded information about the media files, and import them from an XML file instead. This makes your application fully functional to work on the Web. We also see how we can get some pictures to be loaded from Flickr directly, through a web service.

Digging Deeper

As we said before, creating custom controls is a big job. The previous section gave you an overview, but if you intend to create relatively complex custom controls, you may want to “dig deeper.” Karen Corby, program manager for Silverlight 2, created a good series of four blog posts about custom controls. This is where you want to start! The first of her posts and the starting point to the series is located at http://scorbs.com/2008/06/11/parts-states-model-with-visualstatemanager-part-1-of.

Scrolling the Items

What else is missing? Oh yes, we have a ScrollViewer, but it doesn’t scroll yet! Since we set the vertical scrollbar’s visibility to Hidden, we can scroll programmatically. Let’s try this with the following steps.

1. Open the file ThumbnailsViewerControl.xaml in Visual Studio and add a Click event handler to ScrollButtonUp. It should be named ScrollButtonUp_Click.

2. Do the same with ScrollButtonDown and ScrollButtonDown_Click.

3. In ThumbnailsViewerControl.xaml.cs, disable both buttons in the constructor. Add the code shown in Listing 20.5 under the call to InitializeComponent.

Listing 20.5 Disabling Both RepeatButtons

ScrollButtonUp.IsEnabled =  false;
ScrollButtonDown.IsEnabled =  false;

4. Then, implement the handlers as shown in Listing 20.6:

Listing 20.6 Event Handlers for RepeatButtons

  1     private double _scrollOffset = 0.0;
  2     private void  ScrollButtonUp_Click(object sender, RoutedEventArgs e)
  3     {
  4          ScrollButtonDown.IsEnabled = true
;
  5          _scrollOffset -= ThumbScrollViewer.ActualWidth / 10;
  6          if (_scrollOffset <= 0)
  7          {
  8               _scrollOffset = 0;
  9               ScrollButtonUp.IsEnabled = false;
10          }
11          ThumbScrollViewer.ScrollToVerticalOffset(_scrollOffset);
12     }
13
14     private void ScrollButtonDown_Click(object sender, RoutedEventArgs e)
15     {
16          ScrollButtonUp.IsEnabled = true;
17          _scrollOffset += ThumbScrollViewer.ActualHeight / 10;
18          if (_scrollOffset >= ThumbScrollViewer.ScrollableHeight)
19          {
20               _scrollOffset = ThumbScrollViewer.ScrollableHeight;
21               ScrollButtonDown.IsEnabled = false;
22          }
23          ThumbScrollViewer.ScrollToVerticalOffset(_scrollOffset);
24     }

Image Line 1 declares a private attribute to store the value of the offset. Like most values having to do with layout in Silverlight, this is a double.

Image Whenever the user clicks on the up button, we make sure that the down button is enabled. Even if it was disabled before, clicking on the up button restores the down button’s capability to scroll.

Image When you click on the up button (lines 2 to 12), you want to scroll up. We must calculate a smaller offset than the current one. However, we cannot scroll lower than 0 (line 6 to 10). Also, if the calculated value is equal to 0, we disable the button to notify the user.

Image Line 11 does the magic, and calls the ScrollViewer’s ScrollToVerticalOffset method. Remember that this method only works when the corresponding ScrollBar’s visibility has been set to Hidden, not Disabled!

Image Finally, lines 14 to 24 do the corresponding operation for the other direction. Notice the usage of ThumbScrollViewer.ScrollableHeight to make sure that we don’t exceed the scrollable area.

To make the state of the buttons consistent with the control’s size, we now handle the LayoutUpdated event. This event gets called when the application starts and also every time that something causes the layout to change, so the buttons get enabled or disabled depending on the size of the control, the number of items it contains, and so on.

1. In ThumbnailsViewerControl.xaml, add this event to the ThumbItemsControl.

         LayoutUpdated=″ThumbnailsViewerControl_LayoutUpdated″

2. Create the corresponding event handler in ThumbnailsViewerControl.xaml.cs (Listing 20.7):

Listing 20.7 ThumbnailsViewerControl_LayoutUpdated Event Handler

  1     private void ThumbnailsViewerControl_LayoutUpdated(object sender,
  2          EventArgs e)
  3     {
  4          if (ThumbScrollViewer.ScrollableHeight == 0)
  5          {
  6               ScrollButtonUp.IsEnabled = false;
  7               ScrollButtonDown.IsEnabled = false;
  8          }
  9          else
10          {
11               if (ThumbScrollViewer.VerticalOffset > 0)
12               {
13                    ScrollButtonUp.IsEnabled = true;
14               }
15               if (ThumbScrollViewer.VerticalOffset
16                        <  ThumbScrollViewer.ScrollableHeight)
17               {
18                    ScrollButtonDown.IsEnabled = true;
19               }
20          }
21          _scrollOffset = ThumbScrollViewer.VerticalOffset;
22     }

Image Here too, we use the convenient property ScrollableHeight to check whether we are in a state where scrolling is even possible. If not, we disable both buttons.

Image If we can scroll, we check in which direction(s) we can do this and enable or disable the buttons accordingly.

Image Finally, maybe the offset changed during this operation, so we save the new value on line 21.

3. Test the application. Observe what happens when you resize the window to a small size or a big size. The buttons should get enabled or disabled accordingly. Then try scrolling the thumbnails. Since we used RepeatButtons, you can press and hold a button to scroll in successful steps.

The scrolling algorithm we use now is simple and could probably be improved. For example, using a smaller “step” when the button is clicked, making the Interval property shorter, and so on.

Creating a New RepeatButton Template

Let’s make our buttons look nicer! Keep in mind, however, that this author is a developer and no designer—and that he does his best. So please don’t laugh too hard!

Creating the Template

First we will change the appearance of the buttons, by creating a new template for them. Follow the steps:

1. In Expression Blend, open ThumbnailsViewerControl.xaml. Right-click on the top RepeatButton in Blend and select Edit Control Parts (Template), Create Empty.

2. Enter the name ScrollButtonTemplate and create the template in the UserControl.

3. In the template editor, rename the Grid to “Root”. This is needed to work around a bug in Silverlight with RepeatButtons. Then add an Ellipse to the Grid and make it fill the whole cell. Set its Fill property to blue temporarily and reset the Stroke to No brush. Name the Ellipse BackgroundPath.

4. Convert the Ellipse to a Path (select Object, Path, Convert to Path).

5. Using the Pen and the Direct Selection tool, modify the Path to make it look roughly like Figure 20.3.

Figure 20.3 RepeatButton template path

Image

6. In the template editor, make sure that the Grid is selected with a yellow border. Then copy and paste the same Path again. Name the second path Highlight so that it’s easier to recognize it.

7. Set the Highlight’s Fill property to a RadialGradientBrush. Set the first GradientStop to #CCFFFFFF and the second stop to #00FFFFFF.

8. Use the Brush Transform tool to move the brush until you get the effect shown in Figure 20.4.

Figure 20.4 RepeatButton with highlight

Image

Changing the Transitions

1. Select the Base state. Then select the Highlight and set its Opacity to 0%. Set the BackgroundPath’s Opacity to 80%.

2. Select the MouseOver state. This puts Blend in State recording mode. Select the Highlight and make sure that its Opacity is 100%. Also set the BackgroundPath’s Opacity to 100%.

3. Select the Disabled state, and set the BackgroundPath’s Opacity to 20%.

4. Select the Pressed state. Select the Highlight element and set its Opacity to 50%.

5. Exit the State recording mode.

6. Add the following transitions and set their transition time to 0.1 seconds: from Normal to MouseOver; from Normal to Pressed; from MouseOver to Normal; from MouseOver to Pressed; from Pressed to MouseOver; from Pressed to Normal.

We set the transitions from Normal to Pressed to Normal, because the RepeatButton has a ClickMode property (we saw that in Chapter 16, “Digging Deeper into Silverlight Elements,” remember?). When ClickMode is set to Hover, the button goes directly from Normal to Pressed without going over the MouseOver state.

Binding the Template

We want to be able to use the same template on multiple buttons and change the colors.

1. Select the BackgroundPath’s Fill property and click the small square next to it. Choose TemplateBinding, Background.

2. Do the same for the Path’s Stroke and bind it to the BorderBrush.

Testing the New Button

We have all we need to set up the new button and test it with the following steps:

1. Give the scope back to the UserControl.

2. Change the button’s Background to red and check whether the BackgroundPath also changes its color. Do the same for the button’s BorderBrush.

3. Run the application and pass the mouse over the button. The Highlight should appear in 0.1 seconds. Then press the button and check whether the Highlight changes as specified. You must click on the down button first, so that the up button is enabled.

4. Then reset the Background and BorderBrush to the default. We create a Style in the next section to handle this.

At this stage, feel free to change the design as you want. Remember that the Visual State editor allows you to change any property of the button template. Keep it tasteful though!

Styling the RepeatButton

In this section, we set all the RepeatButton’s properties through a Style. Remember that this is a better practice, especially because we use two RepeatButtons with the same look and feel.

Also, it is better to set the template within the Style instead of directly in the RepeatButton. Without going into too much detail, let’s just say that this makes the workflow designer/developer easier, especially when the Style is placed in the App.xaml file. Follow the steps:

1. With the top RepeatButton selected, choose Object, Edit Style, Create Empty from the menu. Name the new style ScrollButtonStyle and place it in the UserControl.

2. In the Style, set the Margin to 10. Set the Background and the Stroke brush to a SolidColorBrush of #FF4D009E.

3. Set the Template property in the Style to Local Resource, ScrollButtonTemplate.

4. Set the scope back to the UserControl. Find the RepeatButton’s Template property and reset it to default if needed.

You can now set the scope back to the UserControl.

Styling the Other Button

Let’s take care of the other RepeatButton. Thankfully, it is easy to do so, even though the button “looks in the other direction.” We simply flip it! Follow the steps:

1. Select the RepeatButton on the bottom.

2. Select Object, Edit Style, Apply Resource and apply the ScrollButtonStyle.

3. Use the Transform section in the Properties panel to “flip” the button along the Y axis.

That’s it; we now have two cool-looking (well, at least I think so) buttons to scroll our Thumbnails collection (see Figure 20.5). You can now build and test the application.

Figure 20.5 Thumbnails application during an animation

Image

Exporting Classes to an External Assembly and Refactoring

A .NET application is always composed of multiple assemblies (EXEs or DLLs). A DLL (Dynamic-Link Library) contains intermediate language (IL) code as we discussed in previous chapters. The classes stored in the DLL can be reused in multiple applications. This is exactly what is done when we create a new project:

Image Expand the References folder.

Image This folder contains references to multiple external assemblies, which contain the code needed to run even the simplest Silverlight application (see Figure 20.6).

Figure 20.6 References folder

Image

You can also create a Silverlight class library and use it to store utility code, custom controls, or any code that you want to separate from the main assembly for any reason.

Creating a Silverlight Class Library

We already saw that object-oriented code allows creating modular code. A specific class can be instantiated in multiple places and used in multiple configurations. Sometimes you want to be even more modular than this and reuse a particular object in a new application that you are developing. This is where a class library can be useful.

For example, let’s imagine that we want to reuse the classes DataFile and User that we created for our Thumbnails application. After all, there are multiple scenarios where you may want to log a user’s name, password, and last visit. We don’t want to have to rewrite that code over and over again.

Of course you could imagine copying and pasting the code you want to reuse in every application where it’s needed. This is not a good idea; however, if you find a bug in your code, or simply want to make a modification, you will need to make that change in every single copy of the code. Not very efficient!

Creating the Project and Moving the Classes

Visual Studio is a great help for refactoring an application and moving classes around if needed. Follow the steps:

1. Open the application Thumbnails in Visual Studio.

2. In the Solution Explorer, right-click on the Thumbnails Solution (not on the project!) and select Add, New Project, as shown in Figure 20.7.

Figure 20.7 Selecting Add, New Project from the Solution Explorer

Image

3. In the Add New Project dialog under Silverlight, select Silverlight Class Library.

4. Enter a name for the new class library, for example, UserManagement.

5. Because we want to reuse this class library in various projects, it makes sense to save it in a location independent from the Thumbnails project. You can choose any location to save the new UserManagement project.

6. The newly created project contains a class created by default and named Class1. We don’t need this class, so you can safely delete this file in the Solution Explorer.

7. In the Solution Explorer, drag the file DataFile.cs from the Data folder in the Thumbnails project. Drop this file on the new UserManagement project. Then repeat the operation for the file User.cs.

8. Dragging and dropping creates a copy of the files. Since we only use the ones from the UserManagement project, you can delete these files from the Data folder in the Thumbnails project.

Changing the Namespace

It is good practice to modify the namespace containing the objects according to the project containing them. This makes looking for bugs easier and provides a neater organization. This is not required, because nothing requires a namespace to be named according to its containing project, but it is good to do so.

1. Open the files DataFile.cs and User.cs and modify the namespace to read:

          namespace UserManagement

After compilation, the classes DataFile and User will be placed into a new assembly named UserManagement.dll, just like the project. For each project, Visual Studio creates a separate assembly when you build. If you compile the application now, you will get errors: DataFile is not found. To use the objects, you need to add a reference to the newly created project.

2. In the Solution Explorer, right-click on the Thumbnails project and select Add Reference.

3. Select the Projects tab. You should see the UserManagement project that we just created (see Figure 20.8). Select this project and click OK.

Figure 20.8 The Add Reference dialog

Image

4. Expand the References folder in the project Thumbnails (in the Solution Explorer). Notice that, in addition to the assemblies we mentioned earlier, you now have a reference to the UserManagement assembly.

5. Double-click on the error in the Error List panel in Visual Studio. This should lead you to the Page class. Add the UserManagement namespace to the using section of the Page class. You must not delete the namespace Thumbnails.Data, because other classes are included in that, such as MediaEx.

6. After replacing the namespace, build the application.

Updating the Visibility

This last step causes a lot of errors in Visual Studio, like ′UserManagement.DataFile′ does not contain a definition for ′LoadUsers′ and no extension method ′LoadUsers′ accepting a first argument of type ′UserManagement.DataFile′ could be found (are you missing a using directive or an assembly reference?)"

So what happened? The method LoadUsers exists, it is defined, and we didn’t change anything in the code! The reason for the error is the method’s visibility: We defined it as internal, which makes it visible only within its own assembly. If you want to use this method outside the assembly, you need to make it public.

It is good practice to use the lowest possible visibility for a class or its members. However, it causes this kind of errors when you refactor your application and move classes in other assemblies. This is what software design is about, carefully planning components (assemblies), classes, their members, and their features.

Let’s correct these errors now:

1. Open the Error List panel in Visual Studio, and double-click on the first error. This takes you in the source code, at the location where the LoadUsers method is called.

2. Right-click on the method’s name, and select Go to Definition from the context menu.

3. In the method’s definition, replace the internal keyword with public.

4. Build the application. You still have errors, but there are less of them.

5. Repeat steps 1 to 4 as many times as needed, until all the errors are gone.

Testing the New Component

After all the errors have been corrected, run the application and try to log in using a previously defined user name and password combination. You see that the user name is still available! The reason is that isolated storage files are available to the application that created them. Even though we moved the code to a different assembly, the application is still the same and it may reuse the same file.

Note that even though refactoring is relatively easy with Visual Studio, creating class libraries comes with a responsibility: If you make changes to code that is already integrated in another application, there is a risk of breaking it. Such changes must be considered carefully to avoid any side effects.

Checking the DLL

As a final step, right-click on the Thumbnails project in the Solution Explorer. Select Open Folder in Windows Explorer. Then navigate to the folder bin/Debug.

If you scroll down, you see a new DLL named UserManagement.dll. This component was created when we built the new project. Because the Thumbnails project has a reference on the UserManagement project, this DLL is copied to the Silverlight application.

Using an External Class Library

To reuse a class library, you don’t always need a reference to the project that created it. It is enough to have a copy of the DLL and to add a reference to it. We can demonstrate this with the following steps:

1. Navigate to the folder UserManagementinDebug in Windows Explorer.

2. Copy the assembly UserManagement.dll.

3. Create a new folder in the project root, at the same level as the file UserManagement.csproj. Name this folder Reference.

4. Paste the UserManagement.dll into the References folder.

5. Create a new Silverlight application in Visual Studio. Name it SimpleUserLogin.

6. Right-click on the project and choose Add Reference. In the Browse tab, navigate to the Reference folder we created just before, and select the UserManagement.dll.

7. In Page.xaml, change LayoutRoot as in Listing 20.8. Note how we use a new control named PasswordBox, useful to mask a user’s input from prying eyes.

Listing 20.8 SimpleUserLogin UI

<StackPanel x:Name=″LayoutRoot″ Background=″White″>
     <TextBlock Text=″User name″ Margin=″10,10,10,0″ />
     <TextBox  x:Name=″UserNameTextBox″ Margin=″10″ />
     <TextBlock Text=″Password″ Margin=″10,10,10,0″ />
     <PasswordBox x:Name=″PasswordTextBox″
                                    Margin=″10″/>
     <Button  Content=″Login″ Margin=″10″
                         x:Name=″LoginButton″
                         Click=″LoginButton_Click″ />
     <Button  Content=″New User″ Margin=″10″
                         x:Name=″NewUserButton″
                         Click=″NewUserButton_Click″ />
     <TextBlock  x:Name=″StatusTextBlock″ Margin=″10″ />
</StackPanel>

8. Modify the Page class like Listing 20.9:

Listing 20.9 SimpleUserLogin Logic

private DataFile _dataFile;

public Page()
{
      InitializeComponent();
      _dataFile = new DataFile
();
      _dataFile.LoadUsers();
}

private void LoginButton_Click(object sender, RoutedEventArgs e)
{
     User inputUser = new User
();
     inputUser.Name  = UserNameTextBox.Text;
     inputUser.Password    = PasswordTextBox.Password;
     if (_dataFile.ValidateUser(inputUser))
     {
          User savedUser = _dataFile.GetUser(inputUser.Name);
          StatusTextBlock.Text = ″(Your last visit was: ″
             + savedUser.LastVisit.Value.ToShortDateString()
             + ″ ″ + savedUser.LastVisit.Value.ToLongTimeString() + ″)″;
          savedUser.SetLastVisit();
          _dataFile.SaveUsers();
     }
     else

     {
          StatusTextBlock.Text = ″Invalid user/password combination″;
     }
}

private void NewUserButton_Click(object sender, RoutedEventArgs e)
{
     try

     {
          User newUser = new User
();
          newUser.Name  = UserNameTextBox.Text;
          newUser.Password = PasswordTextBox.Password;
          newUser.SetLastVisit();
          _dataFile.AddUser(newUser);
          StatusTextBlock.Text = ″(This is your first visit)″;
     }
     catch (Exception ex)
     {
          StatusTextBlock.Text = ex.Message;
     }
}

You will need to add the UserManagement namespace to the using section to build the application. Then you can run and test it. This code is similar to what we have in the Thumbnails application. In fact, we delegate the whole user management scenarios to the UserManagement component, with its classes DataFile and User. This way, the whole isolated storage management is encapsulated, and we don’t need to worry about it.

If you think that your component’s functionality is interesting enough, you can even distribute your assembly, for example, on the Web. Creating external class assemblies allows you to easily manage and reuse functionality.

Talking About the Application Object

In the next chapter, we work with the Application object. While we already mentioned this object a few times, we never discussed it in details. Let’s take a closer look at this fundamental object in Silverlight applications, with the following steps.

1. In any Silverlight application, open the file App.xaml.cs.

2. The class named App (deriving from the Application class) contains all kinds of global information, valid for the whole Silverlight application. For example:

Image The Startup event is called when the application is starting up. Note that this object is responsible for creating the Page instance where our code is running. If you decide to create additional pages, this is where you can decide which one should be the initial one.

Image The Exit event is called when the application is, you guessed it, exiting. This can be because the user navigates to a different HTML page or refreshes the page using the F5 key, for example.

Image The UnhandledException event is called when an exception occurs in the code, but is not handled anywhere. We talk more about exceptions in Chapter 23, “Placing Cross-domain Requests and Handling Exceptions.”

You can modify these events to handle special scenarios in your code. If needed, you can also add your own properties to the App object. For example, Listing 20.10:

Listing 20.10 Property in Application Object

public partial class App : Application
{
     private DateTime _initializationDate = DateTime.Now;
     public DateTime InitializationDate
     {
          get { return _initializationDate; }
     }

     // ...
}

Then you can get this value anywhere in the Silverlight application with:

DateTime _initTime = (App.Current as App).InitializationDate;

Note that the type of App.Current is Application, so to access the properties you add you need to cast it to App.

App.Current is a global object that can be retrieved from anywhere in the rest of the code, even in data objects. That makes it handy to store values that are shared everywhere.

Using Generics

When you need a method (or a class) to deal with multiple types, but you are not sure in advance what these types will be, you want to use generics. It’s a little like saying “I create a method now, and it will accept multiple parameters types.” In Silverlight (and generally in .NET), one of the most common uses of generics is for strongly typed collections. Before generics, filling a collection and then accessing the content was something like Listing 20.11:

Listing 20.11 ArrayList (Old Implementation)

ArrayList myArrayList = new ArrayList();
myArrayList.Add(″hello″);
myArrayList.Add(″world″);
// ...
string element1 = (string) myArrayList[0];
string element2 = (string) myArrayList[1];

Notice how, even though each element is a string, it is necessary to cast them back to string when accessing them. This is because an ArrayList stores elements of type object only. Remember, each and every class in .NET always derives from object, even if nothing explicit is specified.

With generics, you can write (Listing 20.12):

Listing 20.12 Generic List (New Implementation)

List<string> myList = new List<string>();
myList.Add(″hello″);
myList.Add(″world″);
// ...
string element1 = myList[0];
string element2 = myList[1];

No more casting, because the .NET compiler knows that the List contains only elements of type string.

To define your own generics method, use the syntax shown in Listing 20.13:

Listing 20.13 Generic Method Implementation

public void DoSomethingGenerics<T>(T myParameter)
{
     // ...

}
public void UseMyMethod()
{
     DoSomethingGenerics<string>(″hello″); // OK
     DoSomethingGenerics<int>(2);                        // OK

}

You can also create generics classes as in Listing 20.14:

Listing 20.14 Generic Class Implementation

public class MyClass<T>
{
     private T myAttribute;
     public MyClass(T myParameter)
     {
          myAttribute = myParameter;
     }
}

And then Listing 20.15:

Listing 20.15 Using a Generic Class

Uri   uri = new Uri(″http://www.galasoft.ch″);
MyClass<Uri> myObject = new MyClass<Uri>(uri);
Rectangle shape = new Rectangle();
MyClass<Rectangle> myObject2 = new MyClass<Rectangle>(shape);

Notice how we use the same object with two totally different parameters, just like when we use a generics collection such as List<>. The notation <T> tells the compiler that the type can vary.

Summary

In this chapter, we finished what we started in Chapter 19 and gave our Thumbnails application a different look. Also, and maybe more importantly, the media files we display are not located in the Thumbnails assembly anymore, but on the web server. We still have information about them hard-coded, but that will change soon. We now understand how to create user controls and custom controls, how to modify an existing control’s look and feel, and why controls in Silverlight are lookless.

We also talked about advanced Silverlight topics: Exporting classes to a class library, reusing these components in another application; then we talked about the Application object and finally about generics.

We continue our discussion of advanced topics in Chapter 21, “Taking Silverlight 2 Even Further.”

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.72.70