Extending Application Usability


When it comes to enterprise application development, building an application that is scalable and reliable comes first. You should always implement extended usability features last, when basic functionality is near completion. That being said, completed applications or first release applications can always benefit from usability improvements. These improvements can include improving responsiveness from the application, making data management more convenient , remembering user preferences, or localizing the user interface for a target culture.

Keeping the Application Responsive with Threads

Improving application responsiveness is always a big win with customers. In the information age, nobody wants to wait any longer than absolutely necessary, especially when it comes to data entry or data analysis. Applications that demonstrate any sort of lag can be perceived as slow and undesirable. When time permits , you should profile and review code to find room for improvement.

One common problem that desktop applications experience is the feeling of a "locked-up" application when a lengthy function is in progress. This is often attributed to a process that does not yield any processor cycles back to the user interface to refresh. When a user switches to another application and then switches back, a hole in the application remains.

Lengthy functions should execute on separate threads of execution. Listing 8-16 presents the same code as Listing 8-5 with one exception: A new call is made to a method named StallForTime.

Listing 8-16: Adding a Long Delay Method in Response to User Action
start example
 private void listView1_DoubleClick(object sender, System.EventArgs e) {     //call a really long process...     StallForTime();     Issue myIssue = new Issue();     myIssue.IssueID = 101;     Controller.Process( myIssue, Controller.ControllerActions.View ); } 
end example
 

As Listing 8-17 reveals, the StallForTime method is true to its name and eats up processor cycles while the user waits for the Controller object to present the next view. If the user performs a context switch to another application and back, the IssueTracker application will only show an application frame and no user interface. Assuming that the next view does not depend on the results of the lengthy process, the StallForTime method can execute within its own thread of execution.

Listing 8-17: Implementing a Simple Delay Method
start example
 public void StallForTime() {     for( int i = 0; i < 100000; i++ )     {         for( int j = 0; j < 100000; j++ )             ;     }     System.Diagnostics.Debug.WriteLine( "Finished!" ); } 
end example
 

Listing 8-18 creates a separate line of execution for the StallForTime method. Two lines of code and no changes to the lengthy method result in a clean and quick user interface. This time, as the application is running, the form is able to repaint itself as it becomes invalidated.

Listing 8-18: Shifting the Delay Method to Another Thread for Better Application Response
start example
 private void listView1_DoubleClick(object sender, System.EventArgs e) {     //call a really long process...     Thread threadProcess = new Thread(  new ThreadStart( StallForTime )  );     threadProcess.Start();     Issue myIssue = new Issue();     myIssue.IssueID = 101;     Controller.Process( myIssue, Controller.ControllerActions.View ); } 
end example
 

Adding Drag and Drop Functionality

Drag and drop functionality can be effective at streamlining user steps within an enterprise application. You can accomplish this implementation by handling a series of events, such as DragEnter, DragLeave, and DragDrop. These events store all of the necessary information to carry out a drag and drop process within their event arguments.

Dragging Data from a Control

Dragging operations typically begin with a MouseDown event generated by a control containing an object to be dragged. In the case of FormMain, you select the lstIssues list view control and implement a new event handler to respond to the MouseDown event (see Listing 8-19).

Listing 8-19: Beginning the Drag Process
start example
 private void lstIssues_MouseDown( object sender,     System.Windows.Forms.MouseEventArgs e ) {     ListViewItem itemSelected = lstIssues.SelectedItems[0];     lstIssues.DoDragDrop( itemSelected.SubItems[0].Text,          System.Windows.Forms.DragDropEffects.Copy );     return; } 
end example
 

The MouseDown event handler responds to the event by invoking the DoDragDrop method belonging to the originating control. This method takes two parameters. The first parameter represents the object being dragged. You can use any data, from a simple string to a complex object, as a parameter. In this case, the data object is a string that represents the numeric value of the selected Issue object. The second parameter describes how the data is to be dragged. Table 8-3 summarizes the drag methods supported by the DragDropEffects structure.

Table 8-3: The DragDropEffects Elements Identifying How Data Should Be Dragged

PROPERTY

DESCRIPTION

All

The data is copied , removed from the drag source, and scrolled in the drop target.

Copy

The data is copied to the drop target.

Link

The data from the drag source is linked to the drop target.

Move

The data from the drag source is moved to the drop target.

None

The drop target does not accept the data.

Scroll

Scrolling is about to start or is currently occurring in the drop target.

Because the list view and tree view controls are used most often for initiating drag and drop operations, they both have a specific event handler, ItemDrag, for this purpose. Listing 8-19 would more accurately look like Listing 8-20.

Listing 8-20: Beginning the Process with a Drag
start example
 private void lstIssues_DragLeave( object sender,     System.Windows.Forms.MouseEventArgs e ) {     ListViewItem itemSelected = lstIssues.SelectedItems[0];     lstIssues.DoDragDrop( itemSelected.SubItems[0].Text,          System.Windows.Forms.DragDropEffects.Copy );     return; } 
end example
 

Dropping Data into a Control

Once dragging has started, something needs to accept the drop. The cursor changes when it crosses over areas of the form where dropping is supported. Areas can accept dropped data by setting the AllowDrop property and handling the DragEnter and DragDrop events.

In the Properties window, set the AllowDrop property to true. Next, add an event handler to respond to the DragEnter event. Listing 8-21 implements the DragEnter event handler. An If statement performs type-checking to ensure the data being dragged is an acceptable type. Next, the code sets the effect that will happen when the drop occurs in the DragDropEffects property.

Listing 8-21: Determining If a Drop Should Be Allowed
start example
 private void FormMain_DragEnter( object sender,      System.Windows.Forms.DragEventArgs e ) {     if( e.Data.GetDataPresent( DataFormats.Text ) )         e.Effect = DragDropEffects.Copy;     else         e.Effect = DragDropEffects.None;     return; } 
end example
 

You can define custom DataFormats as long as the custom object is specified as serializable. Next, you need to implement the DragDrop event handler, as outlined in Listing 8-22. The GetData method retrieves the data being dragged.

Listing 8-22: Ending the Process with a Drop
start example
 private void FormMain_DragDrop( object sender,      System.Windows.Forms.DragEventArgs e ) {      //close the active MDI child      if( this.ActiveMdiChild != null )           this.ActiveMdiChild.Close();      //initialize the new MDI child      this.dlgViewIssue = new FormIssueView();      dlgViewIssue.MdiParent = this;      dlgViewIssue.Dock = System.Windows.Forms.DockStyle.Fill;     //supply the IssueID carried through the Drag process     dlgViewIssue.SetIssueID( int.Parse( e.Data.GetData(          DataFormats.Text ).ToString() ) );     //display the new MDI child     dlgViewIssue.Show();     return; } 
end example
 

Saving User Preferences with the System Registry

Enterprise applications will often need to store user preferences within the Windows system Registry. The Registry hosts information from the operating system as well as information from applications hosted on the machine. Working with the Registry may compromise security if it stores plain-text passwords and other sensitive information. You should give careful attention to what is stored in the Registry to ensure that it poses no threat to system or user security.

Reading and Writing Registry Keys

Each Registry entry comprises two primary elements: name and value. Each entry is also stored in a collection of keys and subkeys. Any parent key may have one or more child keys, each with one or more names and values. Figure 8-14 shows a snapshot of the Registry Editor highlighting the IssueTracker application settings node.

click to expand
Figure 8-14: Viewing the system Registry with the Registry Editor

The Registry and RegistryKey classes, provided by the .NET Framework, offer services to read and write Registry settings. To include support for system Registry access, include the necessary namespace:

 using Microsoft.Win32; 

The Registry class implements the base Registry keys that can access Registry subkeys and their values. Listing 8-23 shows the application's FormLoad event handler and how it accesses application settings from the Registry

Listing 8-23: Reading from the System Registry at the Start of the Application
start example
 private void FormMain_Load(object sender, System.EventArgs e) {     RegistryKey regkeyAppRoot =         Registry.CurrentUser.CreateSubKey("Software\IssueTracker\Settings");     String strWindowState = (String)regkeyAppRoot.GetValue("WindowState");     if( strWindowState != null && strWindowState.CompareTo("Maximized") == 0 )         WindowState = System.Windows.Forms.FormWindowState.Maximized;     else if(strWindowState != null && strWindowState.CompareTo("Minimized") == 0)         WindowState = FormWindowState.Minimized;     else         WindowState = FormWindowState.Normal;     return; } 
end example
 

The RegistryKey object holds a reference to the specified subkey . Its GetValue method retrieves the Registry value and assigns it to a string variable. Because the return value is a string, it is evaluated to set the window state as maximized or minimized. Listing 8-24 writes the window state value to the Registry upon application closing.

Listing 8-24: Writing to the System Registry upon Application Close
start example
 private void FormMain_Closing( object sender,     System.ComponentModel.CancelEventArgs e ) {     //save the window state     String strPath = "Software\IssueTracker\Settings";     String strWindowState = "";     RegistryKey  regkeyAppRoot = Registry.CurrentUser.CreateSubKey( strPath );     if( WindowState == FormWindowState.Maximized )         strWindowState = "Maximized";     else if( WindowState == FormWindowState.Minimized )         strWindowState = "Minimized";     else         strWindowState = "Normal";     regkeyAppRoot.SetValue( "WindowState", strWindowState );     return; } 
end example
 

Evaluating Registry Access Permissions

The RegistryPermission class, which is in the System.Security.Permission namespace, controls the ability to access Registry variables. Registry variables should not be stored in memory locations where code without RegistryPermission can access them. Similarly, when granting permissions, grant the least privilege necessary to get the job done. For more information, see RegistryPermission and System.Security.Permissions.

Registry permission access values are defined by the RegistryPermissionAccess enumeration. Table 8-4 details the members of the RegistryPermissionAccess enumeration.

Table 8-4: The Registry Access Permissions

VALUE

DESCRIPTION

AllAccess

Create, read, and write access to Registry variables

Create

Create access to Registry variables

NoAccess

No access to Registry variables

Read

Read access to Registry variables

Write

Write access to Registry variables

You can create combinations of permissions, such as permitting read and write access while denying create access, with a bitwise OR operation. Also, when working with deployment projects, you can also use the Registry Editor to specify Registry keys and values that should be added to the Registry of the target computer.

Building Internationalization into the Application

When developing world-ready applications, you must focus attention on a variety of issues throughout the application design and development process. Addressing internationalization requirements early in the design phase will minimize the amount of time and money required to produce quality localized applications for the languages that are intended to be supported.

Targeting a Culture with CultureInfo

The CultureInfo object manages culture-specific information, such as the associated language, sublanguage, country/region, calendar, and cultural conventions. Additional attributes of the CultureInfo object manage the default formats for dates, times, currency, and numbers :

 using System.Globalization; 

Although the CultureInfo object is not a language setting, it does contain information related to settings for a geographical region. Table 8-5 presents a sample of the available culture codes available to the enterprise developer.

Table 8-5: Abridged List of the Cultural Identifiers Supported by the CultureInfo Object

CULTURE CODE

LANGUAGE, COUNTRY

 

Invariant culture

ar-SA

Arabic, Saudi Arabia

ar-AE

Arabic, United Arab Emirates

zh-HK

Chinese, Hong Kong SAR

zh-CN

Chinese, China

da-DK

Danish, Denmark

nl-BE

Dutch, Belgium

nl-NL

Dutch, The Netherlands

en-AU

English, Australia

en-CA

English, Canada

en-GB

English, United Kingdom

en-US

English, United States

fr-BE

French, Belgium

fr-CA

French, Canada

fr-FR

French, France

de-AT

German, Austria

de-DE

German, Germany

el-GR

Greek, Greece

it-IT

Italian, Italy

it-CH

Italian, Switzerland

ru-RU

Russian, Russia

es-MX

Spanish, Mexico

es-ES

Spanish, Spain

The CultureInfo object provides a number of useful properties. Among them, CurrentCulture is most often referenced to programmatically set the cultural preference. To set an application's cultural setting to German in Germany, set the CurrentCulture to de-DE:

 Thread.CurrentThread.CurrentCulture = new CultureInfo("de-DE"); 

You represent a culture by both a language and a region because a language is often spoken in more than one country or region. In the case of German, the language is also popularly spoken within Austria and Switzerland.

By default, the Windows operating system sets the CurrentCulture property. The user sets this property by changing the User Locale through the Regional Options dialog box in the Control Panel or by changing settings related to user locale, such as currency, number, date, and time formats.

To ensure that an application uses the default formats provided by the .NET Framework for currency, numbers, date, and time for a specified culture, override the User Locale defaults in your application's code. To do this, create a CultureInfo object with the useUserOverride parameter set to false. The default settings on the user's system will be overridden by the .NET Framework's default settings.

Formatting Foreign Dates and Times

The DateTime structure provides methods such as ToString and Parse that perform culture-sensitive operations on a DateTime object. The DateTimeFormatInfo object typically formats and displays the value of a DateTime object based on a specific culture. The DateTimeFormatInfo object defines how DateTime values are formatted and displayed, depending on the culture.

In the IssueTracker application, you can modify the timer event handler to display the current date in the status bar, formatted to either the English or German culture (see Listing 8-25). Using ShortDatePattern, the date February 7, 2001 is formatted as 2/7/2001 for the English (en-US) culture and 07.02.2001 for the German (de-DE) culture.

Listing 8-25: An Updated Timer Event Handler Displaying the Date in a Different Culture
start example
 private void timerMain_Tick(object sender, System.EventArgs e) {     String strDateOutput = "";     DateTime dateNow = DateTime.Now;     // Sets the CurrentCulture property to U.S. English.     Thread.CurrentThread.CurrentCulture = new CultureInfo( "en-US" );     // Displays dt, formatted using the ShortDatePattern     // and the CurrentThread.CurrentCulture.     strDateOutput = dateNow.ToString( "d" );     // Creates a CultureInfo for German in Germany.     CultureInfo cultureinfo = new CultureInfo( "de-DE" );     // Displays dt, formatted using the ShortDatePattern     // and the CultureInfo.     strDateOutput += " [";     strDateOutput += dateNow.ToString( "d", cultureinfo );     strDateOutput += "]";     statusbarMain.Panels[0].Text = strDateOutput;     return; } 
end example
 

When working with methods provided by the DateTime structure, be aware that the members such as DateTime.Day, DateTime.Month, and DateTime.Year are based on the Gregorian calendar. Even if the current calendar changes, the Gregorian calendar still performs the calculations. This prevents the mathematics performed by the methods from being corrupted by a user's settings. Listing 8-26 shows how to display a message to the user that displays a date 30 days from the current date.

Listing 8-26: Performing Date Mathematics with the DateTime Object
start example
 public void DisplayReminderMessage() {     Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");     DateTime dateNow = DateTime.Now;     dateNow = dateNow.AddDays(30);     MessageBox.Show( "A 30 Day reminder will be sent on: " +         dateNow.ToString("d") );     return; } 
end example
 

Formatting Foreign Numbers

Another property of the CultureInfo object is NumberFormat, which interacts with the NumberFormatInfo object to define how currency, decimal separators, and other numeric symbols are formatted and displayed based on culture (see Listing 8-27). The decimal number 10000.50 is formatted as 10,000.50 for the English (en-US) culture and 10.000,50 for the German (de-DE) culture.

Listing 8-27: Formatting a Currency Value According to a Culture
start example
 public String GetFormattedCurrency( int intAmount, String strCulture ) {     CultureInfo culture = new CultureInfo( strCulture );     return intAmount.ToString( "c", culture ); } 
end example
 

In this method, you format an integer using the NumberFormatInfo standard currency format ("c") for the specified CurrentCulture. The culture argument can be any of the identifiers listed in Table 8-5. The formatted amount is not actually converted from one currency value to another. Only its currency representation is modified.

Note  

The .NET Framework and Microsoft Windows XP set the default currency symbol to the Euro for the 12 unified European nations. Older versions of Windows will still set the default currency symbol to the local currency for these nations.

Applications running on all versions of Windows operating systems set the default currency symbol from the settings on the user's computer. As mentioned, this setting might be incorrect. To ensure that an application uses the .NET Framework's default settings, create a CultureInfo object and set the useUserOverride parameter to false.




Developing. NET Enterprise Applications
Developing .NET Enterprise Applications
ISBN: 1590590465
EAN: 2147483647
Year: 2005
Pages: 119

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net