Although there is an infinite number of ways to build an application, over time a few reusable patterns emerge that are particularly helpful when designing complex programs. This chapter covers just a few particularly useful patterns.
As with all design patterns, there is no single way to implement these. In some cases, the overall design and idea are more important than the specific code used to implement them.
In addition to application patterns, there are some handy tips for building applications that didn’t fit in anywhere else.
Solution: Use the System.Diagnostics.Stopwatch class.
While there are many extensive profiling packages out there (there is a profiler included in some editions of Visual Studio), sometimes you just need to time a known block of code with a stopwatch.
This produces the output:
A useful trick when you need to use the Stopwatch for debugging purposes is to utilize the IDisposable interface to automate the use of the stopwatch:
Now you can take advantage of the using {}
syntax:
Besides Start()
and Stop()
, there are also Reset()
, which stops and sets Elapsed
to 0, and Restart()
, which sets Elapsed
to 0, but lets timer continue running.
Solution: Mark the method or class with the [Obsolete]
attribute.
The compiler will warn anyone using an entity marked as obsolete with at least a warning. You can optionally supply a message for the compiler to present:
[Obsolete("Don't use this because...")]
class MyClass { }
You can also make it an error to use the obsolete code:
[Obsolete("Don't use this because...", true)]
class MyClass { }
For example, if you have a UI that responds to updates from a data source, you need to take care that if the data source produces a lot of updates, your UI performance doesn’t suffer.
Solution: Rather than notifying subscribers of each update, group them into a meta-event. For a starting example, assume you have a collection that notifies listeners when it has been updated:
The client of this collection happens to be a Windows Form. Here is part of its source code:
When the button is clicked, 20,000 items are added to the collection, which generates 20,000 event notifications, and 20,000 inserts and updates into the ListView
.
The ListView
has the ability to prevent UI updating during large inserts with the BeginUpdate
and EndUpdate
methods. This idea could also be used in your custom collection to batch updates.
The ItemAddedEventArgs<T>
class must be updated to contain more than one item:
Here’s the updated MyCollection<T>
:
Now the client must call BeginUpdate
before adding items to take advantage of this:
The time savings can be immense. Refer to the BatchEvents sample program in the included source code to see the difference. On my machine, the non-batched version took nearly 6 seconds, whereas the batched version took less than a second.
Solution: While most notification systems use .Net events to communicate, there are times when you want something a little more decoupled. For this, .Net 4 provides two interfaces to aid in implementing this common design pattern.
Use the IObserver<T>
and IObservable<T>
interfaces.
The IObservable<T>
interface is implemented on the class that provides data for others to consume.
The IObserver<T> is implemented on classes that want to know about the updates in the IObservable<T>-derived classes.
To tie them together, merely subscribe the observers to the data generator:
The output is something like this:
Solution: An event broker is merely an object that acts as middleman for any number of events from any objects to any other object. In a way, this is taking the Observer Pattern (see previous section) even further.
Here’s a simple event broker implementation:
Usage is very simple: Rather than raising normal .NET events, just call the appropriate methods on the EventBroker
. The project contains three user controls: One of them raises the event and the other two listen for it. A form owns the event broker and ties everything together, as the following partial code example shows:
See the EventBroker sample for the full source.
Using this method gives you a few advantages:
• Because strings are used, any component can publish or subscribe to any event without having to add a reference to a strongly typed object.
• Because no component knows anything about the origin or destination of events, it is trivial to add or remove components with breaking dependencies.
This method is most appropriate for global events that you need to communicate across the entire application, and passing objects around complicated code hierarchies just to listen for events is not worth the headache and maintenance problems that are entailed. For more local events, you should definitely just use the normal .NET event pattern.
Solution: Although this task is easy, you need to take into account that when you restore an application, what used to be on the screen before might not be on the screen anymore. For example, a user might rearrange a multiple-monitor scenario, or merely change the resolution of his screen to something smaller.
The screen location should be a user-specific setting. For the following example, two user settings were created in the standard Settings.settings file (see Chapter 16, “Windows Forms”).
Refer to the SaveScreenLocation project in the accompanying source code to see this code in practice.
Solution: Most programs that let the user edit content have the ability to let the user undo the previous action. This section demonstrates a simple widget application that allows undo functionality (see Figure 25.1).
The most popular way to implement this involves command objects that know how to undo themselves. Every possible action in the program is represented by a command object.
Not everything the user can do in your application needs to be a command. For example, moving the cursor and changing the current selection aren’t usually considered actions. Generally, undoable commands should be those that change the user’s data.
Here’s a possible interface:
We also need a way to track all our commands in the order they were issued:
Given these two things, the specific implementations of commands depends on the data structures of the application.
In this case, we have an IWidget
interface defining all our objects:
One command we need is to be able to undo a drag/move operation. The command object needs only as much context to be able to do and undo the operation (in this case, the old location and the new location):
Here’s the CreateWidgetCommand
object, which takes a different type of state:
To use this functionality, you just have to create the command objects at the appropriate time. Here is the Form
from the CommandUndo
sample code. Look at the project in Visual Studio to see the full source.
Although WPF has the notion of command objects already, they do not have the ability to undo themselves (which makes sense because undo is an application-dependent operation). The ideas in this section can be easily translated to WPF.
Solution: As WPF has increased in popularity, the Model-View-ViewModel pattern has emerged as a variation of Model-View-Presenter, which works very well with the WPF binding system.
The ViewModel solves the problem of trying to associate WPF controls with data objects that don’t have any knowledge of UI. It maps plain data objects to data that WPF can bind to. For example, a color code in a database could be translated to a Brush
for the view to use. The following sections tackle each part of this, piece by piece.
Figure 25.2 shows the final sample application which has two views of the data: a list of all widgets, and a view of a single widget.
In this case, we’ll just use objects in memory, but you could just as easily connect to a database, a web server, or a file.
As you can see, this data model has no notion of anything related to WPF.
Because we’ll have multiple ViewModel classes in this app, and they have some common functionality, let’s define a base class:
WPF uses the INotifyPropertyChanged
interface to know when to update views that are bound to these ViewModel objects.
The first concrete ViewModel is the WidgetViewModel
:
The other ViewModel is for the view that will show a list of all Widget
objects:
The view involves mostly just setting up the UI and bindings to the ViewModel (see Listing 25.1). As you’ll see in the next section, the DataContext
property for this control will be set to the ViewModel.
The Widget
-specific view displays a single widget in a graphical way (see Listing 25.2).
Now we just need to hook everything up with the MainWindow
and MainWindowViewModel
. The MainWindow
needs to execute some commands, which should be done in the ViewModel. To do this, you can’t use the standard WPF RoutedUIEvent
, but you can easily develop your own command classes. A common way to do this is to create a command object that calls a delegate you specify:
Now we can define the MainWindowViewModel
:
Now it’s just a matter of binding the MainView
parts to properties of the ViewModel (see Listing 25.3).
The line: xmlns:local="clr-namespace:MVVMDemo"
brings the .NET namespace into the XML namespace local
so that it can be used to refer to the controls in the XAML.
To see it all in action, look at the MVVMDemo project in the accompanying source code.
The key point to MVVM is to make the view completely concerned with how data looks, never about behavior. Ideally, a view should be completely plug-and-play, with the only work being to hook up the bindings to the ViewModel.
In addition, separating all the behavior from the GUI allows you to be far more complete in unit testing. The ViewModel doesn’t care what type of view uses it—it could easily be a programmatic “view” that tests its functionality.
Solution: The chapters on numbers and strings cover the display of those items in different cultures. To display your program’s UI in a different culture requires a bit more work, and the process can be quite different, depending on the technology you use.
In .Net, culture applies to a thread. Each thread actually has two culture settings: culture and UI culture.
The culture is automatically determined from your region format. In Windows, this is set in Control Panel | Region and Language | Formats. This determines the default formats of numbers, times, and currencies in string formatting (see Chapter 5), but does not affect which localized resources are used.
The UI culture is automatically determined from the computer’s native display language. To view the Windows UI in other languages, you need a localized copy of Windows, or you can install a language pack (only available with certain editions of Windows). Windows 7 lets you change display language in Control Panel | Region and Language | Keyboards and Languages | Display language.
To ease testing for the purposes of this section, the UI culture is manually set to be the same as the non-UI culture in configuration files or in the application startup code.
All .Net applications, regardless of platform, obey a hierarchy when looking for resource files. They look from most specific to least specific, until they get to the default resource repository (usually the application executable itself).
Cultures can be specified with either just the language (French: “fr”), or the language and a region (French-Canada: “fr-CA”). You must create all of your resource files using this standard naming scheme. If resources are stored in a file called Resources.dll, .Net will look for files in this order:
1. Resources.fr-CA.dll
2. Resources.fr.dll
3. Resources.dll
4. Application.exe
This general pattern holds true, even if resources are stored in separate folders.
Windows Forms has the strongest support for localization in the Visual Studio IDE. To localize a form, follow these steps:
1. Change the form’s Localizable
property to true
.
2. Change the form’s Language
property to each language you wish to localize. This will generate new language resource files for the form, such as Form1.en.resx, Form1.it.resx, and so on.
3. With the appropriate language selected, modify the text and other properties (such as location or size) of each element you wish to localize.
4. To add a new control, you must set the language back to Default.
Following these steps changes the form’s InitializeComponent
method to have a lot of code like this:
resources.ApplyResources(this.labelName, "labelName");
This will look in the resources for the appropriate values, including text, location, size, and other properties. Each culture will cause a new directory to be created in the output directory containing the appropriate resource DLL.
To have a global resource, not tied to a specific form, follow these steps:
1. (If one doesn’t exist already) Right-click on the project, and select Add, then Add New Item..., then Resource file. Name it whatever you want, e.g. Resources.resx. Visual Studio will generate a class that automatically reads the resx file for the current culture and returns the right value with strongly typed properties. Only one class exists, regardless of how many cultures you want to translate the file into.
2. Add the desired strings and other resources (in the sample, it’s just a string called Message
).
3. Copy the resource file, or add a new one, called Resources.it.resx
. Make sure it’s in the same folder as the default file.
4. Make the necessary translations and changes.
5. Wherever you need to use the resource, use code similar to the following:
//my resources are in the Properties folder/namespace:
this.labelMessage.Text = Properties.Resources.Message;
Localizing ASP.Net applications is conceptually similar to Windows Forms in many ways.
To localize a specific form, follow these steps:
1. Create the form in the default language (e.g., Default.aspx).
2. Go to the Tools menu and choose Generate Local Resource. Visual Studio will create the App_LocalResources
folder, create the file Default.aspx.resx, and populate it with the keys and values from the ASPX file. It will also add meta:resourceKey
properties to your ASPX file.
The name of the resource for this Label's Text
property will be LabelNameResource1.Text
.
3. Create additional resource files for each target culture, e.g. Default.aspx.it.resx, Default.aspx.fr-CA.resx. You can copy the original file and just rename it to pre-populate it with all the keys and values.
4. Translate each localized resource file.
To create a global resource file (not for a specific ASPX file), follow these steps:
1. Right-click the project, select Add ASP.Net Folder, then App_GlobalResources.
2. In the App_GlobalResources, add a new resource file (e.g., GlobalResources.resx)
3. Add appropriate values to the file (in this case, just a single string named Message).
4. Copy the file to localized versions, GlobalResources.it.resx, for example.
5. In your ASPX files, add code like this to reference the value Message:
To test your web application, make sure that Culture and uiCulture values for the pages are set to Auto (the default):
Then set your web browser’s language to the one you want to test and make sure it’s at the top of the list. Most web browsers allow you to specify desired languages in order of preference.
In Internet Explorer 8, go to Tools | Internet Options | General | Languages.
In Firefox, go to Tools | Options | Content | Languages | Choose....
There are two methods of localizing a WPF application: using XAML and using resources and data binding. Unfortunately, either way, compared to Windows Forms applications, doing localization in WPF is currently a real chore. Table 25.1 highlights some of the pros and cons of the two approaches.
To localize your WPF application using the XAML method, follow these steps:
1. Manually edit the project file and add <UICulture>en-US</UICulture>
under the <PropertyGroup>
section.
2. Add this line to your AssemblyInfo.cs
file:
3. Open a command prompt and navigate to your project’s directory. Run the command:
msbuild /t:updateuid
This will generate a UID for every WPF element, modifying your XAML files in the process. You should never manually edit these UIDs.
msbuild is located with the .Net framework files, and the easiest way to run it is to start the command prompt with the shortcut that Visual Studio installs; this will initialize the correct environment settings to access the .NET Framework binaries.
4. Rebuild the project. A directory called en-US will appear in the output directory. This contains the resource DLL.
5. Obtain the LocBaml tool from the Windows SDK. On my computer, it was packaged in C:Program FilesMicrosoft SDKswindowsv7.0SamplesWPFSamples.zip. To get it to work in .Net 4, I had to recreate the project from scratch and change the target framework to .Net 4.0. By the time you read this, the tool may work out of the box. However, the tool is not officially supported by Microsoft. (Are you starting to get nervous about this method yet?)
6. Copy LocBaml.exe
, the generated resource DLL, and your application executable to the same folder and run this command from a prompt:
locbaml.exe /parse myapp.resources.dll /out:translate_en-US.csv
7. Copy the file translate_en-US.csv to files with different names, depending on your target cultures, e.g., translate_it-IT.csv, translate_fr-CA.csv.
8. Open the csv files in a program that can edit them (such as any text editor or Excel) and translate the text into the target language. You can also modify things like file paths (in the case that you have different images for cultures, for example).
It’s important to realize that other fields besides UI text will be present. When giving the files to translator, you may want to specify which fields need translating.
9. Create a new directory for the localized DLL, e.g. “it-IT.”
10. Create a localized resource DLL by running:
Copy the resource directory to the output directory, next to the existing en-US
directory.
If you want to easily see the differences, you can add some code to the application startup that will set the UI culture to be to the region specified in Control Panel.
As you can see, this method is a little complex and tedious, especially if you need to make changes after localization has begun (make sure you have excellent version control practices). It’s recommended that you build your own set of tools to manage this process, even if it’s just a set of batch files.
Figures 25.3 and 25.4 show a localized WPF app running in English and Italian, with translated text and different resources.
See the LocWPFXAML project in the sample code for a full example.
Rather than go through all of that, you can use resource files just like with Windows Forms.
1. Create a global resource file and add strings, images, etc. to it.
2. Copy and rename it with the desired culture, e.g., Resources.it-IT.resx. (Make sure that this file is in the same directory as the original Resources.resx) Building the project should result in culture-specific directory being created under the output directory.
3. Create an XML namespace Properties
in the Window
where you want to use the localized resource:
xmlns:props="clr-namespace:LocWPFResources.Properties"
4. Use data binding to attach the resources to XAML controls:
<Label x:Name="labelName" Grid.Row="0" Grid.Column="0"
Content="{x:Static props:Resources.labelName}" />
This is much simpler, but it does require more thought as you develop the UI of your application. Also, you can’t directly bind very much other than strings.
For example, to use an image from resources in a XAML Image
control, you can do something like this:
Given the complexities of localization, you should definitely give the topic a lot of thought before deciding on a strategy. I encourage you to read the whitepaper and look at the samples located at http://wpflocalization.codeplex.com/.
See the LocWPFResources project in the sample code for a full example.
Localized resources in Silverlight are very similar to the resource file methods just given.
1. After you create your Silverlight project, add a new resource file called, for example, Resources.resx
. This is the default resource file. Visual Studio will also create a class to access these resources.
2. Add a resource file for each culture you will need, including the appropriate culture code in the filename, e.g., Resources.it.resx
or Resources.fr-CA.resx
.
3. In the project properties, go to the Sliverlight tab, and click the Assembly Information... button. Select the neutral language for your project (in my example, this is English, with no country).
4. Now edit the project file manually by right-clicking it in the Solution Explorer and selecting Unload Project. Right-click the project again and select Edit project.csproj.
5. Edit the <SupportedCultures />
tag to include the non-default cultures you want. For example:
<SupportedCultures>it;fr-CA</SupportedCultures>
6. Save and close the file.
7. Reload the project by right-clicking the project in Solution Explorer and selecting Reload Project.
8. Modify each resource file appropriately with translated versions of each resource. Make sure each resource file’s access modifier is public.
9. Wrap the Visual Studio-generated wrapper class in another class:
10. Add a reference to the wrapper class to the Application.Resources
section of App.xaml:
11. Bind UI elements to the resources:
12. Test the application in different languages by editing the HTML or ASPX file in the accompanying web project with the following lines in the <object> tag:
Make sure you run the web project, not the Silverlight project, so you can use your edited HTML/ASPX file. Otherwise, Visual Studio will generate one for you, and it won’t have the culture tags.
See the LocSilverlight project in the sample code for this project for a full example.
Solution: Use OneClick deployment. Here are the steps to follow:
1. Right-click the project in the Solution Explorer and select Properties.
2. Click the Security tab.
3. Check the Enable ClickOnce Security Settings box.
4. Select the appropriate trust level. Partial trust will cut off your application from most of the computer’s resources, such as the file system.
5. Select the zone the application will be installed from.
6. Click the Publish tab (see Figure 25.3).
7. Select the folder to which you wish to publish the setup files.
8. Select whether the application should also be installed locally and available in the Start menu.
9. Click Options.
10. Select Deployment.
11. Enter a deployment web page, such as publish.htm.
Figure 25.5 shows the Publish settings in Visual Studio.
Once all of the options are set, you can right-click the project and select Publish.
To run the application, navigate to the generated HTML file and click Run. The .NET runtime will run your application under the restrictions you placed on it. To see the effect, the sample application lets you try to write a file both to the file system and to isolated storage. Only isolated storage is accessible.
Figure 25.6 shows what happens when a locally-installed ClickOnce application with limited permissions tries to touch the file system.
You cannot create WPF windows in a partial-trust environment: You’re limited to the browser window.