Ask Dr. GUI #40

Dr. GUI got mentioned where?

No, it wasn't in People magazine, The Economist, or even at Java World. Rather, it was UNIX Review (now Performance Computing—see http://www.performancecomputing.com). In his regular column entitled "Devil's Advocate," Stan Kelly-Bootle (whom you might also know as the author of The Computer Contradictionary, published by MIT Press) wrote a bit about the good doctor. Check it out at http://www.performancecomputing.com/unixreview/backissu/9802/9802dev.htm.

And now, back to our regularly scheduled show.

Calling all objects!

Dear Dr. GUI,

I have a question about sharing objects between Visual Basic and Visual C++. If I create an object (say ADODB.Connection) in Visual Basic using the CreateObject() method, and pass it to an ActiveX control or server created using ATL, how can I get this object to work in the ATL environment?

Thank you very much for your help,
Jun Jiang

Dr. GUI replies:

Well, the anklebone's connected to the leg bone . . . and you want to connect two controls, using Visual Basic as the "glue." This is a powerful technique, so Dr. GUI's glad you asked about this.

To start with, you will need to do two things within the server or control. The first is to add an Automation method that has an LPDISPATCH parameter. This is the mechanism we will use to pass the server object created in Visual Basic so that it can be used in the ATL server. The second thing is to use the Visual C++ 5.0 #import directive to create a helper class that will be used to wrap the IDispatch pointer passed from Visual Basic.

The ATL server sample code below is based on passing the LPDISPATCH object for another simple ATL object containing a single Automation method called Test.

//********** ATL Automation Server **********
// The #import needs the servers TypeLib. Sometimes it is built
// into the server itself.
#import "G:\Development\AutoServer1\AutoServer1.tlb" no_namespace
// The ATL Automation method is used to pass in the IDispatch of the
// server we will control. In this case, the code is based on a simple
// ATL Automation Server with a single method called Test. The same
// technique, however, may be used with any server including
// ADODB.Connection.
STDMETHODIMP CMyDispRetriever::GetDisp(LPDISPATCH pDisp)
{
    IMyDispPtr pMyDisp;
    pMyDisp.Attach((IMyDisp*) pDisp, true);
    pMyDisp->Test();
    // NOTE: The Destructor for IMyDispPtr will automatically
    // release the interface pointer when pMyDisp goes out of
    // scope at the end of this method.
    return S_OK;
}

The Visual Basic code to activate this has been included in a command button handler in the following code.

'********** VB Code to Create the Servers **********
Private Sub Command1_Click()
    '***** Define the Automation Servers
    Dim AutoServer As Object
    Dim AutoClient As Object
  
    '***** Instantiate the Automation Servers
    Set AutoServer = CreateObject("MyDisp.MyDisp.1")
    Set AutoClient = CreateObject("MyDispRetriever.MyDispRetriever.1")
  
    '***** Pass AutoServer object to AutoClient
    AutoClient.GetDisp AutoServer
End Sub
Don't miss the big event!

Dear Dr. GUI,

How the !#$%@#$ do you handle events in the Windows Scripting Host with VBScript? We've heard rumours of shadowy extra parameters to CreateObject and suchlike, but we've not been able to make anything work. The samples don't use events, and the documentation hints, but says little. It's a pretty poor Host that has no knowledge of the Event.

Mark Bartel

Dr. GUI replies:

Shadowy parameters? Not really—but let's shed some light on the subject. You are correct that there is an extra parameter to the CreateObject method of the Wscript object. While the first parameter defines the object to be created, the second parameter is a string that creates a custom subroutine that can be connected to an event of the object. The string should end with the underscore (_) character. What happened? Uh, well, some releases of the documentation for Windows Scripting Host leave this parameter out. Oops.

For instance, the following example will create an InternetExplorer.Application object, which then connects the DownloadBegin method of that object to the routine whose name begins with "CustomSub":

Set IE = WScript.CreateObject("InternetExplorer.Application", "CustomSub_")
IE.Visible = TRUE
IE.Navigate ("http://www.microsoft.com")
WScript.DisconnectObject IE        ' Undo the event connection.
Set IE = Nothing
Sub CustomSub_DownloadBegin()
   WScript.Echo "Download begins at " & Now
End Sub

This information can be found in the documentation at http://www.microsoft.com/management/wsh.htm.

Looking at the bits with a microscope

Hi good Dr.!

I remember reading in one of the Developer Network News editions in the fall that MSDN members would get access to the binary file formats for Word and Excel.

Was I dreaming? Has my memory totally failed me? The only references that I can find relate to the old RTF stuff.

Thanks in advance,
Andrzej B. Zydron

Dr. GUI replies:

Ah, yes, file formats. It's very important to know your way around when operating. Did Dr. GUI ever tell you about the time that he did an operation before he'd even looked at an anatomy text? No? Never mind. You don't wanna know.

The binary file formats for the Microsoft Office 97 programs are available in the MSDN Library, either on your subscription CDs or online at the MSDN Web site: http://www.microsoft.com/msdn/. If you're not an online member (it's free), you'll need to register before you can access the Library.

To access the binary file format information, select Microsoft Office Development from the contents directory. Click to open Office, and then click again to open Microsoft Office 97 Binary File Formats. Select the appropriate category, such as "Microsoft Word 97 Binary File Format" or "Microsoft Excel File Format."

Coolbars in Visual Basic

Dear Dr. GUI,

First, a word of thanks: thanks.

Seriously, I do appreciate the amazing information you unearth in every issue.

Now for my question. As a Visual Basic developer, I'd love to include a tool bar like the one in Internet Explorer 4.0. Making the buttons become active when under the cursor is cool, and certainly doable in VB.

What I'm after is the nifty little buttons with the built-in drop down list. Can you help?

Best regards,
D. Jones

Dr. GUI replies:

You're welcome. Glad to help—but remember, the real thanks go to our specialists (listed at the end of the column). When the going gets tough, call in a specialist.

Microsoft now provides the Coolbar control (COMCT332.OCX), which allows developers using Visual Basic to create toolbars similar to those found in Internet Explorer. The Coolbar and a sample Visual Basic project that demonstrates its use can both be found at: http://www.microsoft.com/vstudio/owner/default.asp.

Unfortunately, the toolbar buttons used by this control do not have the drop-down capability you are seeking. However, the Coolbar control is a container object, which opens up a world of opportunity. Since it is a container, you are not restricted to simply using buttons on the toolbar. Instead, you could add other controls, such as a combo box similar to the address box in Internet Explorer. To a degree, the combo box could be used to provide the capability you are seeking. Plus, since the Coolbar control is a container, you could also write your own ActiveX control and add it to the Coolbar to give your application the functionality you desire.

Take care when lifting heavy packets

Dear Dr. GUI,

Our network architects get hernias when they see the overhead that the custom marshaled ADO Remote Recordset generates in terms of packet size. When you examine the MEOW packet for the ADOR.Recordset pointer you'll see that the field names are duplicated (and they're in UNICODE, the string data is in ASCII). There is also a very interesting URL "http://www.microsoft.com/ActiveX vip..." preceding the column (field) descriptors.

Why is there so much extraneous data? I'd appreciate it if you could cast some light on the structure of the Recordset's marshaled stream.

Cheers,
Pieter Potgieter

Dr. GUI replies:

Pieter, you have sharp eyes! You certainly pass Dr. GUI's vision tests!

OLE DB, which is used by ADOR.Recordset, uses UNICODE, so the names are UNICODE—not ANSI. Doing UNICODE-to-ANSI translation would be slow, and the conversion could potentially cause problems for Far East languages. The two copies of names are the actual name and the base-column name that the column came from. So they're both needed.

Often they're the same, but it's possible they're not and we need to know that, because there is more to data remoting than just passing around a table.

The URL is an RDS 1.5 bug and will be fixed in the next release.

Avoiding conflicts in the hospital

Dear Dr. GUI,

We are a development team from Spain.

We are writing an application on Visual Basic 5.0 (SP3) that runs under Windows 95 with an Access 97 database that resides on a Windows NT 4.0 server. The application adds new patient citations to a medical chart. Nurses see the chart as a grid and select an hour from the grid to add a new citation.

The problem comes when different nurses on different network stations try to add citations on the same hour. Nurse 2 can't see that Nurse 1 is adding or has added a citation on the same hour that they have selected until their grid is refreshed.

How can we avoid this? We are studying the use of dynamic recordsets because we understand that when one user adds, removes or modifies a record, the other users can see the changes in their recordsets. But how we can determine if the dynamic recordset has changed to refresh the grid consequently?

Thanks in advance,
Felix Gonzalez
Tres de Nueve Development Team

Dr. GUI replies:

Now is this in a real hospital? Wow. You're closer to medicine than Dr. GUI is.

I am assuming that the DBGrid is bound to a DataControl and that the recordset is populated with a record for each hour (or period of time) and the users are trying to add field information where they see blanks in fields next to the hour.

You need to do two things here:

The default behavior of the DBGrid is that the Edit method of the recordset is not invoked until the user changes data and then moves to another row. Therefore, if you need to implement pessimistic locking (that is, you want the record locked as soon as a user begins typing new data), you need to call the DataControlName.Recordset.Edit method in the DBGrid ColChange event. You must also make sure that the LockEdits property of the RecordSet equals True (the default). This will set the lock on the page where the record is located when the Edit method is invoked.

You need to add some error handling code in the ColChange event of the DBGrid:

To refresh the DBGrid, call DBGrid.ReBind in the error handler when data has changed and call DBGrid.ReBind in DataControlName.Recordset.Reposition.

Private Sub DBGrid1_ColEdit(ByVal ColIndex As Integer)
Dim iChoice As Integer
   On Error GoTo ErrorHandler
   Data1.Recordset.Edit
   Exit Sub
ErrorHandler:
   Select Case Err
      Case 3260   ' The record is locked.
         iChoice = MsgBox(Err.Description & " Retry?", vbRetryCancel)
         If iChoice <> vbYes Then
         DBGrid1.ReBind
      DBGrid1.Refresh
      Exit Sub
   End If
      Resume  ' Try the edit again.
      Case 3197
      ' Data in the Recordset has changed.
      ' Try to edit the record again.
      MsgBox "Data Has Changed"
      DBGrid1.ReBind
      Resume
   Case Else ' Unanticipated error.
      MsgBox "Error " & Err & ": " & Error, vbOKOnly
      DBGrid1.ReBind
   End Select
End Sub
Private Sub Data1_Reposition()
    DBGrid1.ReBind
End Sub
Reporting bugs to Microsoft

Dear Dr. GUI,

How do I report errors in Microsoft's manuals?

Once upon a time I managed to exchange some e-mail with one of the Visual C++ doc writers, but I can't find any reference to a point of contact with the Windows NT people, specifically the NT Server 4.0 manuals. After flogging Microsoft's Web site, the best guess I can offer is that you don't want feedback on your manuals. Surely, I must be mistaken. :-)

Karl Sutterfield

Dr. GUI replies:

In order to prevent contagion and epidemics, it's important to report bugs. Thanks for offering to do so!

To report bugs in Windows NT (code or documentation), you can send e-mail to ntbug@microsoft.com by filling out the template file available under winnt\system32 directory named as "probrep.txt". The bug needs to be detailed, otherwise it will be discarded. Please include the steps in detail to reproduce the bug.

The other option available is to open a Service Request with Microsoft Support (http://support.microsoft.com/). If the problem reported is an actual bug, you will be credited for the incident, and you will have an opportunity to receive feedback from the engineer. However, if it turns out not to be a bug, you will be charged for an incident. Either way, the support engineer will help you work with (or around) the problem.

Mix and match components

Dear Dr. GUI,

Thank you for the article series, I may be starting to understand OLE/COM after all these years. (Editor's note: See Dr. GUI's series of articles about COM and ATL on MSDN Online.)

BUT . . . let me ask you this one.

I am distributing Visual Basic custom ActiveX components within my client company, and my components sometimes use third-party components.

As not all sites want to use all of my components, not all sites want copies of the third-company components I am forced to distribute with my ActiveX controls. I basically want to be able to provide an ActiveX DLL that is able to use a certain third-party OCX if it finds it on the client machine, but will do with the standard Microsoft combo box if it does not find the third-party OCX on the client machine.

This would make a nice practical example in your COM article, I think.

Thanks for reading this anyway,
Laurent Alsina

Dr. GUI replies:

Transplanting controls is much easier than transplanting organs, but you'll still have to operate with care.

This would be a spectacular feature, but Visual Basic 5.0 does not allow this type of adaptability within a compiled component except where the control currently available has the interfaces required to make it compatible with the version referenced in the component.

Since you are using a control from one vendor and your client uses controls from a different vendor, their interfaces (and corresponding CLSIDs, and so on) will not be the same. Their functionality, methods, and properties may be identical, but internally they are registered and treated separately.

The Visual Basic 5.0 integrated design environment attempts to locate the appropriate interface on your system to match those in your project. If it is unsuccessful, it will provide a warning and replace the object with a PictureBox. There is no way to accomplish this within a compiled Visual Basic component.

However, the good doctor likes to try new things and found a technique that may be useful. You could have two forms that are identical except for the combo boxes, and programmatically choose which to show. Your "adaptability" code would have to determine which control was present and decide which form to use. Your application startup code could attempt to instantiate one of the controls and check for errors. If the action fails with a known error (such as 429), the control is not installed or properly registered on the system, and sets a flag accordingly. Check the flag when you have to decide which form to open. When necessary, check the value of the flag to decide which form to show. We started with our Knowledge Base article Q173407 and came up with the following code.

'Check if a particular control is registered correctly, if so show
   'a form that uses it, otherwise show a different form.

   On Error GoTo Err_OCX_Not_Registered
   Dim IsItThere As Boolean
   Dim MyObj As Object
   Set MyObj = CreateObject("anibtn.Anipushbutton")    'Name of control
                                                       'and class

   'The following statement will fail at run time if the control is not
   'registered correctly.
   Set MyObj = Nothing
   IsItThere = True

   Exit_Here:
      If IsItThere Then
         Form2.Show        'form2 has the control we checked.
      Else: Form3.Show     'form3 does not have the control we checked.
      End If
   Exit Sub
   Err_OCX_Not_Registered:
   ' Check to see if error 429 occurs.
   If Err.Number = 429 Then  IsItThere = False    'The Control is not
                                                   'registered.
   Resume ExitHere
   End Sub

We've done some testing of this code, but be sure to test it yourself to make sure it's right.

Yo! Don't activate my window

Dear Dr. GUI,

I have made a status window that shows different results based on activities in another window. The user cannot click on anything in the status window. I have tried to prevent Windows 95 from deactivating other windows when a user clicks in the client area of the status window, but without result.

First I tried the WM_MOUSECLICKACTIVATE but ended up with no windows activated.

Then I tried to catch the WM_ACTIVATE in the status window and then use the window's handle from the message in the call to ActivateForegroundWindow to activate the window just deactivated.

Finally, I tried to make a mousehook intercepting the WM_LBUTTONDOWN. But Windows 95 deactivates the window losing the focus before sending WM_LBUTTONDOWN to my status window. Again I end up with no active window.

What to do? What to do?
Tim Wentzlau

Dr. GUI replies:

What you want to do is not something Dr. GUI would prescribe. Because Windows wants to allow the user to have control over what goes on rather than a renegade application usurping that control, this kind of thing is difficult to do. Windows changes the activation of applications asynchronously to protect applications from hanging each other. By the time the new application is activated, the old application has already lost activation. You cannot therefore prevent an application from losing activation. However, you can immediately give it the activation back as soon as it loses it.

There are a couple of ways of doing this. The more complicated way is to set up a system-wide CBT hook to keep track of which application has activation (by processing HCBT_ACTIVATE). When your status window is about to gain activation, you can prevent it from doing so. Then you immediately call SetForegroundWindow to the last window that had activation. Note that the upcoming changes in SetForegroundWindow for Windows 98 and Windows NT 5 may make this more difficult or not possible at all. And, of course, dealing with system-wide hooks can be tricky and affect system performance.

The simpler method is to simulate pressing ALT+TAB by using the function keybd_event.

Note   SendInput supercedes keybd_event, but is available only on Windows NT 4.0 Service Pack 3 and later, while keybd_event is available on Windows NT, Windows 95 and Windows CE operating systems.

You want to do this when processing the WM_MOUSEACTIVATE message and only when the focus is switching from another application to yours. (Yes, you can get a WM_MOUSEACTIVATE message again even though you already have the focus.) The following code is an example.

case WM_MOUSEACTIVATE:
           // If the current active app is this app, don't do anything.
           GetWindowThreadProcessId( GetForegroundWindow(), &dwProcID );
           if ( GetCurrentProcessId() != dwProcID )
           {
              // Simulate an ALT-TAB.
              keybd_event( VK_MENU, 0, 0, 0 );          // ALT key down
              keybd_event( VK_TAB,  0, 0, 0 );          // TAB key down
              keybd_event( VK_TAB,  0, KEYEVENTF_KEYUP, 0 );  // TAB key up
              keybd_event( VK_MENU, 0, KEYEVENTF_KEYUP, 0 );  // ALT key up
              return MA_NOACTIVATEANDEAT;
           }
           break;

Be forewarned, though, that the application that was active will temporarily lose activation. This means it will get and process the WM_KILLFOCUS message. This may not be a big concern in most places, but sometimes it might result in the loss of data or the loss of the ability to execute an undo command (especially for those applications that have only a one-level undo). On the other hand, if the user is in the middle of doing something in one application and he clicks on your status window, they should expect that the first application is going to do its finish-up processing.

The chicken or the egg

Dear Dr. GUI,

I never read the good and helpful "Tip of the Day" screen that shows up every time you launch Visual C++. But today, I got one that really called my attention. It was the tip of the day that says: "We use it before you do! Visual C++ was developed using Visual C++". I'm kind of new to the language and was wondering how could this be possible.

Thanks in advance for your answer,
Jose Luis Rojas

Dr. GUI replies:

Dr. GUI is not used to questions about reproduction. After all, he's a programmer, not an obstetrician! But he's happy to share what he knows about this one.

It's actually not as simple as "Which came first—the chicken or the egg?" since we have several versions of both chickens and eggs.

Since the inception of C as a language, operating systems such as UNIX and Windows have been written in C. Many applications for these operating systems have also been written in C, and later, C++. Obviously, the earliest C compilers were not written in C, but since then, compilers and linkers have been written in C/C++, assembly, and other languages.

Of course, there has to be a starting point for Visual C++, and in this case it is the Microsoft C++ compiler. Our DOS-based C++ compiler was used to create Visual C++ for Windows 3.1. Since the first release of Visual C++, we continue to test our compiler by using it to compile each subsequent release of Visual C++. To improve the quality of upcoming releases of Visual C++, we actually use the beta compiler for the next release to build the Integrated Development Environment (IDE) and associated tools. In addition to the compiler, the IDE can be used by Microsoft to facilitate the development of future versions of Visual C++.

As you can see, this process is circular. The more we improve the Visual C++ development system today, the better our development efforts will become in the future, improving the quality of Visual C++. That's why we at Microsoft believe in eating our own dog food—even though, as any doctor would note, doing so can create digestive disturbances.

Thanks again . . .

. . . to Dr. GUI's specialists this month: Shawn Karr, Darin Fisher, Susan Vinson, Robert Jacik, Daniel Kirby, Dale Sorensen, Mohana Sundaram, Jeff Baughman, Robert Thompson, and Jason Roth—all members of Microsoft Technical Support (http://support.microsoft.com). And many, many thanks to Rick Caudle, specialist leader extraordinaire. You all are truly the heart of Dr. GUI.