Chapter 25. Application Patterns and Tips

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.

Use a Stopwatch to Profile Your Code

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.

image

This produces the output:

image

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:

image

Now you can take advantage of the using {} syntax:

image

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.

Mark Obsolete Code

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 { }

Combine Multiple Events into a Single Event

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:

image

The client of this collection happens to be a Windows Form. Here is part of its source code:

image

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:

image

Here’s the updated MyCollection<T>:

image

image

image

Now the client must call BeginUpdate before adding items to take advantage of this:

image

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.

Implement an Observer (aka Subscriber) Pattern

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.

image

image

image

The IObserver<T> is implemented on classes that want to know about the updates in the IObservable<T>-derived classes.

image

To tie them together, merely subscribe the observers to the data generator:

image

The output is something like this:

image

Use an Event Broker

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:

image

image

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:

image

image

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.

Note

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.

Remember the Screen Location

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”).

image

image

Refer to the SaveScreenLocation project in the accompanying source code to see this code in practice.

Implement Undo Using Command Objects

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).

Figure 25.1 This simple application allows the user to undo moves, creations, and deletions.

image

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.

Note

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.

Define the Command Interface and History Buffer

Here’s a possible interface:

image

We also need a way to track all our commands in the order they were issued:

image

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:

image

Define Command Functionality

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):

image

Here’s the CreateWidgetCommand object, which takes a different type of state:

image

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.

image

image

image

image

image

image

Note

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.

Use Model-View-ViewModel in 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.

Figure 25.2 Very simple pieces can be combined to form elegant WPF applications that are easy to extend and maintain.

image

Define the Model

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.

image

image

image

As you can see, this data model has no notion of anything related to WPF.

Define the ViewModel

Because we’ll have multiple ViewModel classes in this app, and they have some common functionality, let’s define a base class:

image

image

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:

image

The other ViewModel is for the view that will show a list of all Widget objects:

image

Define the View

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.

Listing 25.1 AllWidgetsView.xaml

image

The Widget-specific view displays a single widget in a graphical way (see Listing 25.2).

Listing 25.2 WidgetGraphicView.xaml

image

Put Commands into the ViewModel

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:

image

image

Now we can define the MainWindowViewModel:

image

image

Now it’s just a matter of binding the MainView parts to properties of the ViewModel (see Listing 25.3).

Listing 25.3 Mainwindow.xaml

image

image

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.

Note

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.

Understand Localization

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.

Note

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.

Note

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.

Localize a Windows Forms Application

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;

Localize an ASP.NET Application

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.

image

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:

image

To test your web application, make sure that Culture and uiCulture values for the pages are set to Auto (the default):

image

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....

Localize a WPF Application

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.

Table 25.1 Pros and Cons of WPF Localization techniques

image

XAML Localization

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:

image

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.

Note

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).

Note

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:

image

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.

image

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.

Figure 25.3 The localized app running in the default culture.

image

Figure 25.4 Changing the system’s region to Italy causes the app to pick the localized DLL. If Windows were using the Italian language pack, then the Yes and No on the message box would also be in Italian.

image

See the LocWPFXAML project in the sample code for a full example.

Resource File Localization

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:

image

Note

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.

Localize a Silverlight Application

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:

image

10. Add a reference to the wrapper class to the Application.Resources section of App.xaml:

image

11. Bind UI elements to the resources:

image

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:

image

Note

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.

Deploy Applications Using ClickOnce

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.

Figure 25.5 The Publish options allow you to specify where to put the setup files.

image

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.

Figure 25.6 In a partial-trust application, trying to access local resources such as the file system will result in an exception.

image

Note

You cannot create WPF windows in a partial-trust environment: You’re limited to the browser window.

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

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