The mere mention of the word assembly takes me back to my days as a high school freshman. The assembly was actually held in the school gym, with 2,000 screaming adolescents filling the bleachers around the basketball court. Since this was a school function, I naturally thought of an experience packed with fresh educational opportunities. School, education—the words just seem to go together. But then came the marching band, and the football players, and the cheerleaders, and the school mascot (a horse). For the next 30 minutes, the principal whipped the students into a controlled frenzy, attempting to prove the institution’s place as the number-one school in the city. I still don’t know what area we were supposed to be number one in, but it was all very exciting.
.NET assemblies are not that exciting. In fact, they’re just files, EXE and DLL files, and without you to activate them, they just sit there taking up disk space. And as they are not doing anything else, let’s take a moment to examine what they are and what they contain.
As I already mentioned in Chapter 1, an assembly is a “unit of deployment,” which in most cases is just a file. An assembly is a repository for compiled .NET application code; any code you write will eventually be stored in some EXE file (if it is an application) or DLL file (for code libraries or extensions to an application). Everything that .NET needs to know to load and run your application is stored in the assembly.
Assemblies are either private or public. Private assemblies are designed for use in a single application only. If there aren’t any DLLs, an EXE assembly is the application. Private assemblies appear in their own directory, the installation directory of the application or library. You can run two different private assemblies at the same time, and they won’t bother each other. This is true even if each assembly uses the same combination of namespace and class names for its coded elements. If two application assemblies each implement a class named WindowsApplication1.Class1
, they will not interfere with each other when running; they are private, and private means private.
Public assemblies are designed for shared use among multiple .NET applications. Public assemblies differ from private assemblies in two major ways:
Public assemblies always have a strong name, an encrypted digital signature that is attached to an assembly to guarantee that it came from its named vendor or source. (Private assemblies can also include a strong name, but they don’t have to.) The strong name is built from the assembly’s name, version number, culture information, a “public key,” and a digital signature generated from the assembly file that contains the manifest (described later). The .NET Framework includes a Strong Name generation tool (sn.exe) that assists in this process, and Visual Studio includes options that let you add a digital signature during the compilation process. (It’s on the Signing tab of the project’s properties.)
The strong name of an assembly should be (and better be) unique; if two assemblies share a common strong name, they are copies of the same assembly.
Public assemblies are stored in the Global Assembly Cache (GAC). Although you can put a copy of your shared component in your application’s install directory, it will only truly be shared once it reaches the GAC directory. The GAC lives in a directory named assembly within the computer’s Windows directory. (On my system, it’s in c:windowsassembly.) Once a .NET assembly has a strong name applied, you can add it to the GAC by either dragging the file into the assembly directory or using the Global Assembly Cache Tool (gacutil.exe). Don’t worry about your file being lonely if it’s not communing with your other installed files. On my freshly installed copy of .NET, I found nearly 400 files already in the GAC directory, including all the DLLs for the Framework Class Libraries (FCLs).
.NET lets you install multiple versions of an assembly on a system and use them at the same time (a process called versioning). This applies both to applications (EXE) and libraries (DLL), and to private assemblies and shared assemblies in the GAC. Don’t believe me? Open up the GAC’s assembly folder, set the Explorer folder to a Details view, and then sort by Assembly Name. If you scroll down, you’ll see the same file show up multiple times. Figure 5-1 shows a part of the cache. Two copies of “Microsoft.VisualStudio.Windows.Forms” are listed (from the Microsoft.VisualStudio.Windows.Forms.dll file), one with a version number of 2.0 and one listing version 9.0.
Although there is usually a one-to-one relationship between files and assemblies, there may be cases when an assembly is made up of multiple files. For instance, an application might include an external graphics file in its assembly view. .NET keeps a close watch on these files. If any of the files are modified, deleted, or otherwise maimed, you will hear about it. For the purposes of discussion, the rest of this chapter considers only single-file assemblies.
An assembly’s EXE or DLL file is a standard “Portable Execution” (PE) file, the same file format used for non-.NET executables and code libraries (pretty much any Windows EXE or DLL file). What makes .NET PE files different is all the extra stuff found inside. As a general word, assembly indicates a gathering together of various parts into a single unit. In a .NET assembly, these “various parts” are specifically designed for use with .NET.
A .NET PE file contains three main parts:
Required of all PE files, this section identifies the locations of the other sections of the file.
The actual code associated with the assembly is stored as semicompiled Microsoft Intermediate Language (MSIL) code. Unfortunately, the Intel or AMD chip in your computer is apparently too brainless to process MSIL code directly (what were they thinking?), so the .NET Framework includes a just-in-time (JIT) compiler that can convert MSIL to native x86 code at a moment’s notice.
All of the extra detail that .NET needs to rummage through to know about your assembly appears in this essential section. Some of these items, when taken together, make up the assembly’s manifest, a type of document that completely describes the assembly to the world. In the following list of metadata elements, I’ve noted which items appear in the manifest:
(Part of the manifest.) This is defined on the Application tab of the project’s properties.
(Part of the manifest.) That’s the four-part version number, as in 1.2.3.4. You’ve probably been wondering all day how you could set this number in your own projects. Your patience will be rewarded in this chapter’s "Project" section, where I will demonstrate not just one, but two ways to set the assembly version number.
(Part of the manifest.) This includes the publisher’s public key.
(Part of the manifest.) This is especially useful when you need to create language-specific resource files.
(Part of the manifest.) Single-file assemblies will show only the EXE or DLL filename, but some assemblies may include several files in this section. All files in an assembly must appear within the same directory, or in a directory subordinate to the assembly file that contains the manifest.
(Part of the manifest.) Some assemblies “export” some of their types for use outside the application. The details of those types appear here.
(Part of the manifest, but in multifile assemblies, each file will contain its own list of references.) The metadata includes a listing of all external assemblies referenced by your application, whether they are private or appear in the GAC. This list indicates which specific version, culture, and platform-target of the external assembly your assembly expects.
(Not part of the manifest.) All types crafted in your assembly are fully described within the metadata. Also, any additional metadata you added to your types through Visual Basic’s attribute feature appear here.
In multifile assemblies, the manifest-specific elements appear only in the “main” file of the assembly.
The manifest is a subset of the metadata within your assembly. I hate to say that it’s the most important part of the metadata—but it is. The manifest is the public expression of your assembly, and the only way that .NET knows whether it is legit. It’s sort of like the “Nutrition Facts” label put on American food packaging (see Figure 5-2).
When you look at the food label, you know what the food package contains—although no one really knows what riboflavin is. When you look at the manifest for an assembly, you know at a glance what the assembly contains, and what requirements it has before it can be loaded and run.
Even before .NET burst onto the scene, executables and libraries already contained some “metadata,” such as the version number of the file. But this data wasn’t used to manage access between software components, nor was it organized in a generic and extensible way. The metadata in .NET embodies all of these attributes.
The presence of both the MSIL and metadata in each assembly makes these files very readable and understandable. With the right tools, even I seem to understand them. And if I can, anyone can, which leads to a big problem. Companies invest a lot of time and money in their software development efforts, and they don’t want any rinky-dink two-bit startup reverse-engineering their code and getting all their algorithmic secrets. To prevent this casual reading of any .NET application, Microsoft and other third parties include obfuscators, software programs that scramble the contents of an assembly just enough so that it’s hard for humans to understand, but not for the .NET Framework. I’ll talk more about obfuscation in Chapter 22.
It may be a bad thing for people to access the content of an assembly, but it’s great when the code in an assembly can access itself. .NET includes a feature called reflection that lets you examine the contents of an assembly. You generally use this feature to access metadata in your own assembly, but it also works with any available assembly. Most reflection-related features appear in the System.Reflection
namespace.
Through reflection, you can extract pretty much anything stored in the metadata of an assembly, including details on all types, their members, and even the parameters included with function members. This is why obfuscation is so important to vendors; between the compiled MSIL and the metadata, you can virtually regenerate the entire source code for an application from just its executable. The source code would be in MSIL, but it wouldn’t be that tough for someone to massage much of it back into Visual Basic or C#.
.NET applications (EXE files) are an instance of an assembly. But a single application can include multiple assemblies; it fact, it almost always does. I wrote a little program that uses reflection to list all assemblies actively being used by the program itself. I gave the program the default name of WindowsApplication1. When I ran the program against itself, it generated the following list:
mscorlib Microsoft.VisualStudio.HostingProcess.Utilities System.Windows.Forms System System.Drawing Microsoft.VisualStudio.HostingProcess.Utilities.Sync Microsoft.VisualStudio.Debugger.Runtime vshost System.Data System.Deployment System.Xml System.Core System.Xml.Linq System.Data.DataSetExtensions Microsoft.VisualBasic WindowsApplication1 System.Runtime.Remoting
Wow! Seventeen assemblies, including WindowsApplication1, the main program. Most of the assemblies are framework-supplied DLLs. For Microsoft.VisualBasic
, it’s the Microsoft.VisualBasic.dll assembly; for System
, it’s the System.dll assembly. All of the assemblies (except the main program assembly) are shared libraries from the GAC. The application can also support private assemblies loaded from local DLL files.
The .NET Framework automatically loaded these assemblies for me when WindowsApplication1 started up; it figured out which ones needed to be loaded by looking in the manifest for WindowsApplication1. When the framework loaded each assembly, it checked to see whether those assemblies in turn needed additional assemblies loaded, and so on. Pretty soon, your once-simple application becomes a dumping ground for assemblies all over the GAC. But that’s OK, since the purpose of .NET is to manage it all.
The .NET Framework, with its thousands of classes, contains a lot of packaged logic that I can use in my own programs. But I don’t have all of the many assemblies and their classes memorized (yet), and it takes time to wander around the FCL documentation. With so many classes available, I sometimes shudder when I think of the effort it will take me to find just the right class or feature I need to accomplish some development task.
Fortunately, I’m not the only one who thinks this way; Microsoft agrees with me. Historically, Visual Basic programmers were sheltered from the complexities of Windows application development. Not that they needed to be; we all know that Visual Basic developers are generally a cut above the rest. But there was “the Visual Basic motto” to contend with: Make Windows Development Fast and Easy. And calling some esoteric method deep within the bowels of the System
namespace just to get a minor piece of data is neither easy nor fast.
To bring back some semblance of the pleasant experience previously available in Visual Basic development, Microsoft introduced the My
pretend namespace in its 2005 release of the language. The My
pretend namespace collects a lot of useful features from all around the FCL, and organizes them in a much smaller hierarchy for simple and direct access. I briefly mentioned My
in Chapter 1, but now is a good time to take a closer look at what it does.
The My
pretend namespace looks a lot like other namespaces, such as System
, System.Reflection
, and System.Windows.Forms
. But it’s not really a namespace—it’s pretend! For one thing, you can’t use the Imports
keyword to create a shortcut to branches within its hierarchy. Also, some sections of the hierarchy are dynamic; they change as your project changes. Table 5-1 lists the major nodes of the hierarchy.
Table 5-1. Major nodes in the My namespace hierarchy
Branch | Available features |
---|---|
| Provides information about the current application, including culture settings and the deployment method. |
| Gives further details about the application and its assembly, including the name and version. |
| Allows you to generate trace and logging output to registered logging destinations. Used only with client applications. |
| Provides access to general resources located on the local computer. |
| Plays named and system sounds through the computer’s speakers. |
| Retrieves data from the system clipboard, and lets you add your own data to the clipboard in a variety of predefined and custom formats. |
| Gets the current system date and time dished up in a variety of ways. |
| Provides tools to examine and manipulate files and directories on local or networked filesystems. |
| References special Windows folders such as Documents, Desktop, and Temp. |
| Provides information about the installed operating system and other local system resources. |
| Exposes the current state of the keyboard and its keys. |
| Makes available a few properties of the local computer’s mouse. |
| Reports on network availability, and provides features to interact with that network. |
| Lets you interact with the system’s serial ports. |
| Reads and writes keys and values in the registry. |
| Presents a dynamic collection of all forms defined in the application. This node is available only in Windows Forms applications. |
| Allows you to generate trace and logging output to registered logging destinations. Used only with ASP.NET applications. |
| This object is similar to the older Active Server Pages |
| Provides dynamic access to application-specific or locale-specific resources included with the application. |
| This object is similar to the older Active Server Pages |
| Provides dynamic access to the application settings system. |
| Identifies the current Windows user, including authentication information. |
| Presents a collection of available web services for use in the application. This node is not available in ASP.NET applications. |
The My
namespace includes a lot of features you will use regularly, including access to the version number of the application. Instead of typing System.Reflection.whatever to get to the version number’s “major” component, you can now just type:
My.Application.Info.Version.Major
Need a list of assemblies, but you’re too lazy to type the word Reflection? Try:
My.Application.Info.LoadedAssemblies
Need to know the time right now in England?
My.Computer.Clock.GmtTime
Can you communicate over the local area network?
My.Computer.Network.IsAvailable
Who is running this computer anyway?
My.User.Name
There isn’t much in the My
namespace that you can’t already do with standard FCLs. There are even a few parts of My
that are repeats of features already included in the Visual Basic language, although with some enhancements. For instance, Visual Basic includes a Kill
command that lets you delete files. The My.Computer.FileSystem.DeleteFile
method also removes files, but it offers additional options, including one that lets you send the file to the Recycle Bin instead of just losing it forever.
Directives are Visual Basic statements—but then again, they’re not. The two key directives—#Const
and #If
—provide instructions to the compiler on how to handle a block of Visual Basic source code. (A third directive, #Region
, helps to visually present source code within Visual Studio, but it has no impact on the compiler or the final compiled application.) By using directives, you can tell the compiler to include or exclude specific chunks of source code from the final project. So, they aren’t really Visual Basic source code statements, but they are available only in Visual Basic.
Why would you want to include or exclude code in an application? Well, I can think of several good reasons, some of which involve the CIA and former Federal Reserve chairman Alan Greenspan. But the most common use is when you want to produce two different versions of your application, based on some condition. For example, you may sell an “express” version and a “professional” version of a product. Much of the code is identical for the two versions, but the professional version would include features not available in the express version. Also, the express version may include a simplified presentation for a feature that has a more complex usage in the professional edition.
Some software products fulfill this need by using standard Visual Basic conditions.
If (professionalVersion = True) Then ShowWhizBangFeatures( ) Else ShowLaughableFeatures( ) End If
This, of course, works just fine. But the express application still contains all the enhanced features. Since it can’t access any of that code, why even include it on the installation CD? If you use directives, you can mark down that problem as solved. Directives use conditional expressions, much like the professionalVersion = True
condition in the preceding block of code. But they are defined with the #Const
statement, and are called compiler constants.
#Const fullVersion = True
This statement defines a Boolean compiler constant. The constant can be used only with directives; if you try to use fullVersion
in a standard Visual Basic statement, the compiler will complain. But it will work just fine in the #If
directive.
#If (fullVersion = True) Then ShowWhizBangFeatures( ) #Else ShowLaughableFeatures( ) #End If
This code looks a lot like the previous code block, but with the added #
signs. It looks the same but it’s not. With the plain If
statement, the following code gets compiled into the final application:
If (professionalVersion = True) Then ShowWhizBangFeatures( ) Else ShowLaughableFeatures( ) End If
Yeah, the whole block of code. But with the directives, what gets included in the compiled application depends on the value of fullVersion
. If fullVersion
is True
, this gets compiled into the compiled application:
ShowWhizBangFeatures( )
The other four lines are gone; they’ve vanished . . . into thin air, as though they never existed. But in this case, it’s a good thing. The goal was to have a version of the assembly completely devoid of the undesired code, and that’s what happened.
To set the fullVersion
compiler constant to generate the full version, you include this line at the top of each source code file that includes conditional #If
code blocks:
#Const fullVersion = True
When you’re ready to generate the “express” version, just change each of these lines to their False
counterpart:
#Const fullVersion = False
Somehow, changing this line in every source code file that needs it seems like a lot of work, and it is. And what happens if I forget to set one of them to the right version? No good, I can tell you.
To keep Visual Basic developers from running down the halls screaming more than they normally would, Visual Studio provides a few different ways to set compiler constants once, and have them apply to every part of the application. The most common way to do this is through the project properties’ Compile panel (see Figure 5-3). Click on the Advanced Compile Options button, and then add your global compiler constants to the “Custom constants” field.
Now, by adding either fullVersion = True
or fullVersion = False
to this field, you can build different versions of the application. The Visual Basic compiler also provides features that let you set up different compile scripts for your project. I won’t talk about it in this book, but you can read up on the MSBuild tool in the Visual Studio documentation if you need this level of control.
Besides Booleans, compiler constants can be numbers and strings. The Visual Studio environment also defines some compiler constants for you. The DEBUG
and TRACE
constants are True
or False
based on the “Define DEBUG constant” and “Define TRACE constant” checkboxes that appear in Figure 5-3. The VBC_VER
constant identifies the version of the Visual Basic compiler being used; it is set to 9.0 in Visual Basic 2008.
Assemblies aren’t just souped-up EXE or DLL files; they contain gobs of metadata, including the manifest, that make .NET applications self-describing. The compiler uses this information to correctly configure and process the managed MSIL code in each assembly.
Although not actually parts of an assembly, this chapter also discussed the My
namespace and directives, two Visual Basic features that impact what gets included in your assembly.
This chapter’s project officially kicks off the coding of the Library Project (muted applause). We’ll start off with something simple: building the About
form that provides basic information about the application, including its version number.
Load the Chapter 5 (Before) Code project, either through the New Project templates or by accessing the project directly from the installation directory. To see the code in its final form, load Chapter 5 (After) Code instead.
Our goal is a pleasant form that conveys basic information about the program, a form that looks something like Figure 5-4.
Like any Visual Basic application for Windows, the creation of this form involves two steps: (1) adding controls to the form; and (2) writing the related code.
If there is one area where Visual Basic excels, it is form creation. Programs can be created by the simple dragging and dropping of prebuilt controls onto the surface of a prebuilt form. It’s all done from within the comfort and convenience of the Visual Studio Integrated Development Environment (IDE), as shown in Figure 5-5.
The displayed environment includes four key areas, which I’ve labeled with letters in Figure 5-5:
This listing of controls includes not only display controls, but also controls that expose no specific user interface, such as the Timer
. (If you don’t see the toolbox, select the View → Toolbox menu command.) To add a control to a form, double-click the control in the toolbox, drag it from the toolbox to the form, or draw the control on the form after first selecting it from the toolbox.
Place any control that exposes a user interface here. The form is WYSIWYG, so you can see the final result as you design the form.
All files related to your project appear here. For the current project, you will see only the My Project entry and an entry for the form, Form1.vb. There are actually more files. If you click the second button from the left at the top of the Solution Explorer, it will show you additional files, most of which are managed by Visual Studio on your behalf.
When you select a control on your form surface, or the form surface itself, or an item in the Solution Explorer, the properties of the selected item appear in this area. You can alter the settings of many properties by typing in the new setting. Some properties include special tools to assist you in setting the property value.
If you haven’t done so already, open the form Form1.vb in design view by double-clicking it in the Solution Explorer. We’ll add eight text labels, three shape and line elements, two web-style hyperlinks, a command button, and a picture to the form’s surface. I’ve already added the picture to the form for you, with an image of some books, naming it SideImage
.
Set up the form by adjusting the following properties from their defaults. Click on the form surface, and then modify these property values using the Properties panel.
Property | Setting |
---|---|
| AboutProgram |
| False |
| FixedDialog |
| 440, 311 |
| CenterScreen |
| About the Library Project |
Next, add the eight basic text labels to the form’s surface using the Label
control. You’ll find this control in the toolbox. As you add each Label
control, use the following list of settings to set the properties for each label. The included text matches my situation, but feel free to modify the content as needed.
Label name | Property settings |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Let’s add some lines and colored sections to the form. Visual Basic 6.0 included distinct shape controls for lines, rectangles, and ellipses that you could apply directly to the form surface. .NET no longer includes these items; you have to add them by hand using source-code-specified drawing commands.[3] But we can simulate lines and rectangles using the standard Label
control, sans the text.
Label name | Property settings |
---|---|
|
|
|
|
|
|
If the BackgroundSide
label obscures the graphic, right-click on the label and select Send To Back from the shortcut menu that appears.
The LinkLabel
control is similar to the more basic Label
control, but you can include “links” in the text, clickable sections that are similar to the links on a web page. We’ll use these to display the web site and email address. Add two LinkLabel
controls to the form and use the following settings to configure each control’s properties.
LinkLabel name | Property settings |
---|---|
|
|
|
|
The final control to add is a button that lets the user close the form. Add a Button
control to the form with the following properties.
Button name | Property settings |
---|---|
|
|
Forms can be configured so that a press of the Esc key triggers a Button
control on the form, as though the user was clicking on the button instead of pressing the Esc key. To do this, click on the form surface, and then set its CancelButton
property to ActClose
. We had to delay this step until the button was actually added to the form; the CancelButton
property would not have allowed a setting for a nonexistent button.
Well, the form should look pretty good by now. The last thing I like to do is to set up the tab order, the order in which the user accesses each field on the form when pressing the Tab key on the keyboard. To edit the tab order, select the form surface and then select the View → Tab Order menu command. Each control on the form that can be given a tab order value will suddenly have a tab order number next to it. Click on each number or control in order until you get the arrangement you want. (See Figure 5-6 to view how I ordered the controls.) Finally, select the View → Tab Order menu command again, or press the Esc key, to leave the tab ordering process.
You can also set the tab order for each control by modifying its TabIndex
property using a zero-based numbering system. However, it’s usually faster to set these values by clicking on each control in order.
Now it’s time to add some real Visual Basic code. Not that we haven’t added any until now. Everything we did on the form, although we didn’t see it happen, was converted into Visual Basic source code. Let’s take a quick look. In the Solution Explorer, click on the Show All Files button, the second button from the left. When all the files appear, click on the “plus sign” next to Form1.vb, and finally, double-click Form1.Designer.vb (see Figure 5-7).
Since it’s more than 200 lines of source code bliss, I won’t be printing it here. But look it over; it’s all pretty interesting. As you dragged-and-dropped controls on the form and modified its properties, Visual Studio edited this file on your behalf. It’s part of your form’s class (all forms are classes that derive from System.Windows.Forms.Form
). You can tell by the Partial
keyword at the top.
Partial Public Class AboutProgram Inherits System.Windows.Forms.Form
Most of the action happens in the InitializeComponent
procedure. When you are finished looking it all over, close up the designer code and return to the form surface. To make our form a real and interesting form, we need it to do three things:
Show the actual version number of the application. This should be determined and displayed right when the form first appears.
Jump to the appropriate web site or email recipient when clicking on the link labels. These events get processed in response to a user action.
Close the form when the user clicks the Close button. This is also a user-driven event.
Let’s start with the easy one, closing the form. I’m sure you remember about events from Chapter 1. Events are blocks of code that are processed in response to something happening, most often a user action such as a mouse click. All of the actions we want to perform on this form will be in response to a triggered event (lucky us). The easiest way to get to the “default” event for a control is to double-click the control. Try it now; double-click the Close button. When you do, the IDE opens the source code view associated with the form, and adds an empty event handler (the ActClose_Click
subroutine).
Public Class AboutProgram Private Sub ActClose_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles ActClose.Click End Sub End Class
Every forms-based event (and in fact, most other types of events) in .NET has pretty much the same arguments: (1) a sender
argument that indicates which object triggered this event; and (2) the e
argument, which allows sender
to supply any additional information that may be useful in the event. In this case, the sender
argument will be a reference to the ActClose
button, since that’s the object that will generate the Click
event. A button’s Click
event doesn’t have any more useful information available, so e
is the default object type, System.EventArgs
, which is pretty much just a placeholder, and the object from which all of the more interesting e
argument types derive.
The name of this event handler is ActClose_Click
, but if you want to change it to FredAndWilma
, that’s fine; it won’t mess up anything. But you must keep the Handles ActClose.Click
clause intact. This is the part that links the event handler to the actual event.
The code to close the form is extremely simple. Enter it now, either by using the first code snippet for this chapter or by typing it directly.
Insert Chapter 5, Snippet Item 1.
' ----- Close the form. Me.Close( )
This statement says, “I’m the AboutProgram
form/object, and I command myself to close.” If you run the program right now (press the F5 key), you close the form by clicking on the Close button. Since the AboutProgram
form was the only form in the application, closing it automatically ended the entire application, no questions asked.
OK, back up to the second item, the web-style links. You could go back to the form surface and double-click on each link label to create an event handler for each label’s default event (in this case, the LinkClicked
event). But you can also add the event handler subroutines right in the editor, either by typing the code yourself (which is no fun) or by using the two drop-down lists just above the editor window (see Figure 5-8).
The Class Name list appears on the left side. Selecting an entry from this list updates the righthand list, the Method Name list. To add an event handler template for the CompanyWeb
’s LinkClicked
event, first select CompanyWeb from the Class Name list, and then select LinkClicked from the Method Name list. The following code block appears in the code window:
Private Sub CompanyWeb_LinkClicked(ByVal sender As Object, _ ByVal e As System.Windows.Forms. _ LinkLabelLinkClickedEventArgs) _ Handles CompanyWeb.LinkClicked End Sub
This template’s argument list is a little more interesting, since its e
argument is an object of type System.Windows.Forms.LinkLabelLinkClickedEventArgs
. The LinkLabel
control allows you to have multiple web-style links in a single control, interspersed among regular text. The e
argument has a Link
property that tells you which of the links in the control was clicked by the user. Since our labels have only a single link, we won’t bother to check it. We’ll just show the web page immediately anytime the link is clicked.
Insert Chapter 5, Snippet Item 2.
' ----- Show the company web page. Process.Start("http://www.timaki.com")
The Process
object is part of the System.Diagnostics
namespace, and Start
is one of its shared members that lets you start up external applications and resources. You pass it any valid URL and it will run using the user’s default browser or application for that URL. Let’s try it again with the CompanyEmail
’s LinkClicked
event. Add in the template any way you choose and then type or insert the code that starts a new message to an email address.
Insert Chapter 5, Snippet Item 3.
' ----- Send email to the company. Process.Start("mailto:[email protected]")
The last event to design is one of the first events called in the lifetime of the form: the Load
event. It’s called just before the form appears on the screen. Double-clicking on the surface of the form creates an event handler template for the Load
event. If you prefer to use the Class Name and Method Name drop-down lists instead, select (AboutProgram Events) from the Class Name list before using the Method Name list.
Private Sub AboutProgram_Load(ByVal sender As Object, _ ByVal e As System.EventArgs) Handles Me.Load End Sub
Let’s add code to this event handler that displays the correct version number, using the version information found in My.Application.Info.Version
, an instance of the System.Version
class.
Insert Chapter 5, Snippet Item 4.
' ----- Update the version number. With My.Application.Info.Version ProgramVersion.Text = "Version " & .Major & "." & _ .Minor & " Revision " & .Revision End With
This code uses a With
statement to reduce the amount of typing needed in the main assignment statement. Inside the With...End With
statement, you aren’t required to retype the object name that appears just after the With
keyword—in this case, My.Application.Info.Version
. You can just refer to that object’s members by typing a dot (.
) followed by the name of the member. You could forgo the With
statement and type the full object name each time you wanted to use one of the version values, but this way keeps the code cleaner and less overwhelming.
If you run the program, it will display the currently defined version number, “1.0 Revision 0,” as shown in Figure 5-9.
My question—and I hope I can answer it before the paragraph is finished—is, “Where is that version number defined, and how can it be changed?” It turns out that I do know the answer: the version values are stored as metadata within the assembly. Visual Studio includes a form that lets you modify the basic informational metadata stored in the assembly. To access the form, display the project’s properties (double-click on My Project in the Solution Explorer), select the Application tab, and then click on the Assembly Information button (see Figure 5-10).
Our AboutProgram
form displays the assembly’s version number, which is set using the four text fields next to the Assembly Version label. Those four fields represent the Major, Minor, Build, and Revision numbers of the assembly. Go ahead, set them to some other values, click OK, and run the program again.
Although this form is convenient, it’s just another example of Visual Studio writing some of your project’s code on your behalf. Every field on this form gets saved in a source code file included with your project. To view it, make sure you have the Show All Files button still selected in the Solution Explorer. Expand the My Project item using its “plus sign,” and then double-click on the AssemblyInfo.vb item. This file defines several assembly-specific attributes (which we’ll explore in Chapter 18), including the following informational entries:
<Assembly: AssemblyTitle("The Library Project")> <Assembly: AssemblyDescription( _ "ACME Library Database System")> <Assembly: AssemblyCompany("ACME")> <Assembly: AssemblyProduct("Library")> <Assembly: AssemblyCopyright( _ "Copyright © 2008 by Tim Patrick")> <Assembly: AssemblyTrademark("")> <Assembly: AssemblyVersion("1.0.0.0")>
As you can see, this file has been updated with the values I typed into the Assembly Information form. Thank you Visual Studio! You see the AssemblyVersion
attribute defined here. If you modify these values, the changes will be reflected in the Assembly Information form, and also in your running application and final compiled assembly.
The last thing we will do for now to the AboutProgram
form is to give it a meaningful filename. Currently, it is named Form1.vb, but AboutProgram.vb would be much more descriptive. To change the name, select Form1.vb in the Solution Explorer, and modify the File Name property to “AboutProgram.vb” in the Properties panel. If you still have all the files showing, you will see Visual Studio also update the names of the file’s two subordinate files, the designer file (AboutProgram.Designer.vb) and the resource file (AboutProgram.resx).
Now would be a great time to save your work (File → Save All).
As useful and full featured as the AboutProgram
form is, such forms are seldom the core focus of an application. In the Library Project, this form will be displayed only when triggered from the “Main” form, so let’s add a simple main form now. In Visual Studio, select the Project → Add Windows Form menu command. When the Add New Item form appears, select Windows Form from the list of available items, and give it a name of “MainForm.vb” before clicking the Add button.
When the new form appears, adjust the following properties as indicated.
Property | Setting |
---|---|
| MainForm |
| FixedSingle |
| False |
| 576, 459 |
| The Library Project |
From the toolbox, add a Button
control to the form with the following properties.
Property | Setting |
---|---|
| ActHelpAbout |
| 80, 24 |
| &About... |
If you’re familiar with Visual Basic development from its pre-.NET days, you will recognize the “&” character in the button’s text. This special character sets the “shortcut” for the button. When you press the Alt key and the letter that follows “&” (in this case, A), the program acts as though you clicked on the button with the mouse.
Double-click the button and add the following code to the click event procedure.
Insert Chapter 5, Snippet Item 5.
' ----- Show the About form. AboutProgram.ShowDialog( )
Here we specify a direct reference to the AboutProgram
form. Before the 2005 version of Visual Basic, showing a new form required that you create an instance of the form class before showing it.
(New AboutProgram).ShowDialog( )
That syntax still works, and is the way to go if you need to display multiple copies of the same form on-screen at the same time. However, the AboutProgram.ShowDialog( )
syntax is much cleaner for single-use forms, and more closely reflects how form presentation was done in Visual Basic since its initial release. Actually, this statement is using the My
namespace. The full statement looks like this:
My.Forms.AboutProgram.ShowDialog( )
The My.Forms
collection allows you to reference any form within it without having to say “My.Forms” first. The members of the My.Forms
collection represent default instances of each form in the project.
That’s all the code we need for now, but if you run the program, it will still show only the AboutProgram
form. That’s because the AboutProgram
form is set as the “startup” form. To alter this, open the project’s properties window, select the Application tab, and set the “Startup form” field to “MainForm.”
Since the AboutProgram
form is now being shown as a “dialog” form (through a call to its ShowDialog
method), its behavior is somewhat different. Each form includes a DialogResult
property whose value is returned by the ShowDialog
method when the form closes. Each button on your form can be configured to automatically set this property and close the form. The Close button on the AboutProgram
form does just that; its own DialogResult
property is set to Cancel
, which is assigned to the form’s DialogResult
property when the user clicks the Close button. As a side effect, anytime a value (other than None
) gets assigned to the form’s DialogResult
property, the form closes.
The upshot of that drawn-out paragraph is that you can now delete the event handler for the Close button’s Click
event, and the button will still close the form. Delete the ActClose_Click
procedure from the AboutProgram
’s source code, run the program, and see what happens. The Close button still closes the form, even without the event handler.
You could also have left the procedure there, cleared the Close button’s DialogResult
property, and added the following statement to that button’s event handler:
Me.DialogResult = Windows.Forms.DialogResult.Cancel
That brings to three the number of different ways we can close the AboutProgram
form. It’s the flexibility of .NET at work; there are many different ways to accomplish the same task. So, be creative!
If you’ve still got a little energy left, we can make one more change before this chapter runs out of paper: adding a custom icon to the main form. Just follow these step-by-step instructions:
Display the main form by double-clicking on the MainForm.vb item in the Solution Explorer.
Select the form’s surface.
Select the form’s Icon property in the Properties panel.
Click the " ... " button in this property, and search for the Book.ico file in the Chapter 5 Before subdirectory of the book’s installation directory. You can also use any other .ico file.
Make sure you always save changes. By default, Visual Studio is configured to save your changes every time you run your program, but I like to save often just in case.
This chapter included a lot of manual instruction because there were so many cool Visual Studio features to play with; I just couldn’t help myself. We’ll probably keep up this pace somewhat for a few chapters, but eventually there will be so much code that a lot of it will come from the code snippets.
[3] Microsoft does offer line and shape controls as part of its “Power Packs” for Visual Basic 2005. You’ll find them in the download area of Microsoft’s Visual Basic Development Center, located at http://msdn.microsoft.com/vbasic. As of this writing, 2008 editions of the Power Packs are not yet available, but the 2005 versions will probably work just fine with Visual Basic 2008.
18.222.110.194