Progress with the DatabaseKit - working with data in the VCF

News and Announcements

Well, finally, after quite some time, I have some progress to report on the DatabaseKit implementation that I've been working on. Thanks to markd_mms for helping out with the initial design work, which we largely copied from Borland's excellent VCL DB design, and for coding up and initial skeleton. Based on this, I extended the initial work and built something that works very similar to how you work with DB access in Delphi.

What we now have is an initial cut at this, that allows for read/write access to a DB, allows you to forward iterate through a table, modify rows in a table, and add/delete rows to/from a table. You can iterate the known columns of a table. And you get notification of all sorts of DB events, such as activation changes, when changes to the DB happen, field changes, scroll changes, and so on.

The current implementation has built-in support for SQLite3, and partial support for ADO, which should most of the ODBC data providers for Microsoft windows.

Most importantly, the new design makes it extremely easy to use, with no knowledge of SQL required. As we move forward, new parts of the kit will allow for direct SQL execution. In addition to this, it also makes it very easy to integrate into controls, for example, here's a screenshot of the test database, in SQLite3:

Next we have a simple app that dumps content to stdout:

DataSet* dataSet = DatabaseToolkit::createDataSet( "SQLiteType" );

dataSet->setParam( "databasename", "test.db3" );
dataSet->setParam( "tablename", "Person" );

DataSource* dbSrc = new DataSource();
dbSrc->setEnabled( true );
dbSrc->setDataSet( dataSet );

dataSet->setActive(true);

while ( !dataSet->isEOF() ) {			
	Enumerator* fields = dataSet->getFields();
					
	while ( fields->hasMoreElements() ) {
		DataField* field = fields->nextElement();		
		System::println( "Field name: " + field->getName() + " value: " + field->getAsString() );
	}
	dataSet->next();	
}

This is one way to dump data - there are alternate ways to do this, for example you can access a field instance by name, instead of manually iterating through all of them.

Another example shows how you would hook this into a simple data aware label (again making use of the same DB that we see in the previous screenshot):



class DataLabel : public Label {
public:
	DataLabel(): Label(), dataLink_(NULL) {
		dataLink_ = new FieldDataLink();

		dataLink_->DataChange +=
			new GenericEventHandler(this,&DataLabel::onDataLinkDataChanged,"DataLabel::onDataLinkDataChanged");
	}

	virtual ~DataLabel() {
		dataLink_->free();
	}

	void setDataSource( DataSource* val ) {
		dataLink_->setDataSource( val );
	}

	void setFieldName( const String& name ) {
		dataLink_->setFieldName( name );
	}

	DataField* getField() {
		return dataLink_->getField();
	}

protected:
	FieldDataLink* dataLink_;

	void onDataLinkDataChanged( Event* e ) {
		setCaption( getField()->getAsString() );
	}
};
Note that the control itself is pretty trivial. The control is updated whenever a change occurs to the current record (such as navigating through the DB) via the onDataLinkDataChanged callback. Using the DB aware control looks like this:
dataSet = DatabaseToolkit::createDataSet( "SQLiteType" );

dataSet->setParam( "databasename", "test.db3" );
dataSet->setParam( "tablename", "Person" );

dbSrc = new DataSource();
dbSrc->setEnabled( true );
dbSrc->setDataSet( dataSet );

DataLabel* label1 = new DataLabel();
label1->setDataSource( dbSrc );
label1->setFieldName( "FirstName" );

DataLabel* label2 = new DataLabel();
label2->setDataSource( dbSrc );
label2->setFieldName( "LastName" );

dataSet->setActive( true );

The complete code for this example looks like this:

//dbtest2.cpp


#include "vcf/ApplicationKit/ApplicationKit.h"
#include "vcf/ApplicationKit/Label.h"
#include "vcf/ApplicationKit/CommandButton.h"

#include "DatabaseKit.h"
#include "DataLink.h"

using namespace VCF;




class DataLabel : public Label {
public:
	DataLabel(): Label(), dataLink_(NULL) {
		dataLink_ = new FieldDataLink();

		dataLink_->DataChange +=
			new GenericEventHandler(this,&DataLabel::onDataLinkDataChanged,"DataLabel::onDataLinkDataChanged");
	}

	virtual ~DataLabel() {
		dataLink_->free();
	}

	void setDataSource( DataSource* val ) {
		dataLink_->setDataSource( val );
	}

	void setFieldName( const String& name ) {
		dataLink_->setFieldName( name );
	}

	DataField* getField() {
		return dataLink_->getField();
	}

protected:
	FieldDataLink* dataLink_;

	void onDataLinkDataChanged( Event* e ) {
		setCaption( getField()->getAsString() );
	}
};





class dbtest2Window : public Window {
public:
	dbtest2Window() {
		setCaption( "dbtest2" );
		dataSet = DatabaseToolkit::createDataSet( "SQLiteType" );

		dataSet->setParam( "databasename", "test.db3" );
		dataSet->setParam( "tablename", "Person" );

		dbSrc = new DataSource();
		dbSrc->setEnabled( true );

		dbSrc->setDataSet( dataSet );


		DataLabel* label1 = new DataLabel();
		label1->setCaption( "Test data here..." );
		add( label1 );
		label1->setBounds( 10, 20, 100, 25 );

		label1->setDataSource( dbSrc );
		label1->setFieldName( "FirstName" );


		DataLabel* label2 = new DataLabel();
		label2->setCaption( "Test data here..." );
		add( label2 );
		label2->setBounds( 110, 20, 100, 25 );

		label2->setDataSource( dbSrc );
		label2->setFieldName( "LastName" );


		dataSet->setActive( true );

		CommandButton* first = new CommandButton();
		first->setCaption( "First" );
		first->ButtonClicked +=
			new GenericEventHandler(this,&dbtest2Window::onFirst,"dbtest2Window::onFirst");

		add( first );
		first->setBounds( 10, 120, 100, 35 );


		CommandButton* next = new CommandButton();
		next->setCaption( "Next" );
		next->ButtonClicked +=
			new GenericEventHandler(this,&dbtest2Window::onNext,"dbtest2Window::onNext");

		add( next );
		next->setBounds( 10, 70, 100, 35 );		

	}

	virtual ~dbtest2Window(){
		dbSrc->free();
		dataSet->free();
	};


	void onFirst( Event* ) {
		dataSet->first();
	}

	void onNext( Event* ) {
		dataSet->next();
	}

	DataSet* dataSet;
	DataSource* dbSrc;
		
};




class dbtest2Application : public Application {
public:

	dbtest2Application( int argc, char** argv ) : Application(argc, argv) {
		DatabaseKit::init( argc, argv );
	}

	virtual bool initRunningApplication(){
		bool result = Application::initRunningApplication();
		
		Window* mainWindow = new dbtest2Window();
		setMainWindow(mainWindow);
		mainWindow->setBounds( 100.0, 100.0, 500.0, 500.0 );
		mainWindow->show();
		
		return result;
	}

	virtual void terminateRunningApplication() {
		DatabaseKit::terminate();

		Application::terminateRunningApplication();
	}

};


int main(int argc, char *argv[])
{
	Application* app = new dbtest2Application( argc, argv );

	Application::main();
	
	return 0;
}



And the screenshots:



You can see that as the Next button is pressed, the DataSet's next() method is called which moves to the next record in the DB. Since the label's data link is hooked up to a data source, which is in turn hooked up to a DataSet, the data link is automatically notified of the record change, which in turn is handled by the control's event handler.

I hope that everyone is as excited about this as I am - I think this is potentially one of the cooler DB implementations out there! :)