In This Chapter
Creating a Default Template for the |
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.
MediaInfoDisplay
ControlWhen 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:
<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:
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>
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
(
. This line enables us to specify a generic style by setting the MediaInfoDisplay
);TargetType
only. This Style
is automatically applied to all MediaInfoDisplay
instances (unless of course the designer specifies something different).
Lines 4 to 15 specify the template for our control.
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.
We test this default look and feel later in this chapter, when we add the control to our Thumbnails application!
To make sure that all changes are visible, build your application in Visual Studio before going further.
MediaInfoDisplay
ControlBecause 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 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.
<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.
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
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
.
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
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.
Now we can remove the test data; we don’t need it anymore. Simply delete TEMPMediaInfo
from the resources in Page.xaml in Studio.
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:
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.
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.
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
.
ScrollButtonUp.IsEnabled = false;
ScrollButtonDown.IsEnabled = false;
4. Then, implement the handlers as shown in Listing 20.6:
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 }
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
.
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.
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.
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!
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):
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 }
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.
If we can scroll, we check in which direction(s) we can do this and enable or disable the buttons accordingly.
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.
RepeatButton
TemplateLet’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!
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
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
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.
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
.
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!
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
.
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
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:
Expand the References folder.
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
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.
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!
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
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.
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
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.
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.
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.
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.
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.
<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:
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.
Application
ObjectIn 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:
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.
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.
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:
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 = (
).InitializationDate;App
.Current as App
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.
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:
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):
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:
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:
public class MyClass<T>
{
private T myAttribute;
public MyClass(T myParameter)
{
myAttribute = myParameter;
}
}
And then Listing 20.15:
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.
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.”
3.145.72.70