July/August 1999
Sightings of knobby knees and sandals with socks are increasing with frightening regularity. Even the esteemed doctor has adopted the developer uniform for summer (even if it does look a little odd with his lab coat).
Now that he's comfortably attired, Dr. GUI is ready to tackle your questions. But first, a little news:
If your apps would benefit from an industry-standard macro language (and whose apps wouldn't?), you'll want to know that Microsoft has released Visual Basic® for Applications (VBA) version 6.0 (http://msdn.microsoft.com/vba/). In addition to a bunch of great new language features (including digital signatures, multithreaded operation, and COM add-ins), it's easier to integrate into your applications and easier to license. In fact, you can get an evaluation copy of the VBA 6.0 SDK, version 6.1 for free (not including Internet access or shipping and handling charges) at http://msdn.microsoft.com/vba/corpdev/eval/evalcd.asp. Since the evaluation copy is free, you may want to check it out to see whether your apps could benefit from being scripted by VBA—it's easier than ever!
You've probably heard about Microsoft® Office 2000 Developer, which includes Office 2000 Premium and provides an SDK for Office 2000. (If not, check it out at http://msdn.microsoft.com/officedev/.)
Well, it's available now—so come 'n get it.
Also available is the SQL Server™ 7.0-compatible Microsoft Data Engine (MSDE) that Visual Studio and Office developers can get, use, and redistribute for a very low price: free. It comes with recent copies of Visual Studio® and with Office 2000 Developer—but if you need it, you can get a copy at http://msdn.microsoft.com/vstudio/msde/.
If you've ever wanted to write a Java Virtual Machine (VM) and/or compiler that supports Microsoft J/Direct®, Java/COM, and delegates, now's your chance: Microsoft is making the Developer Tools Interoperability Kit available at http://www.microsoft.com/java/. You'll also find information on the Microsoft VM there—it makes interesting reading.
There's a new Pocket Outlook® Object Model SDK available for download at http://msdn.microsoft.com/cetools/platform/poomsdk.asp. This allows you to write Microsoft Visual C++® and Visual Basic applications that run on Microsoft Windows® CE devices and access the Pocket Outlook databases on the device. Not happy with the contact editor? Write your own. The applications are endless. Note that because the original Palm-size PCs don't fully support COM, POOM only supports Visual C++ on those devices. But you really wanted one of the new color devices anyway, right?
In preparing for his Tech·Ed keynote demo, Dr. GUI was introduced to the wonders of MSDN Online. (The good doctor had spent too much time with the CD and not enough knowing his own online product.)
There are a number of features you should check out. One of the best is Web-based access to the Microsoft-sponsored newsgroups, which includes a cool feature: e-mail notification when something new is posted to a thread you're interested in. There are also Online Special Interest Groups (OSIGs) for various Microsoft technologies and products, and Dr. GUI's favorite: Members Helping Members.
Members Helping Members is a database of thousands of folks who've signed up to answer your questions. You select a member by expertise, location, and even languages spoken. Then you send the member e-mail, and the member responds.
But if you've been reading Dr. GUI, you're already an expert—so why not sign up and help someone else out? You can limit the number of questions you'll get, so we promise you won't get swamped.
Whether you want to ask a question or sign up as an expert, check out Members Helping Members at http://msdn.microsoft.com/community/mhm/.
Dr. GUI's mentioned before that memory has dropped well below US$1 per megabyte—he recently saw 128 MB available for $98. Running Windows 2000 Server at home looks better and better every day … if he ever buys a machine faster than a 133 MHz Pentium. (Hey! It was a HOT machine three years ago!)
Dr. GUI recently moved to a new office—a not-infrequent occurrence at the ever-changing company he works for. As it turns out, the office number is 1130—the model number of the IBM minicomputer he first learned to program on (in FORTRAN mainly, and a little in RPG). It had a disk cartridge that held 1 MB (500 K 16-bit words) on a 15-inch platter, 16 K bytes (8 K 16-bit words) of honest-to-goodness magnetic core memory, a card reader, a console with an IBM Selectric typing element, and a line printer. The IBM 1130 would barely fit in Dr. GUI's current office 1130.
But the coolest feature (besides Dr. GUI's somewhat buggy tic-tac-toe program that cheated—written before Dr. GUI got as far as "subroutines" in the FORTRAN reference manual) was that it was a multimedia machine: We could play music on it by setting an A.M. radio near the CPU while running programs designed to produce radio frequency interference. Or you could play music on the line printer—but that wasted paper and ink. And you could do gray-scale pictures on the printer by overprinting characters appropriately.
Things have come a long way since 1977.
I have a CToolBar that uses the TBSTYLE_EX_DRAWDDARROWS style, and toolbar buttons that use the TBSTYLE_DROPDOWN style to make drop-down buttons. I'd like to make the arrows on the right side of the buttons narrower, like the ones in Microsoft Word's font color button. How do I do this? Will I have to create an owner-drawn toolbar?
Thank you,
Lynn Pettipaw
Dr. GUI wishes people would just accept their buttons as they are rather than dieting to get them narrow and having them get wider again. But because you insist, here's a way to narrow your buttons. But this isn't as elegant as we'd like, since the toolbar that Microsoft Word uses isn't documented, and the Windows toolbar doesn't provide for owner-draw. The Windows toolbar does, however, provide custom draw notification messages that you can use to draw the down arrows. One note: while you can make the arrows appear narrower, you won't actually be able to make them take up less space because Windows doesn't provide a way for us to specify the size of the item drawn. So the next button will still be drawn in the same place. If you really need more flexibility, Dr. GUI recommends checking out some of the third-party Microsoft ActiveX® control packages that are available.
For this job, we're going to use the custom draw notification messages that are sent by some common controls to notify their parent window about drawing operations. These notifications are sent only for Comctl32.dll version 4.70 and later, which ships with Microsoft Internet Explorer 3.0 or later. The notifications are sent in the form of a WM_NOTIFY message and are very useful when only parts of a control, such as Toolbar, are to be custom drawn by the user.
You can handle these notification messages in CToolBar's parent, CFrameWnd. Because you will be custom drawing the down arrow part of the toolbar, you need to have three arrow graphics (bitmap files): one for the highlighted item, one for the clicked item, and one for the normal state. Using a graphical tool such as Windows Paint, draw these arrows so they're properly formed when highlighted, selected, and in the normal state. You can then draw these bitmaps on the arrow portion of the toolbar using BitBlt.
The drawing stages for the control are in NMCUSTOMDRAW structure's dwDrawStage member. The ID of the item being drawn is in the dwItemSpec member of the same structure. Other members of the structure include the device context of the toolbar and the state of the item (disabled, selected, hot item, and so forth). Refer to the NMCUSTOMDRAW documentation in the MSDN Library for details.
You do the custom drawing operation in the CDDS_ITEMPOSTPAINT drawing stage of the custom draw notifications—in other words, after Windows has painted the arrow, we'll repaint it. (Since the area is small, users shouldn't notice any screen flicker.)
In our code, you check if the item being painted is your item with the down arrow (dwItemSpec), determine the state of the item (uItemState), load the appropriate graphic, get the item's bounding rectangle (CToolbar::GetItemRect()), and then draw the graphic on the arrow area using BitBlt(). (You can optimize some by prefetching and caching the bitmaps, but for demonstration purposes, it's clearer to fetch it just before you need it.)
Following is the code snippet from the OnNotify() override for CMainFrame:
BOOL CMainFrame::OnNotify(WPARAM wParam,
LPARAM lParam, LRESULT* pResult)
{
LPNMHDR lpnm = ((LPNMHDR)lParam);
if(!lpnm)
return 0;
switch(lpnm->code)
{
//... other notify messages
case NM_CUSTOMDRAW:
{
LPNMCUSTOMDRAW pnmCustDraw =
(LPNMCUSTOMDRAW)lParam;
switch(pnmCustDraw->dwDrawStage)
{
case CDDS_PREPAINT:
*pResult = CDRF_NOTIFYITEMDRAW ;
return 1;
case CDDS_ITEMPREPAINT:
*pResult = CDRF_NOTIFYPOSTPAINT ;
return 1 ;
case CDDS_ITEMPOSTPAINT:
{
CDC memDC1;
LPNMTBCUSTOMDRAW lpnmtbcd =
(LPNMTBCUSTOMDRAW)lParam;
if( pnmCustDraw->dwItemSpec ==
ID_MYTOOLBARBTN)
{
HBITMAP hbitmap ;
if(CDIS_SELECTED & lpnmtbcd->
nmcd.uItemState)
{
hbitmap = ::LoadBitmap
(GetModuleHandle(NULL),
MAKEINTRESOURCE
(IDB_SELBITMAP));
}
else
if(CDIS_HOT & lpnmtbcd->
nmcd.uItemState)
{
hbitmap = ::LoadBitmap
(GetModuleHandle(NULL),
MAKEINTRESOURCE
(IDB_HOTBITMAP));
}
else
{
hbitmap = ::LoadBitmap
(GetModuleHandle(NULL),
MAKEINTRESOURCE
(IDB_NOSELBITMAP));
}
memDC1.CreateCompatibleDC (NULL);
RECT rect,rect1;
int nIndex =
m_wndToolBar.CommandToIndex
(ID_MYTOOLBARBTN);
m_wndToolBar.GetToolBarCtrl().
GetItemRect(nIndex,&rect);
// Get the rect of any other
// non-arrow button so that
// we can draw only on the
// arrow area with our arrow bitmap
m_wndToolBar.GetToolBarCtrl().
GetItemRect(1,&rect1);
::SelectObject(memDC1.m_hDC,hbitmap);
::BitBlt(pnmCustDraw->hdc,rect.left
+ rect1.right - rect1.left ,
rect.top ,rect.right - (rect.left
+ rect1.right - rect1.left) ,
rect.bottom ,memDC1.m_hDC,0,0,SRCCOPY);
}
*pResult = CDRF_DODEFAULT;
return 1;
}
default:
*pResult = CDRF_DODEFAULT;
return 1;
}
}
}
return CFrameWnd::OnNotify
(wParam, lParam, pResult);
}
I'm using the Windows NT 4.0 Access Control Lists (ACL) function GetSecurityInfo() to get the discretionary access control lists (DACLs) and the SecurityDescriptor of a windowstation and its desktop. The function worked as expected, but it blocked for 30 seconds before returning. The two calls below actually took close to 60 seconds to complete. My machine is not the fastest in the world, but this is kind of ridiculous. I tried using the low-level ACL calls, and they were returned immediately. What gives?
dwRes = GetSecurityInfo(hwinsta, SE_WINDOW_OBJECT,
DACL_SECURITY_INFORMATION, NULL, NULL, &pwsDACL, NULL, &pwsSD);
// ...
dwRes = GetSecurityInfo(hdesk, SE_WINDOW_OBJECT,
DACL_SECURITY_INFORMATION,
NULL, NULL, &pdtDACL, NULL, &pdtSD);
Thank you,
Eddy Ho
Hmmm ... Dr. GUI's looked at the symptoms, and has made his diagnosis: You've found a bug in the implementation of the GetSecurityInfo function. Your code is correct—it would work if it weren't for the bug. The good doctor has reported this bug to the epidemiologists at the Center for Bug Control.
But we're not sure when the bug will be fixed so, for now, Dr. GUI suggests you continue to use the low-level API GetUserObjectSecurity for obtaining security information on windowstation and desktop objects.
I'm developing graphical applications on Windows CE 2.11. With Windows CE 2.0 I used to write code with Visual C++ 5.0 using the AppWizard in Microsoft Foundation Classes (MFC) for Windows CE. Now that I've installed Visual Studio 6.0 and Windows CE 2.11, I can't find the MFC for Windows CE AppWizard in either Visual Studio 6.0 or the Windows CE Platform Builder. Where did it go? Is there any documentation about developing graphical applications on Windows CE 2.11?
Thank you,
Riccardo Laderchi
An AppWizard is like love: If you go searching for it, you have to look in the right place. And the Platform Builder isn't the right place to be looking for the MFC AppWizard. Let the good doctor explain.
Windows CE comes in many different flavors. There are several target platforms that are defined by Microsoft and OEM partners—they're Handheld PC, Handheld PC Pro, Palm-size PC, and Auto PC. MFC and Visual Basic run only on these target platforms (and not necessarily on all of them—check the docs).
But in addition to those target platforms, Windows CE is a modular embedded operating system that allows you to create your own custom versions of Windows CE to run your own custom hardware platforms. Creating these custom OSs is the function of Platform Builder. Since MFC isn't written to run on arbitrary platforms, the MFC AppWizard isn't available from Platform Builder. (Platform Builder does, however, allow you to create your own custom SDK headers and libraries to match your custom platform.)
The MFC AppWizard, you remember, was for the Microsoft Windows CE Toolkit for Visual C++ 5.0. Unfortunately, this add-on doesn't work with Visual C++ 6.0—thus the missing AppWizard.
To develop MFC applications for the Microsoft-defined Windows CE target platforms using Visual C++ 6.0, you need the Windows CE Toolkit for Visual C++ 6.0. Once you've installed the toolkit, you'll find the MFC AppWizard for Windows CE and several other AppWizards. The documentation on developing Windows CE applications can be found in the Windows CE library, which comes with the toolkit. For more information on the toolkit, visit the Windows CE Toolkit Web site (http://msdn.microsoft.com/cetools/).
If you're building your own hardware platform rather than writing apps for one of the Microsoft-defined target platforms, you'll want to use the Platform Builder. For information on it, see the Windows CE Embedded Systems home page (http://www.microsoft.com/windowsce/embedded/default.asp).
I'm having a little problem with the MPEG audio decoder filter included in the DirectShow SDK. I have written several source/capture filters reading MPEG audio streams. When I connect one of these filters to the MPEG audio decoder filter, it plays fine on the first play, but it doesn't work when I stop and try to play it again. This problem also occurs in the graph editor. Please help me!
Thank you,
Olivier Molero
The good doctor's great-great grandmother, Dr. Anna Litical-Engine, had a witty saying for just about any occasion. I can hear her muttering now: "Sonny, if you're going to start something, start from the beginning." (That wasn't one of her better ones, but it'll have to do.)
One of the most common problems for the scenario you've described is the failure of the source filter to set the file pointer back to the beginning of the file when it's finished playing. If you don't do this, the next time your filter starts running, it will already be at the end of the file.
To determine if this is the cause, set a breakpoint in your filter's Stop() method and step through your code. Confirm that you're resetting the necessary variables so the next time your filter starts to run it will start reading from the beginning of the file. Also, when the Filter Graph Manager transitions filters to the run state, it will first call each filter's Pause() method so it can queue up and send data to the downstream filter. Set a breakpoint in your filter's Pause() method and confirm that your reader is, in fact, reading from the beginning of the file and delivering data to your filter's output pin.
How can I include an application wizard selection in the Projects section of the New dialog box? This is the dialog box that appears when the user selects New from the File menu. I would like to have a selection in the Project box that, when selected, will automatically set up the developer workspace to some preset parameters. Any insight?
Thanks,
Alex Akinnuoye
First we get someone who just plain old wants an AppWizard. And now we have someone who has one, but wants something different. Is there no pleasing you folks? <smile>
As it turns out, it's possible to create your own AppWizard—and doing so is one of the features of Visual C++ that Dr. GUI suspects is sadly underused.
So how do you create an AppWizard? Use an AppWizard, of course! The Custom AppWizard project type allows you to create an AppWizard that you will subsequently use to create projects with customized starter files. It's available in the Professional and Enterprise Editions of Visual C++ 6.0 (and 5.0, although the implementation is different, so be sure to check the docs).
To create a Custom AppWizard project, click the File menu, select New, and then select the Projects tab. The Custom AppWizard AppWizard will appear. You can create a Custom AppWizard project based on one of the following:
Once you create the project (let's call it "Test"), you can make modifications and then build it. The corresponding Test.awx file gets copied to the \Program Files\Microsoft Visual Studio\Common\MSDev98\Template directory (this is due to the default Custom Build step for the project). The next time you go to the File menu, select New, and then look under the Projects tab, you should see your "Test" AppWizard. Cool, huh?
For more information on creating a custom AppWizard and the APIs, see the following topic in the MSDN Library:
Visual C++ Documentation
Using Visual C++
Visual C++ Programmer's Guide
Beginning Your Program
Creating a Custom AppWizard
There's also a sample of a Custom AppWizard in the MSDN Library called CUSTOMWZ. If you don't have the latest MSDN Library CD, check out the MSDN Online Library (http://msdn.microsoft.com/library/). To get the latest Knowledge Base articles, go to http://support.microsoft.com/search/.
But wait, there's more! You can also customize your AppWizard to take advantage of the Visual C++ Object Model (also sadly underused, in Dr. GUI's not-so-humble opinion), with which you can automate tasks. The CustomizeProject method gives you a pointer to the IBuildProject interface. You can then use smart pointer classes such as CComPtr and CComQIPtr to obtain pointers to other interfaces. To get into the guts of Visual C++ automation, look for the following topic in the MSDN Library:
Visual C++ Documentation
Using Visual C++
Visual C++ User's Guide
Automating Tasks in Visual C++
I'm curious about the impact of using the standard C++ libraries, including iostreams and disabling exception handling (/GX-). Specifically, are there performance impacts with using /GX? What is the difference between building with /GX and /GX-? I'm using Visual C++ 6.0 SP2.
Thank you,
Steve Moss
As Hamlet might have said (were he a programmer rather than a prince):
To /GX, or not to /GX: that is the question:
Whether 'tis nobler in the mind to suffer
The slings and blue screens of uncaught exceptions,
Or to take arms against a sea of troubles,
And by catching the exceptions, end them?
Okay, enough of that. /GX enables support for synchronous (also called typed or C++) exception handling with the assumption that extern C functions don't throw C++ exceptions. If you disable exception handling (with /GX-), you won't be able to catch exceptions thrown by functions you call, including run-time library functions. If an exception is thrown and not caught, your program will terminate—not a pretty state of affairs. However, enabling exception handling makes your program somewhat larger and perhaps a tad slower. In many cases, the differences are nearly imperceptible. The magnitude of the speed reduction and size increase are highly dependent on the structure of your application.
You'll want to test (with and without /GX) or get assembly-language listings to find out how much overhead will be added to your program. The Visual C++ team has been working hard over the years to minimize the impacts of exception handling, so the added functionality is usually well worth the small performance hit. But you're the only one who can make that decision for your application.
Don't confuse C++ exceptions with asynchronous (hardware) exceptions, such as keyboard interrupts or floating-point exceptions. A C++ exception is an error-handling mechanism in which an exception of a specific data type (usually a C++ class or class pointer) is thrown. Because C++ exceptions are typed, exception handlers can be written to deal only with certain classes of exceptions.
By the way, there are a whole slew of mechanisms available to C and C++ programmers for handling exceptions and errors. For a great overview of these, check out Bobby Schmidt's series on exceptions in his MSDN Online Voices column, called Deep C++ (http://msdn.microsoft.com/voices/deep.asp).
Before Dr. GUI addresses the effect of using or not using /GX, let's talk about what happens when a C++ exception is thrown. When an exception is thrown, the call chain is searched from the throw point up through its callers for a handler that can handle the particular type of exception that has been thrown. This allows errors detected in low-level library routines to be caught and handled in higher-level application code—in other words, the error is handled in the function that has the context necessary to correctly deal with the error.
Note, then, that an exception may or may not be caught within the context of the currently executing function. If it isn't, the functions that called the currently executing function must be checked for a handler that can catch the exception, moving backward through the chain of function calls (the call stack). If this backward search reaches the entry point and still finds no handler, the run-time library catches the exception and terminates the application after generating an error. The syntax (try-catch) and behavior of C++ exception handling are defined by the ANSI C++ specification.
The overhead for supporting C++ exceptions comes from the implementation of "call stack unwinding" and the housekeeping that must be done as it moves backward through the call stack. All local (stack) variables must be destroyed (destructors called) and the stack cleaned up as the search moves backward from function to caller. This means the compiler must keep a table of the stack variables associated with each instance of each function so the stack unwinding routine can call the appropriate destructors for those variables as they go out of scope during the unwinding. This implies the need for additional data storage (for the table), and also means that extra instructions to update the table must be executed each time a function is called, and each time a local variable of a C++ class or struct (meaning a destructor must be called) is declared or goes out of scope. Finally, the stack unwinding code itself must be linked into the final executable.
If a C++ exception is thrown in a module compiled without support for C++ exception handling enabled, the behavior is undefined. The result could be termination of your program. Therefore, Dr. GUI advises against using the STD C++ Libraries (including STL) without /GX. This is because the STD C++ Libraries use C++ exception handling as their primary error-handling mechanism (as dictated by the ANSI C++ specification). This is why /GX is now included in the compiler options for Visual C++ projects. MFC, ATL, and many other commercially available C++ class libraries also use C++ exceptions as their primary error-handling mechanism. On the other hand, if you're not using exceptions and you're absolutely sure you're not calling any library routines that might throw an exception, you may as well turn /GX off and make your program smaller and a little faster.
The good news is there are some techniques you can use to minimize the effects of supporting C++ exception handling. Consider the implication of being able to assume that a particular function (functX, for example), or any of the functions functX calls (or any that they call, or any that they call, or ...) will not throw a C++ exception. If you can make that assumption, it then follows that call-chain unwinding will never move backward through functX. That conclusion removes the need for the housekeeping overhead, thus both the additional instructions and the additional data storage can be removed for functX.
You can tell the compiler to assume a given function won't throw any exceptions by adding "throw()" to the end of the declaration, which is standard syntax, or through the Microsoft-only _declspec(nothrow) extended attribute. Dr. GUI prefers sticking with the standard whenever possible—especially when the standard way is easier to type.
// ANSI syntax
void _cdecl MyFunction() throw();
// -or-
void _declspec(nothrow) _cdecl MyFunction();
// Use MS-only extended attribute "nothrow"
If an extern "C" function or a _declspec(nothrow) function throws a C++ exception, even with /GX enabled, the behavior is undefined. (If you're lucky, your app will be terminated.) This means that a _declspec(nothrow) function should never call a standard C++ library function that might throw an exception.
Finally, let's consider whether we can assume that extern "C" functions don't throw C++ exceptions (remember that such an assumption is built into /GX). Extern "C" functions in a C++ program are most likely being linked in from an object module or library. These functions are often written in a different language, such as C or assembler. Because the type of exception we are handling is a feature specifically of the C++ language only, it seems a reasonable assumption to say that an extern "C" function will not throw an exception.
Of course, if an extern "C" function is compiled by the C++ compiler, it is perfectly legal for that function, or one of its called functions, to throw a C++ exception. For those who must throw C++ exceptions from extern "C" functions, replace /GX, which is the same as using /EHsc, with just /EHs. /EHs enables C++ exception handling without the assumption that extern "C" functions will never throw a C++ exception. /EHc enables the assumption that extern "C" functions do not throw exceptions.
I'm trying to retrieve values from an Oracle database using Visual Basic 6.0 with ActiveX Data Objects (ADO). I based my code around Knowledge Base article Q176086: "HOWTO: Retrieve Recordsets from Oracle Stored Procs Using ADO" (available from http://support.microsoft.com/search/). The Oracle side works fine, but when I try to run it from Visual Basic, I get an OLE-DB provider error. I'm using the MSDAORA OLE DB provider for Oracle.
In my attempt to solve this problem, I was scanning through Oracle Programming with Visual Basic by Nick Snowdon, published by Sybex. In chapter 17, page 618, Snowdon says that the method of packages returning a table is supported only by Oracle Objects. Has the code in Q176086 ever actually worked? I'm passing three parameters into the procedure and expecting a return parameter of 1. I'm confused!
Thank you,
Bill Crawley
Dr. GUI isn't perfect, but he knows that the sample code in Knowledge Base article Q176086 works fine if you follow all the steps, including creating the table, the package, and the package body successfully—meaning without warnings. Make sure the Visual Basic code in the article works with that particular table and package. If this code (after filling in the user name and password) doesn't work, most likely your Oracle client setup wasn't successful. To use Microsoft ODBC for Oracle, you must have Microsoft Windows® 95, Windows 98, or Microsoft Windows NT® and Oracle Client Software version 7.3 or later installed. Microsoft ODBC for Oracle supports only SQL*Net 2.3 or later.
Because Dr. GUI doesn't have all the symptoms (error number, description, and at what line of code), he can't be absolutely sure what to prescribe—but here are some suggestions. Make sure each input parameter is defined correctly and matches the field as defined in the table. Note that you cannot use Oracle functions that return PL/SQL arrays to return resultsets. You must use stored procedures.
Dr. GUI notes that despite what you've read, using Oracle Objects for OLE (OO4O) is not the only way to retrieve PL/SQL tables into recordsets. You can also do this by enhancing the ODBC driver, which is what Microsoft did with its ODBC driver. (The author of the book states this later on, saying, "Converting a PL/SQL table to a recordset is a little unusual and makes use of enhancements to the ODBC driver, but at the present time, this feature is not available through the native OLE-DB provider for Oracle.")
How do I extract a "Word" OLE object from a Microsoft Access 97 database record and save to a Word document file for a user "browsing" into a Web site to see the document?
I inherited an Access database to track test cases and steps to execute test cases. The "steps" were written in Word and saved in an Access record OLE field. This works quite well using Access forms to view and activate WORD to do the editing. But when I want to do online reports, the Word document is not accessible to most of the data access controls that I've found. What I'd like to do is use ADO, RDO, or even DAO to open the table, get records, and, in particular, get the Word object saved into a new file and redirect the page to point to the newly saved document.
Thank you,
Franc Woods
Nothing against dentists, but even the word "extract" gives Dr. GUI the shudders. Microsoft Access packages OLE objects in a wrapper before it embeds them in a table, which is why you can't assign the contents of an OLE object field directly to a Word object variable in ADO or Visual Basic. The OLE object storage definition in Microsoft Access is not documented, so there is no easy way to isolate a Word document from its binary header.
There are two kinds of OLE objects in Microsoft Access: linked and embedded. Embedded objects are accessible only from within the host database. Linked objects are more flexible. To share objects between Microsoft Access and other applications, the objects should be saved as files outside of the database, placed in a shared folder, and added back to the Access table as linked OLE objects. Any updates to the objects (in this case, Word documents) will be reflected in the linked files. You can see them from Word, Access, a Web browser, or a development tool like Visual Basic.
To save the embedded objects in your Access table as Word document files, double-click each embedded object in the table to activate it in place and select Save Copy As from the File menu. To add each Word document back to the Access table as a link, click Object on the Insert menu, and then click Create From File in the Insert Object dialog box.
It's possible to automate this, but it's more complicated than we have space for here. There will soon be a Knowledge Base article describing exactly how to do this—it'll be number Q234857. If you want to try it in the meantime, the basic steps are as follows:
Once the files are linked to your Microsoft Access database, rather than embedded, your viewing options are virtually unlimited. You can point a browser directly to the Word documents or, to create more robust reports, you can use the Navigate method of the WebBrowser control in Visual Basic to display them on a form.
The recommended way to access the path to each linked document in Access is to create an additional text field in the table and store the path to the linked OLE object in that field. However, Microsoft Knowledge Base article Q170531, "How to Retrieve the Path for Linked OLE Objects," describes how to do this programmatically.
Another option: there are third-party developers who offer add-ins to help with this issue. You may want to investigate their products before you roll your own solution.
I have been trying, unsuccessfully, to place an icon on a CPropertySheet that is running in wizard mode. I have attempted to use the members of the PROPERTYSHEETHEADER structure and have only been successful when the CPropertySheet is not in Wizard Mode. Without icons my wizards may lose their ability to cast their spells, please save them.
Leslie Brody
To tell you the truth, Dr. GUI was at first clueless—he couldn't reproduce this problem at all. He has tried putting an icon on the title bar of the property sheet and it all looked so simple. Little did he realize that others do not find it so simple if they have not installed Internet Explorer 5.0 on their machines as Dr.GUI has done. As you know, the PropertySheet function is implemented in the Comctl32.dll. For reasons unknown to Dr.GUI, in the pre-Internet Explorer 5.0 or pre-Windows 2000 versions of Comctl32.dll, PropertySheet in wizard mode does not have the WS_SYSMENU style set. The icon, even if set, isn't displayed if the window doesn't have that style set. The good doctor prescribes an upgrade: Internet Explorer 5.0 is MUCH better than any other browser. And Windows 2000, even though it's still in beta, is MUCH better than Windows 98 or Windows NT 4.0. But if you can't upgrade, Dr. GUI does have a good workaround for you.
Thankfully, MFC lets you hook into the creation of the property sheet so you can change the style of the property sheet before it's displayed. Call CWnd::ModifyStyle in your WM_CREATE message handler of your CPropertySheet to add the WS_SYSMENU style to PropertySheet. Once the correct style is set, the rest of the steps are same as those for showing the icon in a non-wizard property sheet.
Once again, Dr. GUI thanks his team of specialists: Ranjit Sawant, Frank Kim, Ranga Narasimhan (two questions—thanks double!), Richard Fricks, Rick Troemel, Hussein Abuthuraya, Lynn Shanklin, and Arun Kumar. Also, Dr. GUI wants to thank specialist coordinators Tom Moran and Laurie Moloney, who make it all make sense.