The .NET Framework has a remarkable collection of several thousand classes, designed to handle almost every conceivable programming need. Yet there are times when the developer needs a little extra flexibility or performance and turns to legacy applications or low- level system calls. In fact, until all programs are rewritten to .NET standards, you’ll probably have a continuing need to interoperate with the COM world and unmanaged code.
Although you pay a performance penalty whenever you interoperate (data marshaling and additional instructions being the culprits), you sometimes either have no choice because the required functionality doesn’t yet exist in a .NET application, or you simply prefer the legacy applications or functions. This chapter describes how you can make your .NET applications use Microsoft Excel, Microsoft Word, and Microsoft Internet Explorer and how you can call unmanaged dynamic-link library (DLL) functions from managed code.
This sample application shows you how to automate Microsoft Office from .NET.
Application #7: Object-Oriented Features
Not every application can be all things to all people, so sometimes you’ll find that you need the help of another program to accomplish your goals. For example, you might have some raw numbers you’re processing in your Microsoft Visual Basic .NET application, and you decide that you need some sophisticated statistical computations done on the data. One way to meet the need is to use an Excel workbook to process the numbers.
However, you don’t have to click a shortcut to call up Excel because, like all the major Microsoft Office programs, Excel exposes its object model to other programs, which can then invoke and manipulate it programmatically.
This process is known as Automation, and it lets one application (the client) create an instance of another (the server) and use the commands from the server’s object model to perform a variety of tasks. This sample application shows how you can use Automation to access Microsoft Office applications from a Visual Basic .NET application.
Automation lets you use a program such as Word or Excel like a component, so you must first set a reference to the Word or Excel object library. Then you can programmatically create an instance of Word or Excel and get some work done with it.
Adding a Reference to a COM object
If you want to set a reference to Excel, for example, choose Project | Add Reference. In the Add Reference dialog box, choose the COM tab (because Office applications live in the COM world). Find the reference you need, such as Microsoft Excel 10.0 Object Library, click Select, and then click OK. The reference will be set and will show up in your References folder as Excel.
Behind the scenes, however, Microsoft Visual Studio has performed a little sleight of hand on your behalf by creating a runtime callable wrapper (RCW) around the COM component and setting the reference to the wrapper rather than to the COM component itself. The wrapper is needed because your .NET application has no idea how to interact with a COM component, which requires that you access it via interfaces—and that’s just not the .NET way.
The RCW bridges the gap between .NET and COM by serving as the broker between the two worlds, translating calls from your application into the format the COM component wants and marshalling data back and forth between the two entities.
You can create an RCW in a couple ways. One is by using a command-line utility named tlbimp (type library importer), which is located in your FrameworkSDKin folder. Once you create the wrapper you can set a reference to it and access the COM component through it.
But it’s much easier to simply set a reference to the COM component from within Visual Studio .NET, as described earlier, because Visual Studio .NET automatically creates the wrapper and also sets the reference to it.
If you created the reference to the Excel object library just described, here’s how you can see the wrapper that was created for you. Click on the Project menu, and choose Show All Files. Then in the Project Explorer, expand the bin folder. You’ll see a file named Interop.Excel.dll and, if you have other COM component references, possibly other files beginning with Interop.
Now that you have the wrapper and the reference, you’re ready to go to work by instantiating an Excel Application object and using its methods. That’s what we’ll describe in the code walkthrough. Figure 12-1 shows the sample lunch menu, ready to be exported to Excel, where the calorie average of the foods listed will be calculated.
Figure 12-1: With Automation, your Visual Basic .NET application can use Microsoft Office programs such as Microsoft Excel to do work on its behalf.
We’ll show how to use Excel to perform calculations and how to use Word to do spelling checking.
This code creates and fills a DataSet from an XML document and then binds it to a DataGrid. Then it exports the contents of the DataSet to an Excel spreadsheet and runs an Excel function that calculates the average of values in a column. First, we’ll load the grid with items on a fictitious lunch menu contained in an XML document.
PrivateSubbtnGetMenu_Click(... ⋮ dsMenu=NewDataSet() dsMenu.ReadXml(Application.StartupPath& "..menu.xml") WithgrdMenu .CaptionText= "Today'sMenu" .DataSource=dsMenu.Tables(0) EndWith ⋮ btnExport.Enabled=True EndSub
Now we want to export the contents of the DataGrid to Excel, and then run a simple Average function to determine the calorie average for all the foods. The Excel object model has a hierarchy from Application to Workbook to Worksheet. We first instantiate an Application object, which means we’re creating an instance of Excel. Then we create a workbook and a get a reference to the first worksheet within that workbook. Note the use of CType, which is needed because excelBook.Worksheets(1) returns an Object, which then has to be cast as a worksheet object.
We make the Excel instance visible so that the user can see the data being entered into the spreadsheet. If we don’t do that, all the operations will proceed, but Excel will operate invisibly. (We’ll see an example of that in the Word demo.)
PrivateSubbtnExport_Click(... DimexcelAppAsNewExcel.Application() DimexcelBookAsExcel.Workbook=excelApp.Workbooks.Add DimexcelWorksheetAsExcel.Worksheet=_ CType(excelBook.Worksheets(1),Excel.Worksheet) excelApp.Visible=True
Now we use properties and methods of the Worksheet object to set the column headers and to format the cells and columns. We use the Excel Range object to refer to the set of cells we’re about to work on, and then we apply changes as needed.
Now we’re ready to export the data from our DataGrid. We’ll use a counter that starts at 2 so that the data will be placed beginning in row 2 of the worksheet, following the column headers. Then we’ll loop through the Rows collection of the DataSet and write the data in each row to the cells in Excel.
DimiAsInteger=2 WithexcelWorksheet DimdrAsDataRow ForEachdrIndsMenu.Tables(0).Rows .Range("A" &i.ToString).Value=dr("Item") .Range("B" &i.ToString).Value=dr("Price") .Range("C" &i.ToString).Value=dr("Calories") i+=1 Next EndWith
We want to format the bottom row differently from the data, so we select row 8 from column A to column C and make it Red and Bold. We also set the first cell in the range to Average Calories. Note that A1 in this case doesn’t mean the first cell of the Worksheet, but the first cell of the range in question, A8 to C8.
rng=excelWorksheet.Range("A8:C8") Withrng .Font.Color=RGB(255,0,0) .Font.Bold=True .Range("A1").Value= "AverageCalories" EndWith
Finally, we select the cell that will display the calorie average, and then set the Average formula using the FormulaR1C1 property, which uses numbers to label both rows and columns and makes it easy for us to refer to the current row minus 6 through the current row minus 2. Then we AutoFit all columns and go to the first cell of the sheet. The user is provided with the average calorie count of the items in his lunch menu.
WithexcelWorksheet .Range("C8").Select() excelApp.ActiveCell.FormulaR1C1= "=AVERAGE(R[-6]C:R[-2]C)" .UsedRange.Columns.AutoFit() .Range("A1").Select() EndWith EndSub
If you want to add spelling checking capability to any application, one way to do it is by using the Microsoft Word spelling checker. This example shows how to do that.
The btnSpellCheck_Click event procedure lets the user run the Word spelling checker against whatever text is in the RichTextBox control (loaded earlier with the btnBrowseWord_Click procedure). First, we instantiate the Word Application object. Notice that, unlike the Excel example, we don’t make Word visible, because it doesn’t need to be seen to do a spelling check. We want to let the user either check the entire document or choose a portion of the document to be checked, so we test the length of the RichTextBox selected text. If it’s greater than zero, we check only that portion; otherwise, we check the entire document.
PrivateSubbtnSpellCheck_Click(... DimwordAppAsNewWord.Application() DimhasNoSpellingErrorsAsBoolean DimportionCheckedAsString IfLen(rtfDocument.SelectedText)>0Then portionChecked= "text" hasNoSpellingErrors=_ wordApp.CheckSpelling(rtfDocument.SelectedText) Else portionChecked= "document" hasNoSpellingErrors=wordApp.CheckSpelling(rtfDocument.Text ) EndIf DimspellCheckResponseAsString IfhasNoSpellingErrorsThen spellCheckResponse= "Congratulations,your " &portionChecked&_ " hasnospellingerrors." Else spellCheckResponse= "Your " &portionChecked&_ " hasspellingerrors." EndIf MessageBox.Show(spellCheckResponse, "SpellingCheckResults",_ MessageBoxButtons.OK,MessageBoxIcon.Information) EndSub
This example simply displays a dialog box telling you whether your spelling check succeeded or failed. However, you could enhance this functionality to create a more feature-rich application that mimics the Word spelling checker, including such features as allowing the use of custom dictionaries, and more.
When Visual Basic .NET doesn’t do all that you need, you can enlist the services of other applications, such as those in the Microsoft Office suite. Once you set a reference to the object library you need, Visual Studio .NET automatically creates the necessary runtime callable wrapper to let your .NET application interoperate with the COM component. There’s almost no limit to what you can do with such interoperation if you’re willing to learn the object models of the applications you choose to automate.
This sample application demonstrates two ways of automating Internet Explorer.
Application #9: Use Callbacks
Application #84: Asynchronous Calls
Application #93: Automate Office via COM Interop
Just as you can automate Microsoft Office applications such as Word and Excel (as shown in the “Application #93” section), you can also automate Internet Explorer. You might want to allow a user to browse the Web directly within your application, or you might want to let the user open Internet Explorer to a Web site of her choice from within your application. This sample application demonstrates how to do both.
Using the ActiveX Internet Explorer Web Browser Control
You’re probably familiar with using ActiveX controls in Microsoft Visual Basic 6, and the procedure for using them in .NET is very similar. You need to add the Microsoft Web Browser control to your Toolbox so that you can place it on a form. To do this, select the Toolbox tab on which you want the control to appear. Then right-click the Toolbox and choose Add/Remove Items. The Customize Toolbox dialog box appears, with tabs containing COM components and .NET Framework components. On the COM Components tab, locate Microsoft Web Browser and check the box beside it. Click OK.
The control appears on your Toolbox with the name Microsoft Web Browser and a globe icon. Now you can place the control on a form and use its properties and methods, which we’ll describe in the code walkthrough.
When you put the Microsoft Web Browser control on a form, Visual Studio .NET adds a couple references (AxSHDocVw and SHDocVw) to Microsoft Internet Controls in the project References folder and creates runtime callable wrappers for them both. (See the “Application #93” section for more information on RCWs.)
Automating Internet Explorer
Automating Microsoft Internet Explorer is much like automating Word or Excel, as demonstrated in the “Application #93” section. You set a reference to the object library of the COM application you intend to automate, and then you manipulate it programmatically. To set the reference, right-click your References folder, and choose Add Reference. In the Add Reference dialog box, select the COM tab and locate Microsoft Internet Controls. Click the Select button and the OK button. Visual Studio .NET adds SHDocVw to your References folder and creates an RCW for it. Note that if you had previously put the Microsoft Web Browser control on a form in the project, the SHDocVw reference would already have been set and you’d be ready to go.
Microsoft Internet Explorer offers you a number of ways to customize its user interface, including whether or not to display the address bar, the toolbar, and the status bar. The browser can be displayed in full-screen mode, in which the status bar, toolbar, menu bar, and title bar are hidden. It also offers a theater mode option, which is similar to the full-screen option but which includes minimal navigational buttons and a status bar in the upper right corner.
Now let’s see how we can put these concepts into action.
Coding the Web Browser Control
The Microsoft Web Browser control has a number of methods that let you navigate the Internet just as if you were using a full-scale, standalone browser. The first illustration of that is the following procedure, which uses the Navigate method of the control to display a Web page, based on the URL entered in a text box:
PrivateSubbtnGo_Click(... axBrowser.Navigate(txtAddress.Text) EndSub
It’s important for you to know when the document has been loaded into the browser control so that you can begin to work with the document. The DocumentComplete event provides the necessary notification by firing when the document has been completely loaded. In the following code, we assign the loaded document to axDoc, which was declared at the class level as an mshtml.IHTMLDocument2 interface object. This is an interface you can use to retrieve information about the document, and to examine and modify the HTML elements and text within the document.
Be careful that you don’t attempt to assign axDoc somewhere other than this handler because you’ll get a null object reference if the document has not been completely loaded.
PrivateaxDocAsmshtml.IHTMLDocument2'declaredattheclasslevel ⋮ PrivateSubaxBrowser_DocumentComplete(ByValsenderAsSystem.Object ,_ ByValeAsAxSHDocVw.DWebBrowserEvents2_DocumentCompleteEvent)_ HandlesaxBrowser.DocumentComplete axDoc=CType(axBrowser.Document,mshtml.IHTMLDocument2) txtAddress.Text=axDoc.url HandleNavButtons() EndSub
To enable the user to navigate backward and forward through the history list, the HandleNavButtons procedure determines whether the history has entries and enables or disables the back and forward buttons accordingly. The history property is a member of the mshtml.IHTMLWindow2 interface, so we access it through the parentWindow property of axDoc.
ProtectedSubHandleNavButtons() IfaxDocIsNothingOrElseaxDoc.parentWindow.history.length=0 Then btnBack.Enabled=False btnForward.Enabled=False Else btnBack.Enabled=True btnForward.Enabled=True EndIf EndSub
Now that we’ve set up all the prerequisites, moving backward and forward is easy. We simply use the history property go method to move one step ahead or one step backward in the history list.
PrivateSubbtnBack_Click(... axDoc.parentWindow.history.go(-1) txtAddress.Text=axDoc.url EndSub PrivateSubbtnForward_Click(... axDoc.parentWindow.history.go(+1) txtAddress.Text=axDoc.url EndSub
Coding Internet Explorer as a Standalone Browser
We want to enable the user to set display options before opening the browser, including window size and position and which bars to display. Figure 12-2 shows the available choices as well as the result after navigating to a site on the Web.
Figure 12-2: Using Automation, your application can launch Microsoft Internet Explorer and navigate the World Wide Web.
If the user chooses either the full-screen or theater-mode option, it’s not appropriate to try to set the window size or the bars to be displayed. So we’ll disable those option groups accordingly, using the Options_CheckedChanged procedure, which is an event handler for both the chkFullScreen and chkTheaterMode check boxes. We also want to prevent full-screen and theater mode from being selected together, but we don’t want to use radio buttons, so we handle the issue in the following code by unchecking either one when the other is checked.
PrivateSubOptions_CheckedChanged(ByValsenderAsSystem.Object,_ ByValeAsSystem.EventArgs)HandleschkFullScreen.CheckedChanged,_ chkTheaterMode.CheckedChanged DimisCheckedAsBoolean=chkFullScreen.Checked_ OrchkTheaterMode.Checked grpShow.Enabled=NotisChecked grpPosition.Enabled=NotisChecked DimsndrAsCheckBox=CType(sender,CheckBox) Ifsndr.Name= "chkFullScreen" Then IfchkFullScreen.CheckedThen chkTheaterMode.Checked=False EndIf ElseIfsndr.Name= "chkTheaterMode" Then IfchkTheaterMode.CheckedThen chkFullScreen.Checked=False EndIf EndIf EndSub
With the btnGo2_Click procedure, we’ll open Microsoft Internet Explorer and configure its user interface based on the options selected by the user. We begin by instantiating an SHDocVw.InternetExplorer object, provided it doesn’t already exist.
PrivateSubbtnGo2_Click(... IfieBrowserIsNothingThen ieBrowser=NewSHDocVw.InternetExplorer() EndIf
Now we get to customize the user interface before launching the browser. Note that both the address bar and the menu bar will be displayed only if the toolbar is also displayed.
WithieBrowser IfgrpShow.EnabledThen .AddressBar=chkAddressBar.Checked .MenuBar=chkMenuBar.Checked .StatusBar=chkStatusBar.Checked .ToolBar=CInt(chkToolBar.Checked) EndIf .FullScreen=chkFullScreen.Checked .TheaterMode=chkTheaterMode.Checked IfgrpPosition.EnabledThen IfIsNumeric(txtTop.Text)Then .Top=CInt(txtTop.Text) EndIf ⋮ .Resizable=chkResizable.Checked EndIf
Finally, we make the browser visible and navigate to the URL the user entered. Note the use of the hourglass cursor and of the overloaded Show method of the status form, which takes a message-to-be-displayed parameter.
Remember to set the Visible property of the browser object or else it won’t show up.
Me.Cursor=Cursors.WaitCursor frmStatus=NewfrmStatus() frmStatus.Show("ConnectingtoWebpageandprocessingHTML. " &_ " Pleasestandby...") .Visible=True .Navigate(txtAddress2.Text) EndWith EndSub
We want to know when the document has completely loaded, and the DocumentComplete event provides that notification. However, its event procedure gets executed on a different thread from the one that created the form. This poses a challenge: we can modify the user interface only through the same thread that created it because the underlying user-interface classes are not thread safe. To solve this dilemma, we’ve marshaled the information back to the main thread by using the form’s Invoke method to use a delegate to call the HideStatus method, which hides the status form and restores the cursor to normal. (See the “Application #9: Use Callbacks” section in Chapter 2 and the “Application #84: Asynchronous Calls” section in Chapter 9 for more information on asynchronous calls and delegates.)
PrivateSubieBrowser_DocumentComplete(ByValpDispAsObject,_ ByRefURLAsObject)HandlesieBrowser.DocumentComplete Me.Invoke(NewHideStatusDelegate(AddressOfMe.HideStatus)) EndSub DelegateSubHideStatusDelegate() ProtectedSubHideStatus() frmStatus.Hide() Me.Cursor=Cursors.Default EndSub
You can let your user have free access to the Internet directly from your application with Microsoft Internet Explorer. You can either use the ActiveX Web Browser control, which becomes a part of your form, or invoke Internet Explorer in standalone mode. In either case, the object model is the same, except that the Web Browser control ignores properties such as FullScreen and TheaterMode that make sense only in the full browser.
This sample application demonstrates how to use Win32 API function calls in Visual Basic .NET.
Although the .NET Framework provides thousands of classes, designed to meet almost every programming requirement, you might occasionally want to access low-level operating system functions more directly. In such situations, it’s time for placing a call to the Win32 API. Platform Invoke, or PInvoke, is a service provided by the .NET Framework to enable managed code to call unmanaged functions in DLLs.
Uses for Win32 API Calls
Imagine that you want to determine all the processes that are currently executing, you’d like a list of all active windows, or you want to make a window’s title bar flash to alert the user. Instead of writing code for these tasks, you can take advantage of the large number of functions already existing as part of the operating system. These functions are found in a number of DLLs, of which the most commonly used are Kernel32.dll, which offers functions for managing memory and resources; User32.dll, which lets you manage messages, menus, and communications; and GDI32.dll, which has device output functions such as those for drawing and font management.
Collectively, they’re referred to as the Win32 API, and each of these DLLs is brimming with useful functions you can use right away. But before you rush into using them, be aware that calling DLL functions can be complex, the code in the functions is unmanaged, and the .NET Framework offers many of the same capabilities in an easier-to-use package.
Using the Declare Syntax
To use a Win32 API function, you must first declare it, either with a Declare statement or with the DLLImport method attribute. A Declare statement lets your application know that you want to use an external function found in a DLL. The syntax looks like this:
PublicDeclareFunctionGetWindowsDirectory_ Lib "kernel32" _ Alias "GetWindowsDirectoryA" _ (ByVallpBufferAsStringBuilder,ByValnSizeAsLong)AsLong
The GetWindowsDirectory function retrieves the path to the current Windows directory. Lib specifies the name of the DLL in which the function lives. Alias refers to the actual name of the function within the DLL, a feature that lets you Declare using any name you want to, as long as you Alias to the actual function name. So our example function could have been named FindWindowsFolder, for example, and that’s the name we’d use when calling it.
When a Win32 API function accepts a string parameter, the DLL has two versions of the function: an ANSI version that ends in A (for example, GetWindowsDirectoryA), and a Unicode version that ends in W (for example, GetWindowsDirectoryW).
Once you’ve declared your function, you can use it to find out where Windows lives. You call your function by passing it two parameters: lpBuffer, which is a String (or StringBuilder, as in our previous example), and nSize, which is a Long that you’ll set to the size of the buffer you’re providing. When the function returns, it places the path to Windows in the buffer and returns the size of the path. You could call it as shown in the following example, which you could put in a wrapper class for convenience.
PrivateFunctionLocateWindowsDir()AsString DimsbAsNewStringBuilder(255) DimbufSizeAsLong=255 DimwinSizeAsLong DimwinFolderAsString winSize=GetWindowsDirectory(sb,bufSize) IfwinSize>0Then winFolder=sb.ToString().Substring(0,winSize) EndIf ReturnwinFolder EndFunction
Win32 API declarations must be done in a class or module. In many cases, you’ll find it convenient to create a class that serves as a wrapper around your declared functions, making it easy to call the functions as methods of the class. We’ve taken that approach in this sample application
As an alternative to Declare, you can also use DLLImport, an attribute you can apply to a method that points to an external DLL. See the sample code and the additional reference material for uses of DLLImport. Figure 12-3 shows the ability to uses a Win32 API call to find and display an active window.
Figure 12-3: Platform Invoke makes it possible for your application to make calls to functions in unmanaged Win32 API DLLs, including listing active processes, displaying selected active windows, and gathering system information.
Some DLL functions expect more than simple parameters. Many of them provide a wealth of information, and the easiest way to return it compactly is via a Structure, which is a type that you define that is a lot like a class. You can create an instance of a Structure, and it can have constructors, constants, fields, methods, properties, and more. However it’s different from a class in that you can’t inherit a Structure, nor can it inherit from another class.
One function that requires a Structure to be passed to it is GetVersionEx, which reports on the operating system’s version information, and whose required Structure looks like this:
_ PublicStructureOSVersionInfo PublicOSVersionInfoSizeAsInteger PublicmajorVersionAsInteger PublicminorVersionAsInteger PublicbuildNumberAsInteger PublicplatformIdAsInteger _ PublicversionStringAsString EndStructure
When you’re about to call GetVersionEx, you instantiate an OSVersionInfo object and call it as shown in the following example. When the function call returns, you have access to all the properties of the versionInfo structure. (See the documentation for information on the StructLayout and MarshalAs attributes.)
PrivateSubbtnGetOSVersion_Click(... DimversionInfoAsNewApiWrapper.OSVersionInfo() versionInfo.OSVersionInfoSize=Marshal.SizeOf(versionInfo) ApiWrapper.GetVersionEx(versionInfo) txtFunctionOutput.Text= "OSVersionis: " &_ versionInfo.majorVersion.ToString()& "." &_ versionInfo.minorVersion.ToString()&vbCrLf txtFunctionOutput.Text+= "BuildNumberis: " &_ versionInfo.buildNumber.ToString() EndSub
Now let’s see how we can use these concepts to get a variety of information about the operating system, currently active windows and processes, and more. We’ll examine each tab of the sample application in turn.
Show Active Processes
The btnRefreshActiveProcesses_Click procedure fills a list box with all the currently active processes. It does this by calling the APIWrapper function EnumWindows, which is a wrapper for the Win32 API function of the same name. It uses a delegate to specify that the FillActiveProcessesList function should be called once per active process. Because EnumWindows is unmanaged code, you have to create a delegate to allow it to call FillActiveProcessesList, which is managed code. All three procedures are shown in the following code.
PrivateSubbtnRefreshActiveProcesses_Click(... lvwProcessList.Items.Clear() ApiWrapper.EnumWindows(New_ ApiWrapper.EnumWindowsCallback(AddressOf_ FillActiveProcessesList),0) EndSub PublicDeclareFunctionEnumWindowsLib "user32.dll" _ Alias "EnumWindows" (ByValcallbackAsEnumWindowsCallback,_ ByVallParamAsInteger)AsInteger PublicDelegateFunctionEnumWindowsCallback(ByValhWndAsInteger, _ ByVallParamAsInteger)AsBoolean
To provide a list of active processes, the following function is called once by EnumWindows for each active process. It gets the Window caption and class name and updates the ListView of the active processes. Take a look at the Win32 API functions in the APIWrapper class, and note that they’re declared differently than in Visual Basic 6. All Longs have been replaced with Integers, and Strings have been replaced with StringBuilders.
FunctionFillActiveProcessesList(ByValhWndAsInteger,_ ByVallParamAsInteger)AsBoolean DimwindowTextAsNewStringBuilder(STRING_BUFFER_LENGTH) DimclassNameAsNewStringBuilder(STRING_BUFFER_LENGTH) ApiWrapper.GetWindowText(hWnd,windowText,STRING_BUFFER_LENGTH) ApiWrapper.GetClassName(hWnd,className,STRING_BUFFER_LENGTH) DimprocessItemAsNewListViewItem(windowText.ToString,0) processItem.SubItems.Add(className.ToString) processItem.SubItems.Add(hWnd.ToString) lvwProcessList.Items.Add(processItem) ReturnTrue EndFunction
Show Active Windows
Here we’re doing almost exactly what we did in the preceding example, except this time we want to show only those processes that represent active, visible windows. So we call the FillActiveWindowsList procedure via APIWrapper.EnumWindowsDllImport, which works just like EnumWindows except that it’s defined using DllImport instead of Declare.
PrivateSubbtnRefreshActiveWindows_Click(... lstActiveWindows.Items.Clear() APIWrapper.EnumWindowsDllImport(New_ APIWrapper.EnumWindowsCallback(AddressOf_ FillActiveWindowsList),0) EndSub CharSet:=CharSet.Ansi,ExactSpelling:=True,_ CallingConvention:=CallingConvention.StdCall)>_ PublicSharedFunctionEnumWindowsDllImport(ByValcallback_ AsEnumWindowsCallback,ByVallParamAsInteger)AsInteger EndFunction
This function is called once for each active process by EnumWindows. It passes the handle of the process to ProcessIsActiveWindow to verify that it is a valid window, and it updates the active windows Listbox if it is. You should review the sample code’s ProcessIsActiveWindow procedure, which calls various APIWrapper functions to determine whether a windows process is a valid active window.
FunctionFillActiveWindowsList(ByValhWndAsInteger,_ ByVallParamAsInteger)AsBoolean DimwindowTextAsNewStringBuilder(STRING_BUFFER_LENGTH) ApiWrapper.GetWindowText(hWnd,windowText,STRING_BUFFER_LENGTH) IfProcessIsActiveWindow(hWnd)Then lstActiveWindows.Items.Add(windowText) EndIf ReturnTrue EndFunction
Show an Active Window
When you want an active window to be displayed in the foreground, you can use the ShowWindow Win32 API call to make it so. The following procedure finds an active window based on the values in the Window Caption and Class Name text boxes and then brings it to the foreground. It calls one of four overloads for the Win32 API function FindWindow in the APIWrapper class that allow either a String or an Integer to be passed to the class name and window name. If either of the fields is blank, passing a 0 to the parameter marshals NULL to the function call.
PrivateSubbtnShowWindow_Click(... DimhWndAsInteger IftxtWindowCaption.Text= "" AndtxtClassName.Text= "" Then hWnd=ApiWrapper.FindWindowAny(0,0) ElseIftxtWindowCaption.Text= "" AndtxtClassName.Text<> "" Then hWnd=ApiWrapper.FindWindowNullWindowCaption(txtClassName.T ext,0) ElseIftxtWindowCaption.Text<> "" AndtxtClassName.Text= "" Then hWnd=ApiWrapper.FindWindowNullClassName(0,txtWindowCaptio n.Text) Else hWnd=ApiWrapper.FindWindow(txtClassName.Text,_ txtWindowCaption.Text) EndIf IfhWnd=0Then MsgBox("Specifiedwindowisnotrunning.",_ MsgBoxStyle.Exclamation,Me.Text) Else ApiWrapper.SetForegroundWindow(hWnd) IfApiWrapper.IsIconic(hWnd)Then ApiWrapper.ShowWindow(hWnd,ApiWrapper.SW_RESTORE) Else ApiWrapper.ShowWindow(hWnd,ApiWrapper.SW_SHOW) EndIf EndIf EndSub
FindWindow searches for a window by class name and window name. FindWindowAny takes two Integer parameters and finds any available window. FindWindowNullClassName attempts to locate a window by window name alone. FindWindowNullWindowCaption attempts to locate a window by class name alone.
Various API Calls
On the final tab of the sample form, you’ll find a variety of useful Win32 API calls. Set breakpoints behind each button, and see how they utilize the Win32 API calls.
In this sample application, we’ve shown that although the .NET Framework has almost every functionality you might need, sometimes you just have to get back to basics and do a low-level call. You’ve seen that Platform Invoke (PInvoke) is a way for managed code to call unmanaged functions located in DLLs. Keep in mind that because Win32 API functions are case sensitive, you should alias the function names to fit your own naming conventions. You can use StringBuilders instead of String for most Win32 API function calls. We recommend that you build a wrapper class around your Win32 API function calls. It will make calling them much easier.