Happy life with CDialog

by Alexey Parshin


The Introduction

Dialogs are a part of any modern application. No matter what is the application about, if it has the GUI interface, it would also have a Settings (or Preferences) dialog, maybe one or more Properties dialogs. There are also an error information dialogs. FLTK provides us a window class, but (unfortunately) there is no class to support a dialog. You have to make it yourself. The code to support a basic modal mode for the window does exist already. But the classic dialog grabs the program control till you exit it (by pressing Ok or Cancel buttons). The code to make the dialog window to do it is relatively simple. The problem is – if the application has ten different dialogs – it will have that code in ten different places. I don't know about you, but I don't like the code duplication in my programs. So, that's the reason to create a class that does the job.

The idea

What do we need from the dialog? First, it should require a minimal amount of code to use it. Second, it should be really simple to set and read data in the dialog's widgets. CDialog's code supports the window modal mode. But how to communicate with the widgets inside the dialog?

Every SPTK widget has an extra property – the fieldName(). When the widgets are added to CDialog, the fieldName() is defined for every widget. That allows to address the fields instead of widgets. CDialog has an index operator (operator [ ]) that allows to access widgets with defined field names using the widget index or the field name, like this:

CDialog dlg;

dlg.newPage(“Page 1”);

CInput fnameInput(“First Name:”);

CInput lnameInput(“Last Name:”);

fnameInput.fieldName(“first_name”);

lnameInput.fieldName(“last_name”);

dlg.end();

dlg[ 0 ] = “John”; // Access by index

dlg[ “last_name” ] = “Doe”; // Access by field name

dlg.showModal();

There are several things to consider, though:

The implementation

CDialog class is designed based on CWindow class (Fl_Double_Window descended) and CMultiTabs (IMHO, a better version of Fl_Tabs). It uses layout everywhere. Most of the time it's very convenient – you don't have to worry about locations of the widgets inside like tabs or buttons. CMultiTabs allows CDialog to have multiple pages with widgets. One trick is used, though. The tabs are shown only if there are several of them. If CDialog has only one page the tabs are hidden and CDialog simply shows the FL_THIN_DOWN_BOX group.

There two types of pages currently supported in CDialog. The regular page is CGroup, and the scroll page is CScroll. The pages are created with newPage() and newScroll() methods. It's not necessary to end() the prior page if you call one of these methods. Just make sure you didn't forget to end() the dialog itself.

After the dialog is constructed, and is shown, or the index operator (operator [ ]) used, or the database access or registry (in this dialog) used, the dialog creates the internal list of widgets. CDialog uses this list to perform database and registry (CRegistry, of course) operations with the fields – I call it the default processing. You may want to exclude some of the controls from the default processing (in the derived dialog) if you add such controls (as CControl *) to m_specialProcessingControls list (CList object).

It's good to know that scan operation is recursive. The control may belong to the group that is inside another group that is inside CMultiTabs. Yet, it will be found anyway.

A lot of applications may stay happy forever with just using the direct field access explained before. But there is also a bunch of applications that would benefit from using the database access. CDialog provides simple yet useful support to edit a database data. It allows to edit data from the particular database table, if this data can be uniquly identified with the integer key. Usually, the table's primary key is the best candidate for this role.

In order to use CDialog with the database connection, it takes, of course, to establish the database connection itself. SPTK supports only ODBC database connections, so it will look like this (the actual parameters inside the connect string for you will be different, of course):

CDatabase db(“DSN=test;UID=user;PWD=password”);

db.open();

Now the database is open and we may work with the data. Let's define the data connection for the dialog:

CDialog dlg(“My dialog”,300,200);

dlg.database(&db);

dlg.table(“employees”);

dlg.keyField(“emp_id”);

dlg.newPage(“Page 1”);

CInput fnameInput(“First Name:”);

fnameInput.fieldName(“emp_fname”);

CInput lnameInput(“Last Name:”);

lnameInput.fieldName(“emp_lname”);

dlg.end();

The dialog (above) defines the table 'employees' from the database db. The table contains at least fields: emp_id (integer, unique), emp_fname, emp_lname. So far we just created the dialog. The data connection is defined, but the data isn't loaded yet. The following operation actually loads the data into the dialog's fields:

dlg.keyValue(1);

It makes the dialog to build the necessary SQL queries, and try to search for the record with the key '1' (integer):

SELECT emp_id, emp_fname, emp_lname

FROM employees

WHERE emp_id = 1

If the record is found, the values will be loaded into the fields, and CDialog switches to edit mode. If there is no record with emp_id = 1, the fields will be empty and CDialog works in insert mode. What's the difference? Edit mode means that pressing Ok button will execute an UPDATE SQL statement, as an opposite to insert mode that executes an INSERT SQL statement. If the Cancel button is pressed then, of course, no operation is done on the database. This is pretty much it.

If you need a more complex scenario, like updating several tables from one dialog, then you have to subclass CDialog, define the list of special fields (that belong to other database tables) and override the load() and save() methods. Just don't forget to call the original methods from inside your override methods:

CMyDialog::load() {

CDialog::load();

// your own code is here

}

I leave the details of implementation to you. If it looks too complicated just check the source code for CDialog – it isn't, really.

Using the registry

SPTK registry is just an old – style INI file. CRegistry class allows to work with it the simple way. Please, review CRegistry class for details, if you need them. Let's talk how we can use CRegistry with CDialog.

First, why do we need CRegistry for CDialog? Many applications use so-called 'Preferences' dialog. Such dialog would contain some user-defined settings for this application. The combination of CDialog and CRegistry makes a ready-to use 'Preferences' dialog. It stores the user settings in registry file and restores it when application starts.

CDialog has two methods: loadFromRegistry() and saveToRegistry() to load and update the registry information. Both methods use the same two parameters – the Registry object pointer and the name of the section inside registry. The 'Preferences' dialog may be a subclass of CDialog. It should be created upon the application start, then it should load the data from registry. That's it! Now the application just uses the index operator of the 'Preferences' dialog to get the stored values:

int window_w = myPreferenceDlg[“window_w”];

int window_h = myPreferenceDlg[“window_h”];

window.size(window_w,window_h);

I hope, the information in this document helps you to use CDialog.