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

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

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

{

Query = Query + args[Counter] + " "; Counter++;

if (Counter == args.Length) break;

}

//Locate the output filename. OutFile = "";

for (Counter = 0; Counter < args.Length; Counter++) if (args[Counter].Length > 5)

if (args[Counter].Substring(0, 5).ToUpper() == "/OUT:")

{

OutFile = args[Counter].Substring(5); break;

}

else

if (Counter == args.Length - 1)

{

Console.WriteLine("Using Default Filename"); OutFile = "Temp.TXT";

}

//Create the output file.

Clear = false;

for (Counter = 0; Counter < args.Length; Counter++) if (args[Counter].ToUpper() == "/CLEAR")

Clear = true; if (Clear)

SendOut = new StreamWriter(OutFile, false); else

SendOut = new StreamWriter(OutFile, true);

// Determine if we need console output. Verbose = false;

for (Counter = 0; Counter < args.Length; Counter++) if (args[Counter].ToUpper() == "/V")

Verbose = true;

try

{

//Create a WMI query based on user input. MOS = new ManagementObjectSearcher(Query);

//Get the query results and place them in a collection. MOCollect = MOS.Get();

//Output the data.

foreach( ManagementObject MO in MOCollect )

{

SendOut.Write("Caption: {0} Description: {1}\r\n", MO["Caption"], MO["Description"]);

if (Verbose)

Console.WriteLine("Caption: {0} Description: {1}\r\n", MO["Caption"], MO["Description"]);

}

// Close the output file. SendOut.Close();

}

catch (System.Management.ManagementException e)

{

// Output error information as needed. Console.WriteLine("Error Accessing Device"); Console.WriteLine(e.ErrorCode); Console.WriteLine(e.ErrorInformation); Console.WriteLine(e.Message); Console.WriteLine(e.Source);

return 3;

}

// No errors, so standard return. return 0;

}

As you can see, this console application is a little more complex than the previous example. The first thing you should notice is that the return value of Main() has changed from void to int to allow a return value. The batch file used to test this example relies on the return value to print a message.

The code begins by ensuring the user has provided command-line input. At the very least, the application requires a query to perform any function at all. The user can also request help using the /? command-line switch. The help for this example is more extensive, because the console application can perform more work.

The next two sections of code work with the query string, Query. First, the code needs to locate the /Query switch within the command-line arguments. Second, the code builds the query string. The application continues processing the arguments until it runs into another switch or comes to the end of the command line. The content of Query looks much like a SQL statement and follows some of the same logic. A simple query always includes four elements: Select <What to Select> From <WMI class name>. For example, if you wanted to query the logical disks on your system, you'd use a string similar to, "Select * From Win32_LogicalDisk." The ManagementObjectSearcher object also allows you to filter the output using a WHERE clause such as "WHERE DriveType = 3." In this case, we'd be looking for all local drives and filtering out the network drives.

Tip You can find a list of WMI classes in the Computer System Hardware Classes help topic (ms-help://MS.VSCC/MS.MSDNVS/wmisdk/r_32hard1_9v77.htm). Each class lists the types of information you can retrieve about the hardware in question. Some types of information, such as Description, are generic for any hardware class. Other pieces of information, such as the Compressed Value for Win32_LogicalDisk, are specific to the hardware class in question.

After it creates a query string, the code locates the output filename. Even though the help lists the /Out switch as required, it can recover from input errors by creating a default filename. The application does output a message stating it's using a default filename instead of using one provided by the user. The code checks for the /Clear switch, then creates the file using the StreamWriter() constructor. The second argument of this constructor is true if you want to append data to the existing file. The resulting object assumes you want to use the local directory, if you don't supply a path as part of the filename.

At this point, the code enters a try block. Error handling is nice in other areas of the application, but it's required here. Otherwise, the application will fail on a number of WMI supported devices, even if the device is installed and functional. The problem is one of recognition. Depending on the BIOS provided with the machine, the method used to support WMI, and Windows ability to see the device, some devices might not appear on the list.

It's time to obtain the WMI information from the system. The code performs three steps to complete this process.

1.The code creates the query. The query will normally succeed unless the input string is malformed.

2.The code executes the query and places the result in a collection. If this step fails, the collection will contain a null value, but won't fail.

3.The code uses a foreach loop to view each collection member. This step always fails if the device is inoperative, unrecognized, or unavailable. The example provides two forms of output. The first is to a file, while the second is an optional screen output.

Before you can compile this application, you need to add the usual using statements to the beginning of the file (see the source code in the \Chapter 05\ConsoleScript folder on the CD for details). You also need to add a reference to the System.Management component by selecting the Project Add Reference command. You'll see an Add Reference dialog box similar to the one shown in Figure 5.2. Highlight the System.Management entry, click Select, then click OK to add the reference to your project.

Figure 5.2: Use the Add Reference dialog box to add the System.Management component to your application.

You'll find a simple batch file named Test.BAT in the \Chapter 05\ConsoleScript\bin\Debug folder. It accepts a simple query as input and provides some output based on the ConsoleScript invocation results. Here's the Test.BAT code.

ECHO OFF

REM Run the application.

ConsoleScript %1 %2 %3 %4 %5 %6 %7

REM Test the output.

If ErrorLevel 3 Goto DeviceErr

If ErrorLevel 2 Goto NoQuery

If ErrorLevel 1 Goto NoInput

REM If no error, exit.

Goto Success

REM Error Message Output. :NoInput

ECHO No Input Provided GOTO END

:NoQuery

ECHO Always provide an /Query input. GOTO END

:DeviceErr

ECHO Device error — is it installed? GOTO END

:Success

ECHO Task Completed Successfully!

:END ECHO ON

Of course, you could easily use the ConsoleScript application with a script file or as part of a Task Scheduler entry. The point is that console applications help you automate many tasks and perform others in the background or at a command prompt. A console application isn't a good choice when you need to provide a large amount of user interaction, but it does fill a needed gap in the network administrator's Toolkit.

Resource Essentials

You won't write many applications that can function without resources of some type. Resources include menus, accelerators, bitmaps, special controls, accessibility features, and web-access support. Resources also include fonts, brushes, and pens—the drawing tools we've used in other areas of the book. (See the CommandLine2 example in Chapter 4 for an example of a print routine that uses the drawing tools.) Your code interacts with the resources to present information to the user in a helpful manner.

The following sections will discuss many of the resources we haven't yet covered fully or at all. We'll work with these resources extensively as the book progresses. For example, you'll find some graphics examples later in the chapter. This chapter will also show one way to use timers in your applications. The Resources example found in the \Chapter 05\Resource folder on the CD also contains some examples of using resources within an application.

Accelerators and Menus

Menus come in a variety of shapes and sizes. All SDI and MDI applications require a main menu. This menu normally contains a File entry, as well as some form of Help. However, Visual Studio .NET provides you with a clean slate when you create a menu, so the contents are completely up to you. Following the pattern used by existing applications is good practice because using a standard menu template makes it easier for others to learn your application.

Another typical menu type is the context menu. Most Windows objects provide a context menu so the user knows how to interact with that object. Dialog applications use context menus frequently, and you'll find context menus in SDI and MDI applications. The menu you see in an application that resides in the Taskbar tray is a context menu, as is the menu you see when you click within the work area of an SDI or MDI application.

The main and context menus for an application are different controls: MainMenu and ContextMenu. An application can have multiple context menus, but it can contain only one main menu. The main menu attaches itself to the form where you create it and normally remains visible. In most cases, you can hide the main menu by setting the Menu property of the form to a null (blank) value. Here's an example of two context menu commands that will hide and show the main menu.

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

{

// Hide the main menu. Menu = null;

}

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

{

// Show the main menu. Menu = mainMenu1;

}

It's interesting to note that you can use this same technique for changing between predefined main menus, rather than build each main menu by scratch. Many applications disable the menu entries the user can't access given the current environment, but sometimes it pays to create multiple main menus during design time for a quick swap during runtime. Of course, you can only use one main menu at a time.

You need to attach a context menu to an object using the object ContextMenu property to make it accessible. The context menu is only visible when the user right-clicks on the object or you make the menu visible in some other way. Unlike a main menu, context menus contain only one main entry. However, you can create as many submenus as needed to complete a given task.

Despite their differences, creating a main menu and a context menu is essentially the same. You'll begin with a blank menu where you type the various menu entries, as shown in Figure 5.3. Notice the use of the ampersand to create underlines. A user can type the underlined character to access the menu entry, so you need to make the entries unique for a given menu level.

Figure 5.3: Main and context menus follow essentially the same design process.

Visual Studio .NET uses a nondescript naming technique for menus—menuItem followed by a number. Most developers use a naming scheme that follows the menu hierarchy. For example, a developer might name the Exit option of the File menu mnuFileExit for easy identification.

This brings us to the topic of accelerators or shortcut keys (the term you use likely depends on the environment in which you started working). You assign an accelerator to an object using the Shortcut property. An accelerator enables a user to access a menu or other command using a key combination, rather than the menu hierarchy. Like menu setups, most applications use specific accelerators for the same task. For example, you can usually access the print feature of an application by pressing Ctrl+P.

One of the interesting characteristics of the C# IDE is that you can't see the accelerator you assign to a menu entry until the application is running. This means you have to assign the accelerator judiciously. Otherwise, the same accelerator could appear on two different menu entries, as shown in Figure 5.4. Experimentation shows that the first menu item to receive the accelerator key will normally use it, which means you might receive unexpected results with conflicting accelerators.

Figure 5.4: Always use accelerators carefully to avoid conflicts.

C# doesn't limit you to using accelerators for menu entries. Some controls such as the DataGrid come with built-in accelerators. You can also create accelerators with labels to access the associated control faster. Type an ampersand next to the letter in the label that you want to use for access. When you press Alt+<Letter>, the application automatically selects the control (normally a textbox) associated with the label. Some controls, such as the pushbutton, allow you to add the accelerator directly by typing the ampersand as part of the control text.

Accessibility Features

The best way to make your application friendly is to use a sensible layout, add plenty of help, and use features such as accelerators. These features help a lot; but, in some cases, they aren't enough. That's where the accessibility features come into play. Visual Studio .NET provides the means to add accessibility cues to your applications. For example, these cues could help someone who might not be able to see the application to make more sense out of what they hear about the application.

The three accessibility cues are associated with control properties. The following list tells you about each property and tells how you'd use it to increase the information about your application.

AccessibleDescription This property provides a better description of the use of an application control than might be apparent from the control itself. For example, if you use a picture button, someone with normal vision might understand that a stop-sign symbol enables them to stop the current operation. However, you'd also want to provide an AccessibleDescription that tells how to use the button.

AccessibleName This property contains the short name for a control. You'd use it when you need to provide a more descriptive name for the control than space will allow on a form, but don't require a complete description. For example, this property might contain a value of "OK

Button," so someone with a screen reader would know that it's a button with the word OK on it.

AccessibleRole This property to is used to define the type of control interface. For example, you can define the role of a button or button-like control as a PushButton. Visual Studio appears to use the Default setting for all controls, even when it's easy to determine the type of control. All screen readers will use this property, even the readers that come with the various versions of Windows. Telling the user that they're working with a pushbutton is helpful, especially when the pushbutton had a unique shape or other feature. (Think about the odd buttons on the skin for the Windows Media Player.)

In addition to filling out the accessibility properties for all of the controls in your application, you can also support some accessibility features directly. For example, one of the accessibility features that many users need is high contrast. This feature is indispensable when working on some laptops, because it provides the extra information needed to see the screen in sunlight.

While some people use this feature all of the time, others use it at the end of the day to reduce eyestrain.

Your application can detect when the system goes into high contrast mode by monitoring the SystemInformation.HighContrast property. Of course, you'll still need to select high contrast color combinations (with black and white providing the best contrast). The point is that some screens that look great in color look terrible when put into high contrast mode. If you don't detect this property, the high contrast mode that should help the user might end up causing problems.

Graphics

In some respects, graphics haven't changed for Windows developers since the days of Window 3.x. Visual Studio still provides access to the same three graphic resources: icons (ICO), bitmaps (BMP), and cursors (CUR). The only change for Visual Studio .NET is an increase in the number of colors you can use. Unlike previous versions, you can now create your graphics in something more than 256 colors. You'll also find that icons and cursors now come in a 96 x 96–bit size.

Adding a new graphic image has changed in Visual Studio .NET. The fastest way to add a new graphic is to right-click the project entry in Solution Explorer and choose Add Add New Item from the Context menu. Select the Resources category and you'll see a list of resources similar to the one shown in Figure 5.5.

Figure 5.5: Visual Studio .NET provides access to enhanced forms of the graphics files you've always used.

Once you create the new graphic, you'll see a typical bit level drawing area that includes a color selection toolbar and a second toolbar with drawing tools. Anyone who's used Paint in the past will feel comfortable with the tools in Visual Studio .NET.

Bitmap files contain only one image, so if you need different resolutions of the same bitmap, you'll need to create individual bitmap files. However, cursor and icon files can both contain multiple images that vary by resolution and color depth. In fact, you'll need to define multiple images when working with icons to obtain the best display, because Windows will select the best image automatically. This is especially important in Windows XP, because 64–bit icons and greater color depth. To addthis new operating system uses 64 a new image to the list for an icon, right-click the drawing area and choose New Image Type from the Context menu. You'll see a New Icon Image Type dialog box from which you can choose a new type. Context menu options also enable you to select from an existing image type or delete one that you no longer need.

Graphic resources aren't limited to the three you can access directly in Visual Studio .NET. We'll see later that you can use the Bitmap object to import graphics that Visual Studio never supported in the past. This includes using resources such as GIFs in your applications. Anyone who's designed a web page knows the value of animated GIFs in dressing up a display so it looks attractive to visitors. In fact, we'll create a desktop application that makes use of an animated GIF later in the "SDI Example" section of this chapter.

Timers

Timers help you keep track of time. A timer is actually one of the easiest controls to use in Windows. All you need to do is set the Interval property for the number of milliseconds between each Tick event. You create an event handler for the tick that tracks events or performs other tasks based on the time. Look in the "Dialog-Based Example" section of the chapter for an example of an application that uses timers to keep track of your typing time.

Toolbars

Toolbars are a required resource for SDI and MDI applications because they provide a shortcut to essential application functionality. For example, users have become very

dependent on the three icons used to manage documents in most SDI and MDI applications: New, Open, and Save.

In the past, developers would drop a toolbar on their application, then drop buttons on the toolbar. Visual Studio .NET takes a slightly different approach, with a vastly improved ToolBar control. Unlike previous versions, the buttons are now part of a collection that resides with the toolbar. However, adding a button isn't quite as obvious as it might be, so some people are still using the old approach of dropping buttons on the toolbar.

Click the Buttons property on the ToolBar control and you'll see the word (Collection). Click the ellipsis button and you'll see a ToolBarButton Collection Editor dialog box similar to the one shown in Figure 5.6. (Note that the figure shows a toolbar with buttons already defined— your toolbar will start out buttonless.) This dialog box is the only method for defining the properties for the buttons.

Figure 5.6: Use the ToolBarButton Collection Editor to modify the appearance of the ToolBar control.

Note If you want to create picture buttons for your ToolBar control, you'll want to create an ImageList control first and fill it with the images that you'll need. Associate the ImageList with the ToolBar using the ImageList property. The ImageIndex property found in the ToolBar Collection Editor dialog box enables you to choose a specific icon from the ImageList for the current ToolBarButton.

This brings us to the problem of working with a ToolBar that has embedded ToolBarButtons, rather than standard buttons placed over the ToolBar. When you use standard buttons, you can access the individual button events. However, when working with embedded ToolBarButtons, you only have access to the ToolBar events.

One of the ToolBar events is ButtonClicked. When you create an event handler for this event, the application passes you a copy of the information for the button. Unfortunately, this information doesn't include the button name. To make it possible to identify the button, you must assign the button a Tag property value. Once you have the Tag property value, you can pass the ToolBar button handling onto the associated menu event handler as shown in the following code.

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