Ask Dr. GUI #42

September/October 1998

Great News! Dr. GUI Rejoins MSDN!

If you read Dr. GUI's column on the Web (see http://msdn.microsoft.com/developer/), you'll know this already. But if you don't yet read Dr. GUI on da Web, this will be news to you: MSDN has lured Dr. GUI back into the fold. Previously, the good doctor worked in another group at Microsoft and volunteered his time with MSDN.

But as of the beginning of July (Dr. GUI's tenth anniversary with Microsoft, by the way), Dr. GUI is once again a full-timer at MSDN. The good doctor couldn't be more delighted!

So, that's nice for Dr. GUI, but what does this mean for you? The main advantage is more and better GUI-now that the good doctor won't have to restrict his GUI-making to off-hours. Specifically, Dr. GUI is looking forward to writing more columns, perhaps providing more questions and answers, and certainly writing more code for you to use and learn from. You'll be able to enjoy the fruits of Dr. GUI's labor pains here and at his Web office.

What Would You Like to See?

Now that Dr. GUI's practice is open for regular business hours, he'd really like to know what his patients need. The online column has evolved into a tutorial of sorts, giving you a byte-sized introduction to important and cool technologies. But what else could you use? Perhaps some of you have a prescription that would be even better than what the good doctor is currently offering. If you have a great idea, please e-mail it to Dr. GUI at the address at the end of this column.

Memory Price Update

Dr. GUI notes that as of this writing, memory prices have started to head upwards again-although they're still under U.S. $1 per megabyte. So if you're waiting for the prices to bottom out before you expand your machine's memory, now's probably a good time to buy that memory you need.

COM and Get It

Dr. GUI's pleased to announce that his eight-part series on building COM objects in C++ and using them from a variety of Microsoft languages is complete. You can find the series by going to http://msdn.microsoft.com/developer/archive/bycolumn.htm and clicking "Dr. GUI." And by the time you read this, there should be some columns on C++ templates and ActiveX Template Library (ATL), too.

And Now, Your Questions Making Your Own AppWizard for a Console App

Dear Dr. GUI,

Hi Doc!

It hurts when I do this...

I'm trying to use the AppWizard to create an AppWizard for a non-MFC application-specifically, a console app. I've been unsuccessful. Looking under the covers, I've noticed that the "ATL COM AppWizard" uses the "AppWizard" .DLL and it creates non-MFC applications.

Any idea how to modify the AppWizard to generate a Console application wizard?

Adrian Michaud

Dr. GUI replies:

Let Dr. GUI relieve some of your pain: It is possible to create a custom AppWizard for a console app. For an excellent description on how to create a custom AppWizard that generates non-MFC projects, see the following article in the Microsoft Knowledge Base: Q173483, "HOWTO: Create Custom AppWizards that Generate Non-MFC Projects," located at http://support.microsoft.com/support/kb/articles/q173/4/83.asp.

Each Custom AppWizard project has a method called C<projectname>AppWiz::CustomizeProject(IBuildProject* pProject). You can override this method and use the IBuildProject interface methods to add and remove compiler and linker settings. See the aforementioned Knowledge Base article for details.

In addition to the settings mentioned in the article, you will also have to make the following changes:

In the compiler settings, the default custom AppWizard defines _WINDOWS. This will need to be removed and _CONSOLE will need to be defined.

varSwitch = "/D \"_WINDOWS\"";
pConfig->RemoveToolSettings(varTool, varSwitch, varj);
varSwitch = "/D \"_CONSOLE\"";
pConfig->AddToolSettings(varTool, varSwitch, varj);

The linker settings also need to be modified. The custom AppWizard defines /subsystem:windows. This will need to be removed and /subsystem:console will need to be defined.

varSwitch = "/subsystem:windows";
pConfig->RemoveToolSettings(varTool, varSwitch, varj);
varSwitch = "/subsystem:console";
pConfig->AddToolSettings(varTool, varSwitch, varj);

See the article for definitions of varSwitch, varTool, varj and pConfig. For information on the IBuildProject interface and methods, see the Developer Studio Objects section of the Visual C++ documentation (Developer Studio Environment User's Guide, Automating Tasks in Developer Studio).

Speed Problems with Dev Studio 5.0

Dear Dr. GUI,

I have followed and enjoyed your articles for a couple years, now I have a question that I am sure would be of high interest to all your developers...

Why does it take so long (3-4 minutes on a 300 MHz Pentium pro w/128 Mb ram) to open a project? In fact, after loading Service Pack 3, it seems to take a little longer every week?

My projects compile to about 1.2 Megs (release version), and have maybe a hundred class objects of my own. The real delay seems to be in the "opening classes" section of the studio.

I have looked in the registry to see if some pocket of data is growing indefinitely, but don't see anything out of the ordinary...

Waiting for your ideas...

Bill Stamp

Dr. GUI replies:

This too sounds like a painful condition. Dr. GUI has two prescriptions for you-one will ease your pain short-term, and one will be a long-term solution.

You mentioned you have "...maybe a hundred class objects." If your project contains ATL objects and there are more than 64 classes, your load time will increase. This is a known bug. But the workaround is quite simple: delete the .ncb file and Developer Studio will create a new one-and the slow response time will disappear.

Sometimes slow loading is also a problem when the InfoView tree has many books and chapters open when Visual C++ is shut down. In this case, close the open tree nodes before you shut Developer Studio down by clicking on the plus sign of the topmost node; this will reduce the startup time.

The long-term prescription? Upgrade, of course-neither of these problems exist in Visual Studio version 6.0.

Dynamically Creating ActiveX Controls in Visual Basic Forms

Dear Dr. GUI,

We're currently building a system in Visual Basic 5 whose functionality we would like to extend after release (along the lines of plug-ins). Is there any way to late bind an ActiveX control on a Visual Basic form?

Sort of:

dim ctl as control
set ctl = createcontrol("MyControls.mycontrol")
form1.controls.add ctl
ctl.text="Hello World"

Unfortunately our clients are still using Windows NT 3.51 so the Internet Explorer control is not available.

I've even tried using SetParent to rip a control off a form in a wrapper ActiveX dll and stick it on a form in my application, but this kills the tab-order so the control can't get focus. Interestingly enough, if a treeview control has focus immediately before the setparent'ed control gets focus, everything works fine!

Any suggestions?

Andy Clapham

And Also:

Dear Dr. GUI,

Thank you for the article series, I may be starting to understand OLE/COM after all these years.

BUT... let me ask you this one.

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

As not all sites want to use all of my components, not all sites want copies of the 3rd 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 third-party combo box OCX if it finds it on the client machine, but use the standard MS Combo-box otherwise. See what I mean? (No kidding, I really need this.)

To cut a long story short, what I need is:

A way to load a not-already-referenced ActiveX components at run time in a Visual Basic 5 EXE or DLL.

I guess there must be a way somehow, after all, VB5.exe manages to do it for itself.

Plus, this would make a nice practical example in your COM article. (I think.)

Thank for reading this, anyway.

Alsina Laurent

Dr. GUI replies:

Dr. GUI loves it when he can answer more than one question with the same answer. What both of you want to do is to add a control to a Visual Basic form without knowing the exact type of the control at compile time-you want to add it dynamically at run time.

In Visual Basic version 5.0, you can't quite do this, but you can come close: just use the Load statement on a control array. (This requires that you know the exact type of the control, however.) Sure, Visual Basic does what you want internally, but Visual Basic isn't written in Visual Basic, if you get the good doctor's drift.

With the new Visual Basic 6.0, you can do exactly what you want by using the Add method of the Form's Controls collection. More on this in a bit.

In Visual Basic 5.0, to dynamically add a control to a form you must first have an existing control array, such as an array of Textboxes. Once you have an existing array of controls, you may add an element to it by using the Load statement. For example, if your control array of Textboxes presently consists of two elements, you can add a new TextBox and manipulate it as follows:

Load Text1(2)
Text1(2).Text = "First New Text Box"
Text1(2).Top = 500
Text1(2).Left = 20
Text1(2).Visible = True

The preceding code will add a third element to the control array, set its properties, and display it on your form.

With Visual Basic 6.0, you are not limited to control arrays. Instead, you can add just about any kind of control you desire by using the new Add method of the Controls collection. For example, to add a new CommandButton to a form and manipulate its properties you would execute the following code:

Dim cmdButton as Object
Set cmdButton = Form1.Controls.Add("VB.CommandButton", "newButton")
cmdButton.Caption = "New Button"
cmdButton.Width = 1200
cmdButton.Height = 1100
cmdButton.Visible = True

Note, however, that Visual Basic 6.0 applications don't run on versions of Windows NT earlier than 4.0, so Mr. Clapham will have to use the Visual Basic 5.0 method for his Windows NT 3.51 customers. That means having to include the references in your project. You may want to build separate versions of your product to work around this for now.

If the control you are adding is licensed, you will need to provide license information for the control. In order to address this, a Licenses collection has been added to Visual Basic 6.0. This call will look like the following:

Licenses.Add "MSFlexGridLib.MSFlexGrid.1"

For third-party controls you will likely need to provide the value of the License key along with the ProgID. This License key will be need to be provided in the optional second parameter to the Licences.Add method. To obtain the License key please contact the vendor of the third-party control.

Lastly, in order to determine whether a particular control is loaded on a machine, you can make a call to RegEnumValue and look for the class ID of that control in HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\. If the control's class ID exists, the control is registered on that machine and you will be able to create the control dynamically in Visual Basic 6.0. For more information on RegEnumValue, please refer to the following Microsoft Knowledge Base article: Q178755, "HOWTO: Enumerate the Values of a Registry Key," located at http://support.microsoft.com/support/kb/articles/Q173/0/09.ASP?LNG=ENG&SA=PER.

I've Been Hooked, But Not Like I Want

Dear Dr. GUI,

With my question I reply to the problem Michele Mazzurco already encountered in the MSDN News issue from Jan/Feb 1998 (Vol. 7, Number 1).

The problem was to redirect all keyboard and mouse input to one window. You solved this problem with installing a hook for these messages.

My question is: If I install this hook, are all the keyboard messages captured? Like: CTRL-ALT-DEL, CTRL-ESC, ALT-TAB, etc.

And: Is a screen saver, when running, therefore noninterruptible with the keyboard & mouse messages being captured?

Did Windows 95/98 and Windows NT react in the same manner regarding this hook?

If you are to answer all these questions with a YES, OF COURSE, please reveal a complete code for setting up this hook, because I wasn't able to install the hook-example shown in the issue Vol. 7, No.1. (for example, is it necessary to install the hook in a DLL?)

Thanks for your response,

Uwe Andersen

Dr. GUI replies:

I guess you've hooked poor Dr. GUI with this question.

I'll answer the last question first, because it is the easiest. Yes, you must install system-wide hooks in a dynamic-link library (DLL) because the hook procedure must be loaded into each process space that is being hooked. This is accomplished easily in Windows with DLLs, and is true for both system-wide hooks and thread-specific hooks where the thread being hooked is not in your process. There are exceptions to this rule, most notably the journal hooks. Also, you should make the call to SetWindowsHookEx inside the DLL.

A normal keyboard hook (WH_KEYBOARD) can swallow keystrokes by returning TRUE in the hook procedure. However, this will not work for system keyboard events such as the ones you listed. For Windows NT version4.0 Service Pack 3 and later, you can install a low-level keyboard hook (WH_KEYBOARD_LL) that can detect these system keyboard events and eat all of them, except for CTRL+ALT+DEL. This exception is for security reasons.

Having said that, there are some ways to get around that exception, although Dr. GUI doesn't recommend it. In Windows NT, you can write a GINA (Graphical Identification 'N' Authentication) DLL to handle the security exception. (Do a full-text search on "GINA" in Online Help to learn all about it.) Dr. GUI's preference is to leave system keys such as CTRL+ALT+DEL alone.

In Windows 95/98 you can write a filter device driver. There is also a trick for Windows 95/98 that is described in the Microsoft Knowledge Base article Q154868, "HOWTO: Block CTRL+ALT+DEL and ALT+TAB in Windows 95/98 in VB," located at http://support.microsoft.com/support/kb/articles/q154/8/68.asp. You can make a call to SystemParametersInfo(SPI_SETSCREENSAVERRUNNING, TRUE, lpvoid, 0) in order to disable the system keyboard events, including CTRL+ALT+DEL. Calling it again with the TRUE changed to FALSE will re-enable these key sequences.

The code for setting up the hooks will look like this:

hhookMouse = SetWindowsHookEx(WH_MOUSE, 
   (HOOKPROC) MouseFunc,  hInstance, 0);

…which will cause the following hook function to be called:

LRESULT CALLBACK MouseFunc(int nCode, WPARAM wParam, LPARAM lParam)
{
if ( nCode >= 0 ) 
         return 1;
else
   return(CallNextHookEx(hhookMouse, nCode, wParam, lParam));
}

Don't forget to unhook your hook before you exit by calling the following:

UnhookWindowsHookEx(hhookMouse);

Now for the special part of your question-screen savers. Under Windows NT, screen savers run under a different desktop, where the hooks will have no effect. Once you're in the screen saver desktop, any mouse or keyboard activity will stop the screen saver. Under Windows 95/98, the screen saver is running in the same environment as the hooks. So, once the screen saver is running, the hooks will prevent keyboard or mouse activity from stopping the screen saver. Here's the interesting part: While the hooks will prevent mouse and keyboard activity from stopping the screen saver, they will not prevent such activity from delaying the screen saver from starting. That is, the screen saver will not start as long as you are clanging on the keyboard or monkeying with your mouse, even though the hooks are eating those events. This is true for Windows NT as well. So, to answer your question, the screen saver is noninterruptible with these hooks running under Windows 95/98, as long as you've taken care of the CTRL+ALT+DEL possibility.

WebBrowser Control Not Tabbing in MFC Application

Dear Dr. GUI,

I have been subscribing to MSDN for years. This is the first time I have been really stuck on a problem. Hope you can help. I built the example program from Mastering MFC Development, chapter 11, exercise 1, "Using the Web Browser Control." Everything works great, except that whenever I'm on a web page, I cannot tab through the page like you can in Visual C++ 5.0 or Internet Explorer. I ran the example application on the CD of Mastering MFC Development 5.0, and it doesn't work either. It does work when you embed a Web Control into Visual Basic 5.0. Can you help me?

Thanks,

Robert Bickne

Dr. GUI replies:

When the Web Browser control is embedded in a CView or CWnd class, keystrokes do not work properly. You may notice the following symptoms:

This functionality in CView and CWnd classes is by design: These classes do not carry the additional overhead necessary to forward these keyboard messages to child windows for processing. But it's easy to add this functionality to your derived class by overriding the PreTranslateMessage function in the parent class of the Web Browser control as follows:

BOOL CMyView::PreTranslateMessage(MSG* pMsg)
{
if (IsDialogMessage(pMsg))
         return TRUE;
else
   return CView::PreTranslateMessage(pMsg);
}

The good doctor assumes that you've derived your class from CView; if that's not correct, replace CView in the last line with the name of the immediate base class of your view.

This is described in detail in the Microsoft Knowledge Base article Q165074, "PRB: Keystroke Problems in CView/CWnd WebBrowser Control," located at http://support.microsoft.com/support/kb/articles/q165/0/74.asp.

You can write the Doc at drgui@microsoft.com.

Thanks…

…to Dr. GUI's team of specialists this month: Gerard Collop, Donald Hrascs, Robert Jacik, Brian P. Wright, Robert Thompson, and Scott Roberts. And special thanks to Dr. GUI's chief assistant, Rick Caudle. Without Rick, the operating room wouldn't run on time.