Designing and Developing Windows Applications Using Microsoft .NET Framework 4: Designing the Presentation Layer
- 12/17/2011
- Objective 2.1: Choose the Appropriate Windows Technology
- Objective 2.2: Design the UI Layout and Structure
- Objective 2.3: Design Application Workflow
- Objective 2.4: Design Data Presentation and Input
- Objective 2.5: Design Presentation Behavior
- Objective 2.6: Design for UI Responsiveness
- Objective 2.6: Design for UI Responsiveness
- Chapter Summary
- Answers
Objective 2.2: Design the UI Layout and Structure
The layout of an application can mean the difference between an application that is easy to use and efficient and an application that is complicated and frustrates the user. Careful evaluation of the application design is vital to serving the needs of your users. In this section, you will learn how to make decisions that are important to the design of the layout.
Evaluate the Conceptual Design
The design of your UI should fulfill the needs of the application, the client, and the user. The interface should enable the user to complete tasks in the application quickly and easily and without distraction. A good UI will be internally and externally consistent and allow the user to learn how to use the application intuitively. In their book Software for Use: A Practical Guide to the Models and Methods of Usage-Centered Design, authors Larry Constantine and Lucy Lockwood describe the following principles for UI design:
Design for structure A well-structured UI is logical, consistent, and intuitive. Use a model when designing your UI. Put elements with related functionality together in logically organized groups. Functional groups should be easily recognizable by the user, and dissimilar items or functional groups should be easily differentiated. A well-structured UI will not have users hunting blindly for desired functionality but will allow them to find what they need intuitively.
Design for simplicity A well-designed UI is simple and easy to use. Common tasks are easy to access, and navigation to more infrequently used tasks is uncomplicated and intuitive. The use of menu items for common tasks is an example of incorporating simplicity into a UI.
Design for visibility A good UI will have necessary functional units easily visible and accessible to the user. It is good to keep simplicity in mind with this principle, however, as an overly visible UI is likely to be cluttered, presenting the user with too many options and thwarting the intent of being easy to use.
Design for feedback A good UI will keep the user informed as to changes in the state of the application. Errors that are relevant to the user or actions that need to be performed by the user should be communicated clearly and concisely. Conversely, the internal state of the application, if it does not require user input, should not be unnecessarily exposed.
Design for tolerance Users make mistakes. A well-designed UI will allow the user to recover quickly and easily from mistakes. User interfaces should allow for faulty input to be easily and rapidly corrected and for mistaken changes to be rolled back.
Design for reuse A UI should be consistent. Components and visual styles should be reused whenever possible. Not only does this reduce the amount of time spent coding but it also helps your UI to be consistent and purposeful.
Designing for Inheritance and the Reuse of Visual Elements
Both Windows Forms and WPF allow you to extend and reuse controls and forms in your application. Windows Forms allows extending existing controls and forms through visual inheritance, and WPF takes it further by allowing the visual appearance and behavior of every WPF element to be modified through the use of styles. WPF further allows the reuse of components designated as resources.
Creating Extended Controls in Windows Forms
Extended controls are user-created controls that extend a preexisting .NET Framework control. By extending existing controls, you can retain all the functionality of the control but add properties and methods and, in some cases, alter the rendered appearance of the control.
Extending a Control
You can create an extended control by creating a class that inherits the control in question. The following example demonstrates how to create a control that inherits the Button class:
Sample of Visual Basic.NET Code
Public Class ExtendedButton Inherits System.Windows.Forms.Button End Class
Sample of C# Code
public class ExtendedButton : System.Windows.Forms.Button {}
The ExtendedButton class created in the previous example has the same appearance, behavior, and properties as the Button class, but now you can extend this functionality by adding custom properties or methods. You also can override existing methods to incorporate custom functionality.
Extending a Form
To create a cohesive look and feel to your application, you might want to start with a base form that all other forms in the application derive from. You can extend preexisting forms in the same way that you extend controls. The following example shows how to inherit from a previously created form named BaseForm:
Sample of Visual Basic.NET Code
Public Class ExtendedForm Inherits MyProject.BaseForm ' Implementation omitted End Class
Sample of C# Code
public class ExtendedForm : MyProject.BaseForm { // Implementation omitted }
Creating Custom Dialog Boxes
Dialog boxes are commonly used to gather information from the application user. Microsoft Visual Studio provides prebuilt dialog boxes that enable the user to select a file, font, or color. You can create custom dialog boxes to collect specialized information from the user. For example, you might create a dialog box that collects user information and relays it to the main form of the application.
A dialog box generally includes an OK button, a Cancel button, and whatever controls are required to gather information from the user. The general behavior of an OK button is to accept the information provided by the user and then to close the form, returning a result of DialogResult.OK. The general behavior of the Cancel button is to reject the user input and close the form, returning a result of DialogResult.Cancel.
A modal dialog box is a dialog box that pauses program execution until the dialog box is closed. Conversely, a modeless dialog box allows application execution to continue. You can display any form as a modal dialog box by calling the ShowDialog method.
Using Styles in WPF
Styles can be thought of as analogous to cascading style sheets as used in HTML pages. Styles basically tell the Presentation layer to substitute a new visual appearance for the standard one. They allow you to make changes easily to the UI as a whole and to provide a consistent look and feel for your application in a variety of situations. Styles enable you to set properties and hook up events on UI elements through the application of those styles. Further, you can create visual elements that respond dynamically to property changes through the application of triggers, which listen for a property change and then apply style changes in response.
The primary class in the application of styles is, unsurprisingly, the Style class. The Style class contains information about styling a group of properties. A Style can be created to apply to a single instance of an element, to all instances of an element type, or across multiple types. The important properties of the Style class are shown in Table 2-4.
Table 2-4 Important Properties of the Style Class
Property |
Description |
BasedOn |
Indicates another style that this style is based on. This property is useful for creating inherited styles. |
Resources |
Contains a collection of local resources used by the style. |
Setters |
Contains a collection of Setter or EventSetter objects. These are used to set properties or events on an element as part of a style. |
TargetType |
This property identifies the intended element type for the style. |
Triggers |
Contains a collection of Trigger objects and related objects that allow you to designate a change in the UI in response to changes in properties. |
The basic skeleton of a <Style> element in XAML markup looks like the following:
<Style> <!-- A collection of setters is enumerated here --> <Style.Triggers> <!-- A collection of Trigger and related objects is enumerated here --> </Style.Triggers> <Style.Resources> <!-- A collection of local resources for use in the style --> </Style.Resources> </Style>
Setters
The most common class you will use in the construction of styles is the Setter. As their name implies, Setters are responsible for setting some aspect of an element. Setters come in two flavors: property setters (or just Setters, as they are called in markup), which set values for properties; and event setters, which set handlers for events.
Property Setters
Property setters, represented by the <Setter> tag in XAML, allow you to set properties of elements to specific values. A property setter has two important properties: the Property property, which designates the property that is to be set by the Setter, and the Value property, which indicates the value to which the property is to be set. The following example demonstrates a Setter that sets the Background property of a Button element to Red:
<Setter Property="Button.Background" Value="Red" />
The value for Property must take the following form:
Element.PropertyName
If you want to create a style that sets a property on multiple different types of elements, you could set the style on a common class that the elements inherit, as shown here:
<Style> <Setter Property="Control.Background" Value="Red" /> </Style>
This style sets the Background property of all elements that inherit from the Control to which it is applied.
Event Setters
Event setters (represented by the <EventSetter> tag) are similar to property setters, but they set event handlers rather than property values. The two important properties for an EventSetter are the Event property, which specifies the event for which the handler is being set; and the Handler property, which specifies the event handler to attach to that event. An example is shown here:
<EventSetter Event="Button.MouseEnter" Handler="Button_MouseEnter" />
The value of the Handler property must specify an extant event handler with the correct signature for the type of event with which it is connected. Similar to property setters, the format for the Event property is <Element>.<EventName>, where the element type is specified, followed by the event name.
Using Resources in WPF
Logical resources allow you to define objects in XAML that are not part of the visual tree but are available for use by WPF elements in your UI. Elements in your UI can access the resource as needed. An example of an object that you might define as a resource is a Brush used to provide a common color scheme for the application.
By defining objects that are used by several elements in a Resources section, you gain a few advantages over defining the object each time you use it. First, you gain reusability because you define your object only once rather than multiple times. You also gain flexibility—by separating the objects used by your UI from the UI itself, you can refactor parts of the UI without having to redesign it completely. For example, you might use different collections of resources for different cultures in localization or for different application conditions.
Any type of object can be defined as a resource. Every WPF element defines a Resources collection, which can be used to define objects that are available to that element and the elements in its visual tree. Although it is most common to define resources in the Resources collection of the Window, you can define a resource in any element’s Resources collection and access it, so long as the accessing element is part of the defining element’s visual tree.
Declaring a Logical Resource
You declare a logical resource by adding it to a Resources collection, as seen here:
<Window.Resources> <RadialGradientBrush x:Key="myBrush"> <GradientStop Color="CornflowerBlue" Offset="0" /> <GradientStop Color="Crimson" Offset="1" /> </RadialGradientBrush> </Window.Resources>
If you don’t intend a resource to be available to the entire Window, you can define it in the Resources collection of an element in the Window, as shown in this example:
<Grid> <Grid.Resources> <RadialGradientBrush x:Key="myBrush"> <GradientStop Color="CornflowerBlue" Offset="0" /> <GradientStop Color="Crimson" Offset="1" /> </RadialGradientBrush> </Grid.Resources> </Grid>
The usefulness of this is somewhat limited, and the most common scenario is to define resources in the Window.Resources collection. One point to remember is that when using static resources, you must define the resource in the XAML code before you refer to it. Static and dynamic resources are explained later in this section.
Every object declared as a Resource must set the x:Key property. This is the name that will be used by other WPF elements to access the resource. There is one exception to this rule: Style objects that set the TargetType property do not need to set the x:Key property explicitly because it is set implicitly, behind the scenes. In the previous two examples, the key is set to myBrush.
The x:Key property does not have to be unique in the application, but it must be unique in the Resources collection where it is defined. Thus, you could define one resource in the Grid.Resources collection with a key of myBrush and another in the Window.Resources collection with the same key. Objects within the visual tree of the Grid that refer to a resource with the key myBrush refer to the object defined in the Grid.Resources collection, and objects that are not in the visual tree of the Grid but are within the visual tree of the Window refer to the object defined in the Window.Resources collection.
Application Resources
In addition to defining resources at the level of the element or Window, you can define resources that are accessible by all objects in a particular application. You can create an application resource by opening the App.xaml file (for C# projects) or the Application.xaml file (for Visual Basic projects) and adding the resource to the Application .Resources collection, as shown in bold here:
<Application x:Class="WpfApplication2.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" StartupUri="Window1.xaml"> <Application.Resources> <SolidColorBrush x:Key="appBrush" Color="PapayaWhip" /> </Application.Resources> </Application>
Accessing a Resource in XAML
You can access a resource in XAML by using the following syntax:
{StaticResource myBrush}
In this example, the markup declares that a static resource with the key myBrush is accessed. Because this resource is a Brush object, you can plug that markup into any place that expects a Brush object. This example demonstrates how to use a resource in the context of a WPF element:
<Grid Background="{StaticResource myBrush}"> </Grid>
When a resource is referred to in XAML, the Resources collection of the declaring object first is searched for a resource with a matching key. If one is not found, the Resources collection of that element’s parent is searched, and so on, up to the Window that hosts the element and to the application Resources collection.
Static and Dynamic Resources
In addition to the syntax described previously, you can refer to a resource with the following syntax:
{DynamicResource myBrush}
The difference between the syntax of DynamicResource and the syntax of StaticResource lies in how the resources are retrieved by the referring elements. Resources referred to by the StaticResource syntax are retrieved once by the referring element and used for the lifetime of the resource. Resources referred to with the DynamicResource syntax, on the other hand, are acquired every time the referred object is used.
It might seem intuitive to think that if you use the StaticResource syntax, the referring object does not reflect changes to the underlying resource, but this is not necessarily the case. WPF objects that implement dependency properties automatically incorporate change notification, and changes made to the properties of the resource are picked up by any objects using that resource. Take the following example:
<Window x:Class="WpfApplication2.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Window1" Height="300" Width="300"> <Window.Resources> <SolidColorBrush x:Key="BlueBrush" Color="Blue" /> </Window.Resources> <Grid Background="{StaticResource BlueBrush}"> </Grid> </Window>
This code renders the Grid in the Window with a Blue background. If the color property of the SolidColorBrush object defined in the Window.Resources collection were changed in code to Red, for instance, the background of the Grid would render as Red because change notification would notify all objects using that resource that the property had changed.
The difference between static and dynamic resources comes when the underlying object changes. If the Brush defined in the Windows.Resources collection were accessed in code and set to a different object instance, the Grid in the previous example would not detect this change. However, if the Grid used the following markup, the change of the object would be detected and the Grid would render the background with the new Brush:
<Grid Background="{DynamicResource BlueBrush}"> </Grid>
Accessing resources in code is discussed in the not found “Retrieving Resources in Code” section later in this chapter.
The downside of using dynamic resources is that they tend to decrease application performance. Because the resources are retrieved every time they are used, dynamic resources can reduce the efficiency of an application. The best practice is to use static resources unless there is a specific reason for using a dynamic resource. Examples of when you would want to use a dynamic resource include when you use the SystemBrushes, SystemFonts, and SystemParameters classes as resources, or any other time when you expect the underlying object of the resource to change.
Creating a Resource Dictionary
A resource dictionary is a collection of resources that reside in a separate XAML file and can be imported into your application. They can be useful for organizing your resources in a single place or for sharing resources between multiple projects in a single solution. The following procedure describes how to create a new resource dictionary in your application:
To create a resource dictionary
From the Project menu, choose Add Resource Dictionary. The Add New Item dialog box opens. Choose the name for the resource dictionary and click Add. The new resource dictionary is opened in XAML view.
Add resources to the new resource dictionary in XAML view. You can add resources to the file in XAML view, as shown in bold here:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <SolidColorBrush x:Key="appBrush" Color="DarkSalmon" /> </ResourceDictionary>
Merging Resource Dictionaries
For objects in your application to access resources in a resource dictionary, you must merge the resource dictionary file with a Resources collection that is accessible in your application, such as the Windows.Resources or Application.Resources collection. You merge resource dictionaries by adding a reference to your resource dictionary file in the ResourceDictionary.MergedDictionaries collection. The following example demonstrates how to merge the resources in a Windows.Resources collection with the resources in resource dictionary files named Dictionary1.xaml and Dictionary2.xaml:
<Window.Resources> <ResourceDictionary> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="Dictionary1.xaml" /> <ResourceDictionary Source="Dictionary2.xaml" /> </ResourceDictionary.MergedDictionaries> <SolidColorBrush x:Key="BlueBrush" Color="Blue" /> </ResourceDictionary> </Window.Resources>
Note that if you define additional resources in your Resources collection, they must be defined within the bounds of the <ResourceDictionary> tags.
Choosing Where to Store a Resource
You have seen several options regarding where resources should be stored. The factors that should be weighed when deciding where to store a resource include ease of accessibility by referring elements, readability and maintainability of the code, and reusability.
For resources to be accessed by all elements in an application, you should store resources in the Application.Resources collection. The Window.Resources collection makes resources available only to elements in that Window, but that is typically sufficient for most purposes. If you need to share individual resources over multiple projects in a solution, your best choice is to store your resources in a resource dictionary that can be shared among different projects.
Readability is important for enabling maintenance of your code by other developers. The best choice for readability is to store resources in the Window.Resources collection because this allows developers to read your code in a single file rather than having to refer to other code files.
If making your resources reusable is important, then the ideal method for storing them is to use a resource dictionary. This allows you to reuse resources among different projects and to extract those resources easily for use in other solutions as well.
Designing for Accessibility
The workforce contains a significant number of people with accessibility requirements, requiring applications that meet the broad demands of today’s business environment. Accessible applications begin in the design phase. When you plan for accessibility in application design, you can integrate the principles of accessibility into the UI. Some of these principles are:
Flexibility
Choice of input and output methods
Consistency
Compatibility with accessibility aids
An accessible program requires flexibility. Users must be able to customize the UI to suit their specific needs—for example, by increasing font sizes. A user also should have a choice of input methods, such as keyboard and mouse devices. That is, the application should provide keyboard access for all important features and mouse access for all main features. A choice of output methods also renders an application more accessible, and the user should have the ability to choose among visual cues, sounds, text, and graphics. An accessible application should interact within its own operation and with other applications in a consistent manner, and it should be compatible with existing accessibility aids.
Support Standard System Settings
For your application to be accessible, it must support standard system settings for size, color, font, and input. Adopting this measure will ensure that all users’ applications have a consistent UI that conforms to the system settings. Users with accessibility needs can thus configure all their applications by configuring their system settings.
You can implement standard system settings in your application by using the classes that represent the UI options used by the system. Table 2-5 lists the classes that expose the system settings. These classes are found in the System.Drawing namespace.
Table 2-5 Classes That Expose System Settings
Class |
Description |
SystemBrushes |
Exposes Brush objects that can be used to paint in the system colors |
SystemColors |
Exposes the system colors |
SystemFonts |
Exposes the fonts used by the system |
SystemIcons |
Exposes the icons used by the system |
SystemPens |
Exposes Pen objects that can be used to draw in the system colors |
These classes monitor changes to the system settings and adjust correspondingly. For example, if you build an application that uses the SystemFonts class to determine all the fonts, the fonts in the application will be reset automatically when the system settings are changed.
Ensure Compatibility with the High-Contrast Option
The high-contrast option (which users can set themselves in the Control Panel) sets the Windows color scheme to provide the highest possible level of contrast in the UI. This option is useful for users requiring a high degree of legibility.
By using only system colors and fonts, you can ensure that your application is compatible with the high-contrast settings. You also should avoid the use of background images because they tend to reduce contrast in an application.
Provide Documented Keyboard Access to All Features
Your application should provide keyboard access for all features and comprehensive documentation that describes this access. Shortcut keys for controls and menu items, as well as setting the Tab order for controls on the UI, allow you to implement keyboard navigation in your UI. Documentation of these features is likewise important. A user must have some means of discovering keyboard access to features, whether that is through UI cues or actual documentation.
Provide Notification of the Keyboard Focus Location
The location of the keyboard focus is used by accessibility aids such as Magnifier and Narrator. Thus, it is important that the application and the user have a clear understanding of where the keyboard focus is at all times. For most purposes the .NET Framework provides this functionality, but when designing your program flow, you should incorporate code to set the focus to the first control on a form when the form is initially displayed and the Tab order should follow the logical program flow.
Convey No Information by Sound Alone
Although sound is an important cue for many users, an application should never rely on conveying information by using sound alone. When using sound to convey information, you should combine that with a visual notification, such as flashing the form or displaying a message box.
Accessibility Properties of Windows Forms Controls
In addition to properties that affect the visual interface of a control, Windows Forms controls have five properties related to accessibility that determine how the control interacts with accessibility aids. These properties are summarized in Table 2-6.
Table 2-6 Accessibility Properties of Windows Controls
Property |
Description |
AccessibleDefaultActionDescription |
Contains a description of the default action of a control. This property cannot be set at design time and must be set in code. |
AccessibleDescription |
Contains the description that is reported to accessibility aids. |
AccessibleName |
Contains the name that is reported to accessibility aids. |
AccessibleRole |
Contains the role that is reported to accessibility aids. This value is a member of the AccessibleRole enumeration, and a variety of accessibility aids use it to determine what kind of UI element an object is. |
AccessibilityObject |
Contains an instance of AccessibleObject, which provides information about the control to usability aids. This property is read-only and set by the designer. |
These properties provide information to accessibility aids about the role of the control in the application. Accessibility aids then can present this information to the user or make decisions about how to display the control.
Deciding When Custom Controls Are Needed
User controls, custom controls, and templates all allow you to create custom elements with custom appearances. Because each of these methods is so powerful, you might be confused about what technique to use when creating a custom element for your application. The key to making the right decision isn’t based on the appearance that you want to create, but rather the functionality that you want to incorporate into your application.
The standard WPF controls provide a great deal of built-in functionality. If the functionality of one of the preset controls, such as a progress bar or a slider, matches the functionality that you want to incorporate, then you should create a new template for that preexisting control to achieve your visual appearance goals. Creating a new template is the lightest solution to creating a custom element, and you should consider that option first.
If the functionality that you want to incorporate into your application can be achieved through a combination of preexisting controls and code, you should consider creating a user control. User controls allow you to bind together multiple preexisting controls in a single interface and add code that determines how they behave.
If no preexisting control or combination of controls can approach the functionality that you want to create, then you should create a custom control. Custom controls allow you to create a completely new template that defines the visual representation of the control and add completely custom code that determines the control’s functionality.
Objective Summary
The conceptual design should be evaluated for structure, simplicity, visibility, feedback, tolerance, and reuse.
Whenever possible, code should be inherited and reused. You can extend controls and forms in Windows Forms applications and reuse styles and resources in WPF.
When designing for accessibility, you should support standard system settings, ensure compatibility with the high-contrast option, provide documented keyboard access to all features, provide notification of the keyboard focus location, and convey no information by sound alone.
User controls should be employed when you want to bind multiple preexisting controls into a single functional unit. Custom controls should be used when no preexisting control or controls incorporate the functionality you desire.
Objective Review
Answer the following questions to test your knowledge of the information in this objective. You can find the answers to these questions and explanations of why each answer choice is correct or incorrect in the “Answers” section at the end of the chapter.
Which of the following is not a best practice for designing for accessibility?
Provide audio for all important information.
Support standard system settings.
Ensure compatibility with the high-contrast mode.
Provide keyboard access to all important functionalities.
You have created a series of customized Brush objects that will be used to create a common color scheme for every window in each of several applications in your company. The Brush objects have been implemented as resources. What is the best place to define these resources?
In the Resources collection of each control that needs them
In the Resources collection of each Window that needs them
In the Application.Resources collection
In a separate resource dictionary