One thing I've missed from VisualC++ was a way to develop fast a database application. There are some data bound ActiveX controls that come with VisualC++ 5.0, but a data source for DAO is missing. The RDO is buggy (or at least was in VC 5.0 SP1 - with SP3 it seems more stable, but I didn't tested it very hard :-)). And RDO works through ODBC, which makes it a very bad choice for mdb databases. After I searched the Internet for an example how to make a data source, I found very little information and nowhere the source code for such a control. So I started with a base code from Data Binding SDK, and I made a control that works (almost :-)).
Notes about the code:
Since there is a lot of code involved, I emphasize here just some basics. I recommend to get the Data Binding SDK for more information. First, the IDE will recognize an ActiveX control as a data source if it will support the VBDSC interface. That interface allows creating a cursor interface that can be used by any control that requests it. There are two kind of data-binding: simple and complex. For simple bound controls the container will handle the data binding. The simple bound ActiveX control will talk with the container. A complex bound control will obtain a cursor interface and will talk with the data source control rather than the container.
There are two kinds of cursors: that handles the actual data (recordsets), and that handles the information about those recordsets (meta data). The CColCursor class implements the meta data cursor, and the CRowCursor class implements the data cursor. There are several kinds of interfaces that can be requested, each one extending the previous: ICursor - the basic one, that allows to obtain the meta data cursor, to bound columns and to get rows in a forward only fashion; the next one is ICursorMove, that allows moving through cursor in any direction; the next one is ICursorScroll that allows moving through cursor using approximate positions (it is needed by controls that have scroll bars, for example). There are another two special interfaces: ICursorFind, that allows finding a record using field values, and ICursorUpdateARow, that allows changing field values, adding a row and deleting one.
The CMyDaoRecordset is an extension of the CDaoRecordset class, with some changes in GetBookmark and SetBookmark (they use absolute position for snapshot recordsets), and there is a new method called Clone, that makes a clone from the current recordset. The Reclone method makes the current recordset being the clone of the given recordset.
The CRowCursor implements also the IConnectionPoint interface. The VisualC++ framework (and with high probability Visual Basic, too) will try to connect to the control instead of the rowset, so I changed both GetConnectionHook (to pass the connections to the recordset), and GetInterfaceHook (to pass the queries about cursor interfaces to the rowset). The source control uses INotifyDBEvents interface to notify the bound controls about the changes in cursor's state (this interface is implemented by the container and/or the data bound control - not by the source). The data source supports IConnectionPointContainer and IConntectionPoint interfaces, that allows data bound controls to be connected to the data source (so they will be notified about changes that occur).
Now, how to use it:
Use regsvr32 /v "path"\dscdao.ocx to register the control. You need to have the jet engine installed on the computer you will use this control, the runtime library and mfc dlls. After that you will be able to use this control like any other ActiveX (it should work with Visual Basic, too, but I tested it only with VisualC++). Please drop me an e-mail if somebody tests it with Visual Basic.
Known bugs (I will fix them when I will have time):
Since I don't have very much free time, the code is written very fast, and can be full of bugs :-(. And remember, it's only the first version (1.0).
If compiled with debug info, the container program will crash at the exit. It seems that the mfc framework tries to call an interface function after the rowset object is released. If you edit a field on the last record in DBGrid, and after that go on he new record, the other bound columns will not be updated (it seems that the current record will remain the last one - the dbgrid uses the clone to add the new record, and for some reason the other controls are not notified by the change when the record is updated). DBGrid works with two rowsets (at least), the original one for moving/editing (usually), and a cloned one for drawing, so it's pretty hard to spot the reason of that error. Another bug (and the worst one), is that the DBGrid will behave strange if an index is used, or if the table is edited by more than one user.
It's possible that the control will not work for some data types. It is sure that it will not work for BLOB fields. In order to fully support blob fields, it needs to implement IEntryID interface. Instead of field value, there will be an entry id, that will allow to ask IEntryID interface for IStream interface for that entry id. The IStream interface is used to get the BLOB field. I also not tested the control for VARIANTs, but it should work. I recently implemented the ICursorFind interface, and I didn't test it yet, so probably it will not work.
I want to hear if you found any bugs, or make some improvements to the code.
Allow to draw the control vertically. Suport for BLOB fields. Better support for different kind of data. Extending the meta data cursor to support Clone method and bookmarks. Possible allowing "master-slave" (relations) between two data source controls...
And well, a copyright notice:
You can use the code free of charge, you can modify it, but in any case the author (Adrian Roman) is not responsible of any kind of damage or loss of data or loss of profit, incidental or consequential, occurred using this code. You cannot claim that the code is written by yourself, even the code is modified. If you use this control, you have to make a notice (in About box and/or startup splash screen and/or help file) that the program contains code developed by Adrian Roman, e-mail: firstname.lastname@example.org.
Changes to version 22.214.171.124:
I used BstrToVector and VectorFromBstr to convert the bookmarks. I did something wrong, and somtime the conversion failed. On small recordsets it worked, but on large ones the result was an empty recordset. I changed the code working directly with the safarray, and now it works well.
I allso added a check box that allows to set the type of locking.
In the previous version there was a memory leak (when a clone was made, a recordset was alloced twice), now it's fixed.
Also in the previous version, the FindByValues interface was not functionl. Now it works, but only for dynaset or snapshot recordset. For table type it returns E_NOTIMPL.