Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Visual CSharp .NET Developer's Handbook (2002) [eng]

.pdf
Скачиваний:
35
Добавлен:
16.08.2013
Размер:
4.94 Mб
Скачать

"Software",

true);

oSub.DeleteSubKey("MyCompany");

//Close the registry key. oSub.Close();

//Enable the read and delete buttons after we add a new

//registry entry.

btnReadEntry.Enabled = false; btnDeleteEntry.Enabled = false;

}

private void btnReadEntry_Click(object sender, System.EventArgs e)

{

RegistryKey

oSub;

//

Company storage key.

String

RValue;

//

Stored key value.

// Read the stored value and display it.

oSub = Microsoft.Win32.Registry.LocalMachine.OpenSubKey( "Software\\MyCompany");

RValue = (String)oSub.GetValue(null); MessageBox.Show(RValue,

"Stored Registry Value", MessageBoxButtons.OK, MessageBoxIcon.Information);

// Close the registry keys. oSub.Close();

}

Each of the buttons works in a similar fashion, but each shows a distinct part of working with the Registry. The RegistryKey class enables you to work with Registry objects. However, you'll use the Registry class to gain access to one of the six main Registry divisions, as shown in Table 2.2.

 

Table 2.2: Registry Class Instances

 

 

 

 

Instance

 

 

Associated Registry Key

 

 

 

 

ClassesRoot

 

 

HKEY_CLASSES_ROOT

 

 

 

 

CurrentUser

 

 

HKEY_CURRENT_USER

 

 

 

 

LocalMachine

 

 

HKEY_LOCAL_MACHINE

 

 

 

 

Users

 

 

HKEY_USERS

 

 

 

 

CurrentConfig

 

 

HKEY_CURRENT_CONFIG

 

 

 

 

PerformanceData

 

 

HKEY_PERFORMANCE_DATA

 

 

 

 

DynData

 

 

HKEY_DYN_DATA

 

 

 

 

The btnAddEntry_Click() event handler actually creates two Registry key objects. To add a new key, you must first open the parent key, then use the CreateSubKey() method to create the sub key. Once the sub key exists, the code creates a second Registry key object that points to the new sub key. You can't change the existing parent key to point to the new location, so the second key is a requirement.

The SetValue() and GetValue() methods enable you to write and read Registry values. Note that you can only write to the Registry if you enable write access. The various Registry methods assume you want read-only access unless you specify write access as part of the OpenSubKey() method call by supplying true as the second argument. Figure 2.5 shows the results of the btnAddEntry_Click() event handler code.

Figure 2.5: The hierarchical format of the Registry enables you to make new entries with ease.

Every Registry routine must end in the same way. Use the Close() method to signify that you no longer require the RegistryKey object. In addition, Close() flushes the Registry changes to the Registry-ensuring the Registry records them. This is an especially important consideration when working with remote Registry entries.

Another consideration when working with the Registry is that many calls assume generic objects. The use of generic types methods means you'll need to perform type conversions in many cases. For example, look at the GetValue() method call found in the btnReadEntry_Click() event handler. As you can see, the return of a generic object means converting the value to a string.

The System Namespace

The System namespace contains the bulk of the namespaces and classes you'll use to create an application. In fact, this namespace is too vast to even summarize properly in a single section. We'll discuss this namespace in detail as the book progresses. In general, anything that more than one programming language could use on more than one platform will appear in this namespace (at least, that's the theory). This namespace includes all type, data access, drawing, data communication (including XML and XML derivatives), component, storage, service, and management classes. The System namespace also includes diagnostic, debug, and trace namespaces used to make application development easier.

You do need to know about some System namespaces to begin programming. Of course, everything you create will contain a reference to the System namespace, but there are other common namespaces. For example, every application you create will include the following namespaces because they're used to begin the application design process.

System.Drawing

System.Collections

System.ComponentModel

System.Windows.Forms

System.Data

The Application Wizard also includes the associated DLL support within your application when you define it. For example, the System.Drawing namespace appears within the System.Drawing.DLL file, not within the standard system files. You don't need drawing support to create a component, so keeping this functionality in a separate DLL makes sense.

Most projects contain a reference to the System.ComponentModel namespace, because this namespace contains basic control and component behavior classes. For example, this namespace contains the various License classes used to enable and verify licensing for the third-party components and controls used in your application. This namespace also contains the Toolbox classes required to support controls and components in the Toolbox.

The System.Windows.Forms namespace contains classes associated with Windows forms. If you want to display any type of information on a form, you need this namespace in your application. In fact, you can't display so much as a message box without it (unless you want to go the PInvoke route). This namespace also contains all of the controls normally associated with forms such as Button and DataGrid.

In many cases, you can actually create applications without the System.Data namespace. This namespace includes support for many types of data access (ODBC.NET is an exception). The reason the wizard includes this namespace is to enable support for the DataGrid and other data bound controls. You can remove this support for smaller, non-database applications and save some memory. However, the gains you obtain by omitting the namespace are small, because Visual Studio .NET optimizes applications to use only the namespaces they actually reference.

Developers will want to learn about the System.Diagnostics namespace because it contains classes that enable you to learn more about problems in your application. We'll use the Trace and Debug classes relatively often in the book. The EventLog class should also be on the top of your list because it helps you record component errors in a place where the network administrator will see them.

In the past, working with the EventLog could be a painful experience. Visual C++ required a lot of obscure code to work with the EventLog, while Visual Basic provided incomplete support (which meant incomplete and often useless entries). Listing 2.3 shows a typical example of event log entry code for a .NET application. (You'll find the complete source in the \Chapter 02\Diagnose folder on the CD.)

Listing 2.3: Working with Event Logs in .NET

private void btnTest_Click(object sender, System.EventArgs e)

{

// Create some raw data to store with the log entry. byte []RawData = {82, 97, 119, 32, 68, 97, 116, 97};

// Open the Application log.

EventLog AppLog = new EventLog("Application"); AppLog.Source = this.ToString();

// Write the event to the Application log. AppLog.WriteEntry("This is a test entry",

EventLogEntryType.Information,

200,

100,

RawData);

// Close the log. AppLog.Close();

}

The first thing you should notice is the array of byte in the form of the RawData variable. In this case, the RawData variable actually contains "Raw Data" (as we'll see later). You must convert all text to byte values, which could prove time-consuming if you don't have a function for doing so, especially when you use the event log regularly and need to convert long strings. In this case, the entry is short, so using a direct conversion makes sense.

Even though the documentation says you don't need to provide a string with the EventLog() constructor, it usually pays to provide at least the log name, as shown in Listing 2.3. Adding the log name ensures the event entries go to the right log on the machine. Other constructor overrides enable you to assign both a source and a machine name to the log entry.

If you don't provide the source as part of the EventLog() constructor, you must set the Source property separately, as shown in Listing 2.3. Notice that the example uses this.ToString() instead of a string. Using this technique produces an interesting result that we'll see during the example test.

You'll use the WriteEntry() method to create a new event log entry. The method accepts an error message string, the type of event log entry (Warning, Error, or Information), an EventID, category, and some raw data. Other overrides enable you to provide less information than shown.

Finally, make sure you close the event log before you exit the application. The Close() method frees resources and ensures the data is flushed to the log. Flushing the data is especially important for remote machine scenarios.

When you run the application and click Test, it makes an event log entry. Figure 2.6 shows the Event Viewer display. Notice that the Source column contains the name of the application class, as well as the text from the form's title bar. The reason that this is such as valuable way to make an event log entry is that you gain a better understanding into the source of the error.

Figure 2.6: The Event Viewer shows the results of creating an event log entry.

Double-clicking the event log entry reveals the details shown in Figure 2.7. Notice that the event log entry contains the raw data we added to the WriteEntry() call. You can also see the error description. Of course, you'll want to make a real entry far more useful by including complete diagnostic information.

Figure 2.7: The Event Properties dialog contains detailed information about the event.

Working with .NET Components

One of the biggest concerns that Microsoft has tried to address in .NET is the problem of DLL hell. The use of separate folders for each version of the .NET Framework, the Global Assembly Cache (GAC), and other versioning measures have made it a lot easier to avoid DLL hell. In fact, a developer would have to work at creating a DLL hell situation-the development environment tends to support application versioning.

What does this new level of support mean to the application developer? In the past, every component you created ended up in the Registry because the IDE registered the component as part of the build process. The .NET Framework no longer relies on the Registry to store component data. The information is stored within the assembly as part of the component. While this technique certainly avoids confusion caused by old Registry entries, it begs the question of how an application will find the component support it will require.

.NET provides two methods for working with components. The first is the private component method. Simply copy the components you need to the application's BIN folder. The application will look in this folder for any components it needs so there's no requirement for a central information store. Because the component contains all the information that used to appear within the Registry as part of the assembly, the Registry is superfluous. This technique also avoids DLL hell by keeping a copy of the component required for application execution with the application.

The second method is to create a public entry for the component in the GAC. If an application can't find a copy of the component it needs in the BIN folder, it automatically looks for the component in the GAC. Note that the GAC stores the version and strong name information for the component as part of the entry as shown in Figure 2.8. (The GAC normally appears in the \Windows\Assembly folder as shown in the figure.)

Figure 2.8: The GAC provides centralized storage for public components.

The application will look for the specific version of the component used to create the application in the GAC. The GAC can also contain multiple versions of the same component to help ensure compatibility. Notice that the GAC display also makes provision for the component type and the culture (locale) information. You can include several copies of a component based on locale, even if the components are the same version. The Public Key Token field relates to the assignment of a strong name to the component. We'll discuss this issue in detail in Chapter 6. For now, all you need to know is that the key provides a unique way to identify the component and ensures that .NET will detect any tampering by a third party. In short, using a strong name not only ensures uniqueness, but aids in maintaining security as well.

.NET Forms and User Interface Support

One overriding goal of .NET is to make application development easier, especially distributed application development. Developers no longer have time to spend researching arcane function calls in the Windows API and even more time figuring out that the documentation is simply wrong. In addition, the Windows API doesn't even come close to providing an environment for distributed applications. The Windows environment of old assumes that every piece of code you create will execute locally and therefore have access to a standard display.

Creating a user interface, a really functional user interface, has been the topic of many books. It's not an easy task under the best of circumstances. However, .NET does reduce the complexity of creating a great user interface. The following sections will look at the ways that you can use the features that .NET provides to create an easy to use and understand interface for your next application in a short amount of time.

Active Server Pages

Active Server Pages (ASP) support is old news .NET. Developers have used ASP for several years now. During that time, developers have created a wish list of those new features they consider essential. This is the basis for the improved form of ASP found in Visual Studio

.NET-ASP .NET.

You can still use all of your existing ASP files with ASP .NET. Unlike some parts of the

.NET upgrade (such as ADO .NET, discussed later in the book), ASP .NET provides full backward compatibility. However, it also provides all of those new features that developers want.

Two of the most important features are Code Behind and ASP controls. One of the biggest problems with ASP files right now is that they mix code and HTML elements. The mixture makes ASP files somewhat difficult to read. Code Behind rids the developer of this problem by placing the script code for a page in a separate file. This technique also makes it easier to reuse existing code, because the code exists separately from the HTML elements used to render the results on screen.

Another problem with ASP is that it relies heavily on plain HTML tags to get the job done. The problem with HTML tags is they don't work like the controls found in desktop applications and they come with a wealth of limitations. ASP controls eliminate this problem by providing full control support for ASP development. The client still sees HTML tags, but the server-side entity is a control. In short, ASP controls enable the developer to create applications faster and with better functionality, without requiring anything new on the part of the user.

Let's consider a very simple example consisting of a Test pushbutton and a label. When you click the Test pushbutton, the web server displays a message within the label. Listing 2.4 shows the ASPX (ASP eXtended) page required for this example. (You'll find this project in the \Chapter 02\SimpleASP folder of the CD.)

Listing 2.4: A Simple ASP .NET Example

<%@ Page language="c#" Codebehind="WebForm1.aspx.cs" AutoEventWireup="false" Inherits="SimpleASP.WebForm1" %>

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" > <HTML>

<HEAD>

<title>WebForm1</title>

<meta name="GENERATOR" Content="Microsoft Visual Studio 7.0"> <meta name="CODE_LANGUAGE" Content="C#">

<meta name="vs_defaultClientScript" content="JavaScript"> <meta name="vs_targetSchema"

content="http://schemas.microsoft.com/intellisense/ie5">

</HEAD>

<body MS_POSITIONING="GridLayout">

<form id="Form1" method="post" runat="server"> <asp:Label id="lblOutput"

style="Z-INDEX: 101; LEFT: 20px; POSITION: absolute; TOP: 16px" runat="server" Width="173px">

</asp:Label> <asp:Button id="btnTest"

style="Z-INDEX: 102; LEFT: 20px; POSITION: absolute; TOP: 55px" runat="server"

Text="Test">

</asp:Button>

</form>

</body>

</HTML>

Notice the page begins with a reference to WebForm1.aspx.cs. This is the glue that binds the web page to the associated script. We'll discuss the whole issue of bonding between the various ASP .NET elements in Chapter 15. All you need to know for now is that the connection exists.

You should already recognize the tabs within the <HEAD>. These tags remain unchanged from ASP for the most part. However, when you get to the <BODY> you'll notice that some things have changed. The page now contains <asp:> tags that enable you to describe the page elements using the same properties you would use on a standard desktop application. In fact, when you work with the pages in the IDE, you'll use the same Toolbox that you would for a desktop application. The feel of designing for the web is the same as it is for the desktop.

The code behind page, WebForm1.aspx.cs, looks similar to any desktop application you create with C#. The application does use some namespaces we haven't discussed yet and you'll find the control display code is missing, but otherwise it looks like a standard desktop application (see the file on the CD for details). Here's the simple event handler for displaying the test text within the label.

private void btnTest_Click(object sender, System.EventArgs e)

{

// Output some text. lblOutput.Text = "Hello ASP .NET";

}

As you can see, the code looks the same as the code any desktop application would use. The only real difference is that it appears on a web page.

Control Additions and Modifications

Developers have had control support in the IDE for quite a few versions of Visual Studio, so controls as an entity aren't anything new. However, controls of the past have always provided an inconsistent interface and contrasting levels of functionality. Part of the problems with controls was the different types of interface support required by each language. We've already discussed the benefit of .NET with regard to uniform interface support. However, there are other issues to consider.

The first issue is one of functionality for existing controls. You'll find that all controls provide a basic level of support for common features, making the controls easier to use because they have a shorter learning curve. For example, all controls support basic properties such as Text. You'll also find common event support for common events such as Click. Finally, every control supports certain methods such as ToString() and you'll find they all use the same method for adding event handlers.

The second issue is new controls that are needed to address the requirements for distributed and data-oriented programming environments. For example, the new DataGrid control provides better data handling support. You can access both local and remote sources of information with little difference in programming approach. In addition, the same control appears in both Windows Forms and Web Forms, which means the knowledge you gain learning to use the control for local applications will also apply for remote applications.

Crystal Reports

Reports are the most important part of any database application. The only way that some people (like management) will ever interact with your application is by viewing the reports that it creates. That's why it's important that your application produce functional, easy-to-read, and great looking reports. Crystal Reports is one way to obtain great-looking reports with less work than creating the report from scratch using hand coding techniques.

Crystal Reports is especially important for database applications, so we'll discuss this utility as part of the database sections of the book. However, it's important to remember that you can use Crystal Reports for any type of data from any application.

Formatting Data

Data formatting is one of those simple, but nagging, issues for many developers-which method to use for converting data from one format or type to another. Of course, the easiest and most common conversion is to a string using the ToString() method. However, what if the ToString() method of producing a simple string isn't what you want? You can also use the Format() method found in the System.String namespace as shown here:

//The single integer value used for all conversions. int myInt = 2345;

//Begin by displaying actual value.

string myString = "Actual Value:\t" + myInt.ToString();

//Currency format. myString = myString +

String.Format("\n\nCurrency:\t{0:C}", myInt);

//Decimal format.

myString = myString + String.Format("\nDecimal:\t\t{0:D}", myInt);

//Exponential format. myString = myString +

String.Format("\nExponential:\t{0:E}", myInt);

//Fixed point format.

myString = myString +

String.Format("\nFixed Point:\t{0:F}", myInt);

//General format. myString = myString +

String.Format("\nGeneral:\t\t{0:G}", myInt);

//Numerical format (with commas).

myString = myString + String.Format("\nNumerical:\t{0:N}", myInt);

// Hexadecimal format. myString = myString +

String.Format("\nHexadecimal:\t{0:X}", myInt);

You also have access to all of the features of the System.Convert namespace. This namespace contains methods such as ToDateTime(), ToBoolean(), and ToInt32() that make it easy to convert from one type to another. The following code shows a conversion from a string to an integer.

//Create a string. string myString = "234";

//Convert it to an integer.

int myInt = System.Convert.ToInt32(myString);

// Display the result. MessageBox.Show(this, myInt.ToString());

You can also use the Int32.Parse() method to convert a string into its numeric counterpart. It turns out that there are other Parse() methods available such as Int16.Parse(). The point is that you have multiple ways to convert one value to another when working with .NET. Note that you can find all of these conversion techniques in the \Chapter 02\Convert example.

Understanding Boxing and Unboxing

The only native data types that are objects are the string and the object. C# considers all objects reference types and allocates them from the heap. Otherwise, whenever you look at a variable, you're looking a value. C# allocates all values from the stack. Generally, values are more efficient than references, but sometimes you need a reference to perform a specific task.

C# calls the process of converting a value to a reference boxing, while going in the other direction is unboxing. Generally, you won't have to box or unbox values manually; C# will do it for you automatically as needed. However, you may run into situations when you need to box or unbox a value manually. The following example shows how to box and unbox a value. (Note that this example also appears in the \Chapter 02\Box_and_Unbox folder.)

//Create an integer value. int myInt = 25;

//Box it to an object and display the type. object myObject = myInt;

MessageBox.Show(this, myObject.GetType().ToString());

//Unbox it into a value and display the contents. int newInt = (int)myObject;

MessageBox.Show(this, newInt.ToString());

When you run this code, the first message will tell you that the object type is still a System.Int32, while the second will show that C# preserves the value during the boxing and unboxing process. Everything about the value remains the same as before. The only thing that's happened is that C# literally places the value in a box. You can operate on boxed values as you would any other object. Note that you can use this technique to make structs appear as objects as needed.