Programming in Qt4 and C++

From QtCentreWiki

Jump to:navigation, search

Introduction

The program described in this document is the first C++ program I have ever written. As such, all corrections, improvements, additional explanations are encouraged. The purpose of this document is to explain, as I understand it, how to install the Trolltech cross-platform Qt 4.x framework and all necessary C++ tools onto a workstation, and then how to use those tools to create Graphical User Interfaces to department data residing on Oracle and other databases. Qt applications run natively — indistinguishable from native applications — and can compile from a single source code on all major platforms: Linux, Max OS X or Windows, with few or no changes. Oracle or PostgreSQL, combined with Qt4 and C++, creates a truly platform independent development and usage environment.

The Qt4 framework is released under two licenses: Open Source and commercial:

The Open Source version is released under the General Public License and Windows, Linux or Mac versions can be obtained for free from Trolltech's FTP server. The Open Source License is explained on this page.

A commercial copy of Qt4 is obtained via Trolltech's sales department, and requires the execution of NDA's and payment of a license fee. The fee include support on an annual basis. After payment is received you will be given the URL to a download site from which you can download the platform version you requested. The commercial license is explained on this page.

What are the major differences between the two licenses? You need a commercial license if your application requires a connection to an Oracle or other major commercial database, but not PostgreSQL, or you want to distribute a binary of an application but not it's source code. The GPL version lacks the files and libraries necessary to create connections to Oracle, but does include capabilities to connect to PostgreSQL, MySQL or SQLite.

Prior to the release of Qt4 the GPL version of Trolltech's toolkit was not available under Windows. Now that a GPL version of Qt4 is available under Windows a search was on to find a free C++ IDE and compiler. The best free C++ IDE for Windows is Dev-C++ 5.0, from Bloodshed. The best free C++ compiler for Windows is MinGW. Using Dev-C++, MinGW and the GPL version of Qt4 creates a development environment with a TCO of 0$. When Qt4's libraries are configured into Dev-C++ it offers a limited code completion functionality.

All Linux distributions include the necessary editors, compiler and debugger to create native applications using Qt4, so non-commercial development under Linux has a TCO of zero dollars, since most Linux distros are free. Indeed, all of them already include GPL Qt3 tools as part of their package and some are now including Qt4 in their repositories. This is not surprising since the major Linux desktop, KDE, was built using Qt3, and the next release of KDE, now under heavy development, is being built using Qt4. However, Qt4 applications can be built under the minor Linux desktop, GNOME, with no difficulty because Qt4 contains all the files necessary to program in either environment.

The PostgreSQL database makes an excellent stand-in for Oracle, so it makes an excellent foil against which to test your putative Oracle applications. Install the latest release of PostgreSQL 8.x for Windows from here. If you want an Oracle connection then install the Oracle.

Installing Qt4 on Windows

To get Qt4 running it must be unpacked, configured and then compiled.

[Edit 12/07/2008 : NOTE! Trolltech has released a new GUI RAD IDE tool for Qt4 called Qt-Creator. There are free versions for Linux and Windows. Qt-Creator rivals Microsoft Visual Studio in its features and has better performance. http://trolltech.com/developer/qt-creator

For the Windows platform the package includes a complete Windows developer environment with MinGW and Qt 4.4.3 built with MinGW, and NONE of the steps listed in this section for Windows is necessary.

For Linux a pre-installed and compiled version of Qt4.x is required. It is best to compile it in the combined debug and released mode.

Since Qt-Creator is deep into the beta stage the most recent snapshots, with tons of fixes and modifications, can be downloaded from: ftp://ftp.trolltech.no/qtcreator/snapshots --GreyGeek]


Unpacking GPLed Qt4

To unpack Qt4 onto a Windows directory:

  1. Run MinGW-4.1.0.exe and select everything. MinGW supplies the gcc compiler and gdb debugger, plus some other utilities.
  2. Run qt-win-opensource-4.1.0-mingw.exe. This application has an option to install MinGW from the web but, unfortunately, the URL to the MinGW application is broken, ergo the need for step 1.

Qt4 comes with a file called INSTALL, which gives a brief description of the two steps after unpacking: configure and make (or nmake).

After Qt4 is unpacked, you will have several menu options added to the Windows menu tree. Two are console connections. One is titled Qt4 (Build Debug Libraries) and the other is Qt4 Command Prompt. The first will build all of the examples and utilities. The second opens a DOS command prompt from which you will issue the commands to create project files and compile.

During unpacking there is also an option to add environmental variables to the register, which I did not check, but it won't hurt if you do. There is a DOS box menu option created under the Qt4 menu, called Qt4 Command Prompt, that automatically runs the batch file, E:\Qt\4.1.0\bin\qtvars.bat, and sets up the environmental variables. It is from that Qt4 Command Prompt DOS box that I compile my apps during development. If you issue it as qtvars compile_debug it will recompile Qt4 and create an executable with debug info compiled in. I chose not to do this. Compiling under DOS is several times faster than using the MSVC IDE build command. Compiling under a Linux console box is several times faster than compiling the same app under a DOS box.

Here are the contents of qtvars.bat:

@rem
@rem This file is generated
@rem
@echo Setting up a Qt environment...
@echo -- QTDIR set to E:\Qt\4.1.0
@echo -- Added E:\Qt\4.1.0\bin to PATH
@echo -- QMAKESPEC set to win32-msvc.net
@set QTDIR=E:\Qt\4.1.0
@set PATH=E:\Qt\4.1.0\bin;%PATH%
@set QMAKESPEC=win32-msvc.net
@if not "%1"=="setup" goto SETUP_DONE
@cd %QTDIR%\examples
@qmake -r -tp vc
@:SETUP_DONE
@if not "%1"=="vsvars" goto END
@call "E:\VS2003\\Common7\Tools\vsvars32.bat"
@:END
@cd %QTDIR%

From it you can capture the following commands (with adjustments made to the paths according to your install locations):

set QTDIR=C:\Qt\4.1.0
set PATH=C:\Qt\4.1.0\bin
set PATH=%PATH%;C:\MinGW\bin
set PATH=%PATH%;%SystemRoot%\System32
set QMAKESPEC=win32-g++

and put them into your own customized batch file. Or, you can edit your environmental variables in the properties section of MyComputer. Every setting except QMAKESPEC is self explanatory. The QMAKESPEC setting tells your Qt4 installation which compiler you are using. These compiler specifications are contained in the E:\Qt\4.1.0\mkspecs and cover all combinations of platforms and compilers that Qt4 runs under, which currently stands at over forty. Each is a subdirectory contains a tailored qmake.conf file, with supporting files, and allows the Qt4 configure command to create the appropriate qmake.exe file for your platform and compiler. The make or nmake command, used to actually compile your application, is supplied by the compiler you chose to use.

After the unpacking is complete, and you let the check box option remain checked on the final screen before you finish, a demo of Qt4 will run, complete with source code if you care to inspect it. GPL Qt4 for Windows comes only with SQLite pre-configured for use, so some database examples many not work except with a SQLite memory database.

Unpacking Commercial Qt4 on Windows

The commercial version requires a separate license for each developer, and also require a signed NDA be faxed to Trolltech. Which one you install depends on which commercial compiler you are installing. Those using Visual Studio 2003 should use the executable ending in -vs2003.exe. Those using Visual C++ 6.0 should use -vc60.exe. The remaining version is in a .zip file and can be used with MinGW. To install the chosen .exe merely double click on it. For the configure commands given below the following line will have to be added if your compiler is the MSVC, depending of course on where you installed MSVC:

-I"C:\Program Files\Microsoft Visual Studio 8\VC\include" \
-I"C:\Program Files\Microsoft Visual Studio 8\VC\PlatformSDK\Include" \
-I"E:\Qt\4.1.0\include\QtSql" \
"C:\Program Files\Microsoft Visual Studio 8\VC\include" \
"C:\Program Files\Microsoft Visual Studio 8\VC\PlatformSDK\lib"

Configuring and compiling Qt4

There are two steps. configure, followed by make or nmake. The command configure is actually configure.exe and is supplied by Qt4. It is located in the main Qt4 directory. The tool called make, or nmake, is supplied by your C++ compiler and uses your C++ source code to create your system binary executable.

Inside the Qt4 directory issue the following command on one line, even though I have separated the components of the command into multiple lines for clarity:

configure -plugin-sql-psql -plugin-sql-oci \
   -I C:\Progra~1\PostgreSQL\8.0\include   \
   -I E:\instantclient10_1                 \
   -L C:\Progra~1\PostgreSQL\8.0\lib       \
   -L E:\instantclient10_1

Of course, the exact paths depend on where you installed PostgreSQL and Oracle. -qt-sql-psql and -qt-sql-oci would compile PostgreSQL and OCI into the application, requiring both of their DLLs to deploy, even if you only connect to one of them. You can use Microsoft's dependency checker tool to determine which DLLs should be included with your executable.

After the configure command has completed then issue the nmake command, with no arguments. On Linux you will have to issue the make command twice. As the user you compile Qt4 with the naked compiler make command. Then you install Qt4 using the install parameter:

su -c "make install"

which prompts you for your root password, and installs the compiled files as root. When it is done it drops out of root automatically. Qt4 is made of C++ source code and we are doing an actual C++ compile here. Compiling Qt4 can take up to 3 or 4 hours, or more, depending on how much memory you have and how fast your CPU is. My Dell GX260 and my Gateway M675PRR laptop both took 2.5 hours to compile Qt 4.1.0.

After the Qt4 compile has completed we can issue the following three commands, each on one line, modified for you installation path, to install the PostgreSQL plugin:

cd C:\Qt\4.1.0\src\plugins\sqldrivers\psql
qmake -o Makefile "INCLUDEPATH+=C:\Progra~1\PostgreSQL\8.0\include" \
                  "LIBS+=-LC:\Progra~1\PostgreSQL\8.0\lib" psql.pro
make

To install the Oracle InstantClient10 plugin do:

set INCLUDE=%INCLUDE%;E:\instantclient10_1;E:\instantclient10_1\sdk\include;
set LIB=%LIB%;E:\instantclient10_1\sdk\lib;E:\instantclient10_1\sdk\lib\msvc;
cd %QTDIR%\src\plugins\sqldrivers\oci
qmake -o Makefile oci.pro
nmake # (for VS or VC, or make for MinGW)

The Qt4 compile process also creates several executables: the Designer, Assistant, QtDemo, moc, qmake, and several other utilities. They reside in the Qt4 bin directory, which was put in the PATH by the unpacking process, or by you, manually.

The Designer is the Qt4 GUI utility that is used to create your application user interfaces. The files it creates ends with the extension *.ui, which is an XML file describing the GUI interface. An application may designed with one or more *.ui files. Applications without a GUI file are considered console or 'batch' applications, with inputs and outputs usually relying on the C++ API iostream classes cout and cin, and the std namespace.

The Assistant is a browser to the Qt API, how-to's, examples and other information. Using F1 from the designer brings it up.

The moc, or Meta-Object-Compiler, is usually called in the background by make and uses the *.ui file to create a ui_classname.h header file, which is C++ source code. Any file which ends in *.h is by definition a C++ header file. qmake is used to create a project file, *.pro, from the C++ source and header files present in the directory and subdirectories of the application being made.

Here is what my environment looks like after the install. First, the output as it appears under the Qt4 Command Prompt:

Setting up a Qt environment...
-- QTDIR set to E:\Qt\4.1.0
-- Added E:\Qt\4.1.0\bin to PATH
-- QMAKESPEC set to win32-msvc.net
Setting environment for using Microsoft Visual Studio .NET 2003 tools.
(If you have another version of Visual Studio or Visual C++ installed and wish
to use its tools from the command line, run vcvars32.bat for that version.)

Then, the results of the DOS SET command, with ellipses standing in for irrelevant parts:

E:\Qt\4.1.0> set
...
INCLUDE=E:\VS2003\VC7\ATLMFC\INCLUDE;...;E:\instantclient10_1\sdk\include\
...
LIB=E:\VS2003\VC7\ATLMFC\LIB;...;E:\instantclient10_1\sdk\lib\msvc\
...
PATH=...;E:\Qt\4.1.0\bin;...;E:\instantclient10_1;...;
...
QMAKESPEC=win32-msvc.net
QTDIR=E:\Qt\4.1.0
...

Here is how my current Qt4 install is configured. Using the Qt4 DOS box I entered configure -help. The configure command responds with a syntax usage summary and then my current settings. I highlighted in red some significant settings:

E:\Qt\4.1.0>configure -help
Usage: configure [-prefix dir] [-bindir <dir>] [-libdir <dir>]
       [-docdir <dir>] [-headerdir <dir>] [-plugindir <dir>]
       [-datadir <dir>] [-translationdir <dir>]
       [-examplesdir <dir>] [-demosdir <dir>][-buildkey <key>]
       [-release] [-debug] [-debug-and-release] [-shared] [-static]
       [-no-fast] [-fast] [-no-exception] [-exception]
       [-no-accessibility] [-accessibility] [-no-rtti] [-rtti]
       [-no-stl] [-stl] [-no-sql-<driver>] [-qt-sql-<driver>]
       [-plugin-sql-<driver>] [-arch <arch>] [-platform <spec>]
       [-qconfig <local>] [-D <define>] [-I <includepath>]
       [-L <librarypath>] [-help] [-no-dsp] [-dsp] [-no-vcproj]
       [-vcproj] [-no-qmake] [-qmake] [-dont-process] [-process]
       [-no-style-<style>] [-qt-style-<style>] [-redo]
       [-saveconfig <config>] [-loadconfig <config>] [-no-zlib]
       [-qt-zlib] [-system-zlib] [-no-gif] [-qt-gif] [-no-libpng]
       [-qt-libpng] [-system-libpng] [-no-libjpeg] [-qt-libjpeg]
       [-system-libjpeg]

Installation options:

 These are optional, but you may specify install directories.

      -prefix dir ........ This will install everything relative to dir
                           (default $QT_INSTALL_PREFIX)

 You may use these to separate different parts of the install:

      -bindir <dir> ...... Executables will be installed to dir
                                 (default PREFIX/bin)
      -libdir <dir> ...... Libraries will be installed to dir
                                 (default PREFIX/lib)
      -docdir <dir> ...... Documentation will be installed to dir
                                 (default PREFIX/doc)
      -headerdir <dir> ... Headers will be installed to dir
                                 (default PREFIX/include)
      -plugindir <dir> ... Plugins will be installed to dir
                                 (default PREFIX/plugins)
      -datadir <dir> ..... Data used by Qt programs will be installed to dir
                                 (default PREFIX)
      -translationdir <dir> Translations of Qt programs will be installed to dir
                                  (default PREFIX/translations)
      -examplesdir <dir> . Examples will be installed to dir
                                 (default PREFIX/examples)
      -demosdir <dir> .... Demos will be installed to dir
                                 (default PREFIX/demos)
 You may use these options to turn on strict plugin loading:

      -buildkey <key> .... Build the Qt library and plugins using the specified
                                 <key>. When the library loads plugins, it will only
                                 load those that have a matching <key>.

Configure options:

 The defaults (*) are usually acceptable. If marked with a plus (+) a test for
 that feature has not been done yet, but will be evaluated later, the plus
 simply denotes the default value.. Here is a short explanation of each option:

      -release ........... Compile and link Qt with debugging turned off.
      -debug ............. Compile and link Qt with debugging turned on.
   *  -debug-and-release . Compile and link two Qt libraries, with and without
                           debugging turned on.
   *  -shared ............ Create and use shared Qt libraries. (Dynamic libraries)
      -static ............ Create and use static Qt libraries. (Libraries compiled in)
   *  -no-fast ........... Configure Qt normally by generating Makefiles for all
                           project files.
      -fast .............. Configure Qt quickly by generating Makefiles only for
                           library and subdirectory targets. All other Makefiles
                           are created as wrappers which will in turn run qmake
      -no-exception ...... Disable exceptions on platforms that support it.
   *  -exception ......... Enable exceptions on platforms that support it.
      -no-accessibility .. Do not compile Windows Active Accessibilit support.
   *  -accessibility ..... Compile Windows Active Accessibilit support.
      -no-stl ............ Do not compile STL support.
   *  -stl ............... Compile STL support. (Standard Template Library)
      -no-sql-<driver> ... Disable SQL <driver> entirely, by default none are
                           turned on.
      -qt-sql-<driver> ... Enable a SQL <driver> in the Qt Library.
      -plugin-sql-<driver> Enable SQL <driver> as a plugin to be linked to at run
                                 time.
                                 Available values for <driver>:
                                   mysql
                                   psql
   +                               oci
   +                               odbc
                                   tds
                                   db2
   +                               sqlite
                                   sqlite2
                                   ibase
                                 (drivers marked with a '+' have been detected as
                                 available on this system) (But other drivers can be added!)

      -platform <spec> ... The operating system and compiler you are building on.
                                 (default %QMAKESPEC%)

                                 See the README file for a list of supported operating
                                 systems and compilers.

      -D <define> ........ Add an explicit define to the preprocessor.
      -I <includepath> ... Add an explicit include path.
      -L <librarypath> ... Add an explicit library path.

      -help, -h, -? ...... Display this information.

 Third Party Libraries:

      -no-zlib ........... Do not compile in ZLIB support. Implies -no-libpng.
      -qt-zlib ........... Use the zlib bundled with Qt.
   +  -system-zlib ....... Use zlib from the operating system.
                           See http://www.gzip.org/zlib

   *  -no-gif ............ Do not compile the plugin for GIF reading support.
      -qt-gif ............ Compile the plugin for GIF reading support.
                           See also src/plugins/imageformats/gif/qgifhandler.h

      -no-libpng ......... Do not compile in PNG support.
      -qt-libpng ......... Use the libpng bundled with Qt.
   +  -system-libpng ..... Use libpng from the operating system.
                           See http://www.libpng.org/pub/png

      -no-libjpeg ........ Do not compile the plugin for JPEG support.
      -qt-libjpeg ........ Use the libjpeg bundled with Qt.
   +  -system-libjpeg .... Use libjpeg from the operating system.
                           See http://www.ijg.org

 Qt/Windows only:

      -no-dsp ............ Do not generate VC++ .dsp files.
   *  -dsp ............... Generate VC++ .dsp files, only if spec "win32-msvc".

      -no-vcproj ......... Do not generate VC++ .vcproj files.
   *  -vcproj ............ Generate VC++ .vcproj files, only if platform
                           "win32-msvc.net".

      -no-qmake .......... Do not compile qmake.
   *  -qmake ............. Compile qmake.

      -dont-process ...... Do not generate Makefiles/Project files.
   *  -process ........... Generate Makefiles/Project files.

      -no-rtti ........... Do not compile runtime type information.
   *  -rtti .............. Compile runtime type information.

      -arch <arch> ....... Specify an architecture.
                           Available values for <arch>:
   *                         windows
                             boundschecker

      -no-style-<style> .. Disable <style> entirely.
      -qt-style-<style> .. Enable <style> in the Qt Library.
                           Available styles:
   *                         windows
   +                         windowsxp
   *                         plastique
   *                         motif
   *                         cde

      -qconfig <local> ... Use src/tools/qconfig-local.h rather than the default.
                           Possible values for local:
                             minimal
                             small
                             medium
                             large
                             full

      -loadconfig <config> Run configure with the parameters from file configure_
                           <config>.cache.
      -saveconfig <config> Run configure and save the parameters in file
                           configure_<config>.cache.
      -redo .............. Run configure with the same parameters as last time.

The listing above will change as you add or remove arguments from the configure command when or if you reconfigure Qt4.

Using Qt4 to Write Applications

In the winter of 1992, IIRC, I wanted to switch from console application development using DOS tools to GUI development using Windows 3.0 GUI RAD tools. I was looking at Turbo C++ for Windows 3.0 and Visual Basic. My first simple Hello World program using Turbo C++ was almost 1,500 lines!! The VB Hello World was less than a dozen. I went with VB as my first GUI RAD tool. Times have changed. Here is Hello World in Qt4:

// main.cpp
#include <QApplication>
#include <QWidget>
#include <QLabel>
 
int main( int argc, char **argv )
{
   QApplication app( argc, argv );

   QWidget window;
   QLabel* message = new QLabel( "Hello, World!", &window );
   window.show();

   return app.exec();
}

This version of Hello World is totally complete in only one file. Most real world C++ GUI programs are not that simple, but they are not the monsters that C++ required in the past. One reason is the cross platform Qt 4.1.0 API.

In this document I will be using my first Qt4 application, Homestead, to illustrate application development using Qt4. The Homestead application was converted from a DOS based application to a GUI application in 1998. It is composed of a single window with four tabs. It has about 30 or so controls on each tab. It accesses its data via a Visual FoxPro 6 DBC which contains several DBF tables. Access to the application is controlled by the Novell login scripts. This test application is written against an Oracle database and access is controlled by a login screen and requires an Oracle client be installed on the developer's (user's) workstation.

I used the Qt Designer to create Homestead.ui, wholeName.ui, and dlgLogin.ui. I used an editor which highlighted C++ syntax to create homestead.h, homestead.cpp, main.cpp, wholenamedlg.h, wholenamedlg.cpp and dlgLogin.h. I compiled within the Qt4 Command Prompt DOS box because it is faster. Using those files a project file must be created, in order to create Makefile which allow compiling.

Making a Qt4 Project file

The hap2006.pro project file was created in two steps. First, using the Qt4 Command Prompt DOS box, cd to the Homestead directory, E:\hap2006, and issue qmake -project. Then, issue qmake hap2006.pro, or just qmake, since hap2006.pro is the only project file in the directory. The qmake command reads the project file, hap2006.pro, and creates at least one file called Makefile. Depending on your configure settings two other files can be created: Makefile.Release and Makefile.Debug. The C++ compiler utility called make reads the appropriate Makefile and generates a system executable. Reading Makefile.Release, make creates a stripped version of the executable and reading Makefile.Debug, make creates a debug version of the executable which contains debug information, hence that executable can be several times larger than the release version. Now that hap2006.pro is made the nmake or make command can be issued to compile the project and create hap2006.exe. In Linux everything is considered a file and extensions are not used to identify file types, so the binary executable is simply called hap2006 and is marked as executable by the compiler. Also, in Linux batch files are called scripts, and scripts should be marked executable if they contain only shell commands, and the first line of the script contains #!/bin/sh.

If a file is added or removed from the project during the course of development then hap2006.pro and all the Makefile.*'s should be deleted, and qmake used again to recreate the project file and Makefiles.*s. A short cut would be to add the new files names manually to hap2006.pro and reissue qmake. During the process of compiling, the moc creates ui_*.h files. Sometimes these ui_*.h files have to be manually deleted before uic works correctly and generates new ones. I'll explain later how to make these files and how these files are part of the development process.

Here is what is in the hap2006.pro file created by qmake:

######################################################################
# Automatically generated by qmake (2.00a) Wed Aug 24 14:27:47 2005
######################################################################
TEMPLATE = app
DEPENDPATH += .
INCLUDEPATH += .
CONFIG += qt debug # <== a 'Qt' app compiled with Makefile.Debug
QT += sql          # <== without this "#include <QSqlWhatever>" includes fail
win32:LIBS += E:\Qt\4.1.0\plugins\sqldrivers\qsqloci.lib
# Input
HEADERS += Homestead.h dlglogin.h wholename.h
FORMS += Homestead.ui dlglogin.ui wholename.ui
SOURCES += main.cpp Homestead.cpp wholename.cpp

The lines in red were added manually. When qmake hap2006.pro is re-run Makefile.Debug and Makefile.Release are re-made. They are used to tell the make command how to create the executable and where to install it. They rarely need to be manually modified, but there are some old hands at C++ programming who create their own project and make files manually. Such extreme measures are not easy, but are not needed with Qt4. Once a .pro file is created it can be imported into MSVC++ using Qt's import option, if you have the qt-vsintegration executable installed.. That is the best way to configure the MSVC++ compiler and linker. Otherwise, LNK2001 errors abound unless you are an expert at such matters. I am not.

Deploying Qt4 Applications

Finally, to deploy Homestead, I copy into the target directory, if not on a client's workstation then usually on a server somewhere, the following files:

  • hap2006.exe
  • QTCORE4.DLL
  • QTGUI4.DLL
  • QTSQL4.DLL
  • QSQLOCI.DLL
  • MSVCR71.DLL
  • MSVCP71.DLL
  • LIBPQ.DLL (if PostgreSQL connectivity is required)

There are 26 Qt4 DLLs and several compiler DLLs. Exactly which files your application needs to deploy can be determined by running a dependency checker against it. Microsoft offers a free program called depends. If I had configured Qt4 with the static option then I would need to deploy only my application executable, which could be more than 5 MB in size. With my dynamic configuration I would be deploying seven files with a total size of 5,772 KB, or about 6 MB. So, as far as deployment is concerned, the difference is one of choice. If the 26 Qt4 DLLs were placed on a central server included in the path of every user then the only file that would have to be deployed would be the 285 KB dynamic executable, a real advantage.

Designing and Building a Qt4 Application: Homestead

Previously, I have built tests of Homestead using the following tools:

  1. VFP6.0 against DBF tables and against Oracle,
  2. Java and JDeveloper with Beans against Oracle and PostgreSQL,
  3. Python with Boa_Constructor against Oracle and PostgreSQL using the same source code,
  4. Java with Eclipse against PostgreSQL,
  5. RealBasic against PostgreSQL (at home),
  6. KDevelop against PostgreSQL (at home),
  7. Netscape with Oracle's HTP.P command against Oracle, and
  8. Qt4 against Oracle and PostgreSQL using the same source code.

I will not mention the few lessor known Linux tools that I experimented with, mostly on my own time. When including the GUI files only, Qt4 and Python/Boa_Constructor employed strictly ASCII source code files which were cross platform with the same source code, but Boa_Constructor, even though it was the easiest tool of all to use, has some serious warts, including lack of serious support and upgrade disjoints among its various components. In my experience, the Qt4 version of Homestead is clearly several orders of magnitude faster than any of the other combinations tested. Another advantage of Qt is that it will always be available, regardless of what happens to Trolltech.

In 1998, keen to cement its relationship with the KDE community, Trolltech and KDE created the KDE Free Qt Foundation. This enshrines an agreement between KDE and Trolltech that gives the Foundation the right to release Qt under a BSD-style license if Trolltech doesn't continue the development of the GPL edition of Qt. If Trolltech does continue in its benevolent role, then the KDE community continues to benefit from having a professionally developed toolkit. Should Trolltech ever go bankrupt, or be bought out (something that could become an issue as Trolltech is rumored to be going public soon), and cease to be so free-software-friendly, the KDE community would be able to continue Qt development. Moreover, the choice of the BSD license makes it less likely that Trolltech would ever want to change its licensing, since if it did so another company could take the latest release of KDE under the BSD license and start developing its own proprietary product as a competitor.

A Description of Homestead

Homestead stands for Homestead Application Program. The database tables are well defined. So are the user interfaces and the required reports. Also well known are the files which need to be generated and mailed out on a yearly basis. Here are the database table definitions:

CREATE TABLE property
(
  proprty_id int4 NOT NULL DEFAULT nextval('property_seq'::text),
  county int4 NOT NULL,
  cntyname varchar(15) NOT NULL,
  txdistrict varchar(15) NOT NULL,
  legal varchar(1024) NOT NULL,
  parcel_id varchar(25) NOT NULL,
  pvalue numeric(10) DEFAULT 0,
  pctext numeric(3) DEFAULT 0,
  totincome numeric(14,2) DEFAULT 0.00,
  entry_id varchar(10) NOT NULL,
  entry_date timestamp,
  appstatus varchar(15),
  offuse varchar(3),
  itotinc numeric(12,2) DEFAULT 0.00,
  ivalue numeric(10) DEFAULT 0,
  ipctex numeric(3) DEFAULT 0,
  notes varchar(1024),
  CONSTRAINT property_pkey PRIMARY KEY (proprty_id),
  CONSTRAINT prop_county_chk CHECK (county >= 1 AND county <= 93)
)
WITHOUT OIDS;

ALTER TABLE property OWNER TO rev0205;
GRANT ALL ON TABLE property TO rev0205;
GRANT ALL ON TABLE property TO public;

CREATE INDEX county_index
  ON property
  USING btree (county);

CREATE INDEX entry_date_index
  ON property
  USING btree (entry_date);

CREATE INDEX entry_id_index
  ON property
  USING btree (entry_id);

CREATE UNIQUE INDEX proprty_id_index
  ON property
  USING btree
  (proprty_id);

The PersInfo table definition:

CREATE TABLE persinfo
(
  proprty_id int4 NOT NULL,
  ssn int4 NOT NULL,
  wholename varchar(35) NOT NULL,
  app_type int4 NOT NULL DEFAULT 0,
  hec int4 NOT NULL DEFAULT 1,
  yoba int4 NOT NULL DEFAULT 2003,
  ms int4 NOT NULL DEFAULT 1,
  sname varchar(35),
  syob int4,
  sssn int4,
  npart int4 NOT NULL DEFAULT 1,
  totincome numeric(14,2) NOT NULL DEFAULT 0.00,
  p2line1 numeric(12,2) NOT NULL DEFAULT 0.00,
  p2line2 numeric(12,2) NOT NULL DEFAULT 0.00,
  p2line3 numeric(12,2) NOT NULL DEFAULT 0.00,
  p2line4 numeric(12,2) NOT NULL DEFAULT 0.00,
  p2line5 numeric(12,2) NOT NULL DEFAULT 0.00,
  p2line6 numeric(12,2) NOT NULL DEFAULT 0.00,
  p2line7a numeric(12,2) NOT NULL DEFAULT 0.00,
  p2line7b numeric(12,2) NOT NULL DEFAULT 0.00,
  p2line7c numeric(12,2) NOT NULL DEFAULT 0.00,
  p2line8 numeric(12,2) NOT NULL DEFAULT 0.00,
  p1line1 numeric(12,2) NOT NULL DEFAULT 0.00,
  p1line2 numeric(12,2) NOT NULL DEFAULT 0.00,
  p1line3 numeric(12,2) NOT NULL DEFAULT 0.00,
  p1line4a numeric(12,2) NOT NULL DEFAULT 0.00,
  p1line4b numeric(12,2) NOT NULL DEFAULT 0.00,
  p1line5a numeric(12,2) NOT NULL DEFAULT 0.00,
  p1line5b numeric(12,2) NOT NULL DEFAULT 0.00,
  p1line6 numeric(12,2) NOT NULL DEFAULT 0.00,
  p1line7 numeric(12,2) NOT NULL DEFAULT 0.00,
  p1line8 numeric(12,2) NOT NULL DEFAULT 0.00,
  p1line9 numeric(12,2) NOT NULL DEFAULT 0.00,
  p1line10a numeric(12,2) NOT NULL DEFAULT 0.00,
  p1line10b numeric(12,2) NOT NULL DEFAULT 0.00,
  p1line10c numeric(12,2) NOT NULL DEFAULT 0.00,
  p1line11 numeric(12,2) NOT NULL DEFAULT 0.00,
  entint varchar(10) NOT NULL,
  entdate timestamp NOT NULL,
  batch int4 NOT NULL DEFAULT 0,
  rpt int4 NOT NULL DEFAULT 0,
  bdate varchar(4),
  remarks varchar(15),
  modtotinc numeric(14,2) NOT NULL DEFAULT 0.00,
  appstatus varchar(15),
  line8a numeric(12,2) NOT NULL DEFAULT 0.00,
  line8b numeric(12,2) NOT NULL DEFAULT 0.00,
  line8c numeric(12,2) NOT NULL DEFAULT 0.00,
  line8d numeric(12,2) NOT NULL DEFAULT 0.00,
  line8e numeric(12,2) NOT NULL DEFAULT 0.00,
  line8ec varchar(20),
  line8f numeric(12,2) NOT NULL DEFAULT 0.00,
  line8g numeric(12,2) NOT NULL DEFAULT 0.00,
  address varchar(34),
  city varchar(25),
  st varchar(2),
  zip int4 DEFAULT 0,
  azip int4 DEFAULT 0,
  careof varchar(35),
  begserv date,
  endserv date,
  vnss bool,
  CONSTRAINT persinfo_fkey FOREIGN KEY (proprty_id)
      REFERENCES property (proprty_id) MATCH FULL
      ON UPDATE NO ACTION ON DELETE CASCADE
)
WITHOUT OIDS;

ALTER TABLE persinfo OWNER TO rev0205;
GRANT ALL ON TABLE persinfo TO rev0205;
GRANT ALL ON TABLE persinfo TO public;

and here are the PersInfo indexes:

CREATE INDEX persinfo_propid
  ON persinfo
  USING btree (proprty_id);

CREATE INDEX persinfo_ssn
  ON persinfo
  USING btree (ssn);

CREATE INDEX persinfo_sssn
  ON persinfo
  USING btree (sssn);

CREATE INDEX persinfo_wholename
  ON persinfo
  USING btree (wholename);

CREATE UNIQUE INDEX proprtyid_ssn
  ON persinfo
  USING btree (proprty_id, ssn);

CREATE INDEX "public.persinfo_batch_rpt"
  ON persinfo
  USING btree (batch, rpt);

CREATE INDEX "public.persinfo_city"
  ON persinfo
  USING btree (city);

CREATE INDEX "public.persinfo_entdate"
  ON persinfo
  USING btree (entdate);

CREATE INDEX "public.persinfo_entint"
  ON persinfo
  USING btree (entint);

CREATE INDEX "public.persinfo_sssn"
  ON persinfo
  USING btree (sssn);

CREATE INDEX "public.persinfo_zip_azip"
  ON persinfo
  USING btree (zip, azip);

Note that Property.proprty_id is a foreign key into PersInfo. Also note that the primary applicant's Social Security Number is abbreviated ssn while the spouse Social Security Number is abbreviated sssn. A third table, County contains the list of counties in Nebraska.

CREATE TABLE county
(
  cnty int4 NOT NULL,
  county varchar(15)[] NOT NULL,
  Leftflag char(1) NOT NULL
);

ALTER TABLE county OWNER TO rev0205;

There are 94 records in county, with county = 'UNKNOWN' for cnty = 0.

The county table is used on the Utilities page to control reports. A list of counties is also hard coded into the county drop down combo box, in the Homestead initialization code, for the sake of speed.

Sometimes the clerks misspell the city or county name during data entry, or they mistype the zip code. And, the are situations in which a person is living near the border of one county but gets their mail in another country. The table Demogrph contains a list of all the cities, towns and villages in Nebraska, the county in which they are located, and each entity has an additional listing for each zip code. Currently there are 648 records in Demogrph. Demogrph is used by the analyst in a series of about 50 SQL queries that are used to clean up the Property and Persinfo tables after the applications have been entered, although some of those queries are made available to the Homestead clerks on the Utilities tab of the Homestead GUI so they can clean and test in a cyclic nature until the queries return no results. Demogrph has the following structure:

CREATE TABLE Demogrph
(
  city varchar(25) NOT NULL,
  county varchar(17) [] NOT NULL,
  population numeric(10),
  fed_id varchar(10) [],
  zip number numeric(5)
);

ALTER TABLE Demogrph OWNER TO rev0205;

The Homestead Information and Processing Cycle Explained

Folks who apply for a Homestead Exemption are usually over 65 years old and live in the home for which they are seeking reduction or elimination of property taxes, although some exceptions occur. Besides a spouse there may be more than one person living in that home. The primary applicant must supply an N458 form giving the address, legal description, value and other information about the property, and their Nebraska Schedule I - Income Statement filed the previous year. Others living in the home and receiving an income must also file a Nebraska Schedule I – Income Statement. To keep track of this information Homestead requires two primary tables: Property and Persinfo. The Property table contains the N458 information and the Persinfo table contains the Nebraska Schedule I – Income statement information. The relationship between Property and Persinfo is one-to-many.

When a new property is entered its record is given a surrogate primary key, proprty_id. A surrogate key is necessary because the Parcel description is not unique. Several counties could use the same parcel description for a piece of property. Also, parcel descriptions are often abbreviated and abbreviations are often not identical. The tax district information suffers the same problems. And, it is possible that even the applicant's name or Social Security Number could be submitted or entered in error or be changed at a later date. Using these kinds of data for primary keys is not wise. So, at the same time the Property record is created during data entry, a blank record in Persinfo is also created using that same surrogate key and the value of 0 for the ssn information. After the property information is saved the clerk moves to the Persinfo tab and enters the primary applicant's tax information, including their name, age, marital status, batch, report and other information. The clerk replaces the 0 in ssn with the actual Social Security Number of the applicant. Additional applicants can be entered at this time using buttons on the PersInfo tab designed for that purpose. As the data entry process continues the rate of data entry is tracked with the batch reports and new clerks are added to the task as necessary in order to meet mandated deadlines.

After a property and the associated applicants data are entered, their records can be located for later editing by using the property_id value, if it is known. However, it is more common to search for their information using either their whole name and middle initial, or their Social Security Number. Once the information for one applicant is found the information for the other applicants associated with that property is immediately available and can be displayed by clicking on their line in the PersInfo grid at the bottom of the Homestead GUI form.

It is possible that the same property could be entered into the Property table twice because one application was separated from the others and now no link between the original property record and the other applicant now exists. Another possibility is that the applicant may own more than one home, and thus will listed with two different properties. The cleanup queries mentioned earlier identify such problems as duplicate entries and verify those that are correct. After the data has been entered and verified the Reduced and Denied letters are printed and mailed out to applicants. Applicants receiving a 100% reduction in their property taxes are not notified but will learn of their status when they receive a property tax bill with no tax due. After the Reduced and Denied letters are sent out a pristine copy of the database is created that freezes the information as it existed at the time of the R&D mailings. This is for auditing purposes. Then, a subdirectory for the new year is created and the contents of last year's directory is copied to it, and the various locations in the GUI and reports where the year is used is updated to reflect the new year. Sometime in January a new N458 form, populated with current information is printed and mailed to the applicants so they can reapply for the new year. Last year's financial values are zeroed out. Then the process repeats itself. That, in a nutshell, is the Homestead processing cycle.

The Homestead Form Design Process

Since the form and structure of the Homestead GUI is well known it is easiest to begin the design process by designing the user graphical interface. First, create a directory which will be used to contain the development files. The run the Qt4 Designer. It will start up and present the following screen:

Qt4tut designer1.jpg

This screen show the options available when starting a new GUI screen. Since we are going to create a Dialog and not a main window or a widget we choose Dialog with Buttons Bottom and click the Create button. The blank form which is created is shown below.

Qt4tut designer2.jpg

What is known as a control in Windows programming is called a widget in Unix programming. Qt came from a Unix heritage so the text boxes, combo boxes, groups, frames, spinners, etc., are called widgets. The Widget tool bar is on the left side of the screen. On the right side of the screen is the properties tool bar. When a particular widget is dropped onto the form and is selected the properties for that widget show in the properties tool bar, where they can be edited. Below the properties tool bar is a small area called the Signal/Slot Editor. In GUI programming we often want a change in one widget to be notified to another widget. More generally, we want objects of any kind to be able to communicate with one another. Older toolkits achieve this kind of communication using callbacks. A callback is a pointer to a function, so if you want a processing function to notify you about some event you pass a pointer to another function (the callback) to the processing function. The processing function then calls the callback when appropriate. As I understand it, callbacks have two fundamental flaws. Firstly they are not type safe. We can never be certain that the processing function will call the callback with the correct arguments. Secondly the callback is strongly coupled to the processing function since the processing function must know which callback to call. More about pointers later.

Qt has an alternative to the callback technique. It uses signals and slots. A signal is emitted when a particular event occurs. Qt's widgets have many predefined signals, but you can always subclass to add your own. A slot is a function that is called in response to a particular signal. Qt's widgets have many predefined slots, but it is common practice to add your own slots so that you can handle the signals that you are interested in. This is done using the Qt connect command.

Here is an example:

connect( ui.btnSearch, SIGNAL(clicked()), this, SLOT(searchAll()) );

Without going into an unnecessary technical discussion here is what happens: when the Search button on the Homestead form is clicked it emits a clicked() signal. The receiver is this and represents the object in which the line of connect code resides. In this instance the connect command is in the initialization method of the Homestead class, so this is actually the Homestead class. The instantiated Homestead class object receives the clicked() signal and calls the Homestead::searchAll() method, which is one of its members. The searchAll() method is designed to do some searching in the database using the contents of the search textbox widget.

An object can emit more than one signal and more than one slot can relieve a signal. This makes for a very powerful communication technology. Here is how Trolltech diagrammed and explained it:

Qt4tut signals1.png
"The signals and slots mechanism is type safe: the signature of a signal must match the signature of the receiving slot. (In fact a slot may have a shorter signature than the signal it receives because it can ignore extra arguments.) Since the signatures are compatible, the compiler can help us detect type mismatches. Signals and slots are loosely coupled: a class which emits a signal neither knows nor cares which slots receive the signal. Qt's signals and slots mechanism ensures that if you connect a signal to a slot, the slot will be called with the signal's parameters at the right time. Signals and slots can take any number of arguments of any type. They are completely typesafe: no more callback core dumps!
All classes that inherit from QObject or one of its subclasses (e.g. QWidget) can contain signals and slots. Signals are emitted by objects when they change their state in a way that may be interesting to the outside world. This is all the object does to communicate. It does not know or care whether anything is receiving the signals it emits. This is true information encapsulation, and ensures that the object can be used as a software component."

All classes which inherit from QObject or QWidget have built in automatic garbage collection.

This is also a good time to review the meaning of the terms the stack”, the heap, static and dynamic, as they relate to C++ programming. Sometimes we create data structures that are "fixed" and don’t need to grow or shrink. Such data is allocated statically and is declared ahead of time. Data allocated in such way "lives" only as long as the function in which it was declared has scope. If we declare more variables than we need, we waste space. If we declare fewer variables than we need, we are out of luck. Often, real world problems mean that we don’t know how many variables to declare, as the number needed will change over time. When this occurs we want the ability to increase or decrease the number and size of our data structures to accommodate changing needs. Such data need dynamic allocation. We can allocate (create) additional dynamic variables whenever we need them, i.e., "on the fly". We can de-allocate (destroy) dynamic variables whenever we are done with them.

A key advantage of dynamic data is that we can always have a exactly the number of variables required — no more, no less. Dynamic allocation gives us more flexibility. Memory is still limited, but now we can use it where we need it and we can determine that while the program is running. We don't have to know ahead of time how many variables we'll need and how big they'll have to be. As you might suspect, the computer memory available for applications to use is divided into two areas: the stack and the heap. Variables allocated statically, often called automatic variables, are always created in the stack. The heap is memory not used by the stack. Dynamic variables live in the heap. We need a pointer variable to access our dynamic variables in the heap. More on that later.

So, officially, the stack storage is storage that is allocated automatically for automatic variables, register save areas, and other temporary uses by a function. Normally, stack storage is allocated from the stack when a function is called and is returned to the stack when the function returns. However, if the stack overflows (that is, runs out of available storage), the stack manager is called to allocate another block of storage for the stack.

In C++ heap storage is allocated by the new operator and deallocated by the delete operator. Both operators interface to the heap manager to allocate storage. The heap manager creates control information for the allocated storage block and formats the storage block so that overlays (that is, information written outside the boundaries of the storage block) can be detected. When an overlay is detected (either when the storage is freed or when the program terminates), the heap manager issues a user exception. This alerts the programmer to storage overlays. The heap manager can adjust the size of the allocated storage block in order to reduce fragmentation.

If you create a class on the heap without using one of Qt4's classes which use QWidget or QObject then you are responsible for cleaning up after yourself, that is, issuing the delete command before scope is lost. A class is created on the heap if the syntax is:

SomeClass *foo = new SomeClass();

The new operator calls the class's constructor method. If SomeClass does not inherit from QObject it must be destroyed before it's scope is lost using the following syntax:

delete foo;

otherwise your workstation will experience memory loss. The delete operator calls the class's destructor method. I have read somewhere that it is a good idea that if a class is designed without using QObject then it must always be instantiated on the stack, that is, created without using the new operator.

If the class is created on the stack it will be removed from memory when it goes out of focus. In other words, it will behave like a local variable. A class is created on the stack by the following syntax:

SomeClass foo;

or

QSqlQuery persQry;

or

QString propid = persQry.value(0).toString();

The same class may be instantiated either way. When created using the new keyword its members are referenced using:

someObject->someMethod()

When created without using the new keyword the members are referenced using:

someObject.someMethod()

In some situations it is not uncommon to use both reference methods within the same class. This can be illustrated by showing the main.cpp file from the Homestead application:

// main.cpp
#include <QSqlError>
#include <QMessageBox>
#include "homestead.h"
#include "dlglogin.h"
#include "wholenamedlg.h"
#ifdef _WIN32 // on Windows
#define DBDRIVER "QOCI"
#define DBHOST "orc1"
#define DBNAME "orc1"
#else // must be on Linux
#define DBDRIVER "QPSQL"
#define DBHOST "localhost"
#define DBNAME "homestead"
#endif
int main( int argc, char * argv[] ) {
       QString strRejected = "";
       QApplication app(argc, argv);
       app.setQuitOnLastWindowClosed(false);
       dlgLogin dlg;
       if( dlg.exec() == QDialog::Accepted ){
               QSqlDatabase hapdb = QSqlDatabase::addDatabase(DBDRIVER);
               hapdb.setHostName(DBHOST);
               hapdb.setDatabaseName(DBNAME);
               hapdb.setUserName(dlg.dui.leUserName->text());
               hapdb.setPassword(dlg.dui.leUserPassword->text());
               if ( hapdb.open() ) {
                       homestead ht;
                       ht.RevID = dlg.dui.leUserName->text();
                       ht.show();
                       app.setQuitOnLastWindowClosed(true);
                       return app.exec();
               } else {
                       strRejected = QString("The Login was rejected because:
                               %1").arg(hapdb.lastError().text()).toLatin1();
                       QMessageBox::information(0,"Login Rejected!",strRejected,
                              QMessageBox::Ok,QMessageBox::NoButton,QMessageBox::NoButton);
                       return 1;
               }
       } else {
               strRejected = QString("User Canceled the login!").toLatin1();
               QMessageBox::information(0,"Login Rejected!",strRejected,
                              QMessageBox::Ok,QMessageBox::NoButton,QMessageBox::NoButton);
               return 2;
       }
}

On some lines both the "." and the "->" operator are used. This file introduces another point about programming using C++. Every C++ application must contain a function called main(). I could have designed Homestead by including everything in main.cpp, but that would have made coding difficult to maintain or to do version control. As it is, the database login dialog is an independent class defined in dlgLogin.h and dlgLogin.ui, and they can be used or subclassed to add a login dialog to any Qt4 project.

Let's get back to the dialog form. I have added a few widgets to the form and saved it as dlglogin.ui. Turning on the Designer's connections editor gives the following screens:

Qt4tut signals2.png

which shows that I dragged the OK button to the form, causing the Configure Connection dialog to appear.

Since this dialog is for logging into an Oracle database I have not asked for the name of the database driver or the host.

If you look at main.cpp you will notice the following lines of code:

#define DBDRIVER "QOCI"
...
QSqlDatabase hapdb = QSqlDatabase::addDatabase("DBDRIVER");

in which the argument QOCI specifies that the Oracle database driver will be used. Also notice that hapdb is created on the stack, not the heap, so when the function main() loses focus hapdb will be automatically destroyed.

I connected the OK button clicked() signal to the form's accept() slot. Later, I connected the Cancel button clicked() signal to the form's reject() slot, which is shown in blue.

The actual dlgLogin class is defined in the header file, dlglogin.h:

#ifndef DLGLOGIN_H
#define DLGLOGIN_H
#include "ui_dlglogin.h"
class dlgLogin : public QDialog
{
    Q_OBJECT
public:
    Ui::dlgLoginUi dui;
    dlgLogin()
    {
        // called with 'dlgLogin dlg'.
        dui.setupUi(this);
        dui.leUserName->setText("your RevID");
        dui.leUserPassword->setText("");
        dui.leUserName->setFocus();
        dui.leUserName->selectAll()
    }
};

Do you notice the line #include "ui_dlglogin.h"? It references a file which may not exist the first time you compile your application. The user interface compiler, uic, reads the dlglogin.ui file and creates the ui_dlglogin.h header file. As the programmer, I included the #include "ui_dlglogin.h" directive on faith that the uic will create it. There are certain times when that faith is violated and you'll receive a Can't locate ui_dlglogin.h error message even though you may see it in the list of files in your project. The problem is usually the timestamp on the file. Delete the file and uic will see that it needs to be created again and will create it, just in time. You might be asking yourself What is in the ui_dlglogin.h file?

Take a look, but remember that this is generated code and while it may present interesting examples of C++ coding of a Qt object, there is nothing to be gained by editing this code. The next time you compile, your changes would be overwritten with the newly generated code. Notice the connect code in red.

#ifndef UI_DLGLOGIN_H
#define UI_DLGLOGIN_H
#include <QtCore/QVariant>
#include <QtGui/QAction>
#include <QtGui/QApplication>
#include <QtGui/QButtonGroup>
#include <QtGui/QDialog>
#include <QtGui/QLabel>
#include <QtGui/QLineEdit>
#include <QtGui/QPushButton>
#include <Qt3Support/Q3MimeSourceFactory>
class Ui_dlgLoginUi
{
public:
    QLabel *lblDglLoginForm;
    QPushButton *btnCancel;
    QLabel *lblHOST;
    QLineEdit *leHost;
    QLabel *lblUserPassword;
    QLineEdit *leUserPassword;
    QLineEdit *leUserName;
    QLabel *lblUserName;
    QLabel *lblDataBaseName;
    QPushButton *btnOK;
    QLineEdit *leDatabaseName;
    void setupUi(QDialog *dlgLoginUi)
    {
    dlgLoginUi->setObjectName(QString::fromUtf8("dlgLoginUi"));
    dlgLoginUi->resize(QSize(563, 238).expandedTo(dlgLoginUi->minimumSizeHint()));
    dlgLoginUi->setModal(true);
    lblDglLoginForm = new QLabel(dlgLoginUi);
    lblDglLoginForm->setObjectName(QString::fromUtf8("lblDglLoginForm"));
    lblDglLoginForm->setGeometry(QRect(35, 10, 182, 25));
    QFont font;
    font.setFamily(QString::fromUtf8("Courier New"));
    font.setPointSize(14);
    font.setBold(true);
    font.setItalic(false);
    font.setUnderline(false);
    font.setWeight(75);
    font.setStrikeOut(false);
    lblDglLoginForm->setFont(font);
    btnCancel = new QPushButton(dlgLoginUi);
    btnCancel->setObjectName(QString::fromUtf8("btnCancel"));
    btnCancel->setGeometry(QRect(472, 191, 75, 26));
    [snip]
    leUserName = new QlineEdit(dlgLoginUi);
    leUserName->setObjectName(QString::fromUtf8("leUserName"));
    leUserName->setGeometry(QRect(140, 105, 388, 21));
    lblUserName = new QLabel(dlgLoginUi);
    [snip]
    lblDataBaseName = new QLabel(dlgLoginUi);
    lblDataBaseName->setObjectName(QString::fromUtf8("lblDataBaseName"));
    lblDataBaseName->setGeometry(QRect(30, 55, 103, 21));
    lblDataBaseName->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
    btnOK = new QPushButton(dlgLoginUi);
    btnOK->setObjectName(QString::fromUtf8("btnOK"));
    btnOK->setGeometry(QRect(391, 191, 75, 26));
    btnOK->setDefault(true);
    leDatabaseName = new QLineEdit(dlgLoginUi);
    leDatabaseName->setObjectName(QString::fromUtf8("leDatabaseName"));
    leDatabaseName->setGeometry(QRect(140, 55, 388, 21));
    QWidget::setTabOrder(leDatabaseName, leHost);
    QWidget::setTabOrder(leHost, leUserName);
    QWidget::setTabOrder(leUserName, leUserPassword);
    QWidget::setTabOrder(leUserPassword, btnOK);
    QWidget::setTabOrder(btnOK, btnCancel);
    retranslateUi(dlgLoginUi);
    QObject::connect(btnCancel, SIGNAL(clicked()), dlgLoginUi, SLOT(reject()));
    QObject::connect(btnOK, SIGNAL(clicked()), dlgLoginUi, SLOT(accept()));
    QMetaObject::connectSlotsByName(dlgLoginUi);
    } // setupUi
    void retranslateUi(QDialog *dlgLoginUi)
    {
    dlgLoginUi->setWindowTitle(QApplication::translate("dlgLoginUi", "Login to Database"));
    lblDglLoginForm->setText(QApplication::translate("dlgLoginUi", "Oracle Login:"));
    btnCancel->setText(QApplication::translate("dlgLoginUi", "&Cancel"));
    btnCancel->setShortcut(QApplication::translate("dlgLoginUi", "Alt+C"));
    lblHOST->setText(QApplication::translate("dlgLoginUi", "Database Host: "));
    lblUserPassword->setText(QApplication::translate("dlgLoginUi", "User Password: "));
    lblUserName->setText(QApplication::translate("dlgLoginUi", "User Name: "));
    lblDataBaseName->setText(QApplication::translate("dlgLoginUi", "Database Name: "));
    btnOK->setText(QApplication::translate("dlgLoginUi", "&OK"));
    btnOK->setShortcut(QApplication::translate("dlgLoginUi", "Alt+O"));
    Q_UNUSED(dlgLoginUi);
    } // retranslateUi
};
namespace Ui {
    class dlgLoginUi: public Ui_dlgLoginUi {};
} // namespace Ui
#endif // UI_DLGLOGIN_H

Don't confuse ui_dlglogin.h with dlglogin.ui or dlglogin.h.

Another feature to notice is one which will/should be in all C++ header files. I mentioned that there is an #include line in the dlglogin.h file that states: #include "ui_dlglogin.h".

In ui_dlglogin.h the lines:

#ifndef UI_DLGLOGIN_H
#define UI_DLGLOGIN_H

and

#endif // UI_DLGLOGIN_H

are called preprocessor directives because they control preprocessor actions at compile time.

That line causes the ui_dlglogin.h header file to be read into memory and included with the compile of dlglogin.h under the following condition: the #ifndef (If Not Defined) directive causes the preprocessor to check memory to see if the variable UI_DLGLOGIN_H had already been declared. If it had not, then the UI_LGLOGIN_H variable would be declared and the header file would be brought into the compile process. If UI_LGLOGIN_H exists then the code between #define and #enddef would be ignored. This prevents classes and variables from being declared more than once and abending the compile with duplicate declaration errors. The only precaution a programmer needs to take is to use the include directive in the header or .cpp file before he uses classes or variables defined in that header or .cpp file. In the main.cpp file the following line: #include "dlglogin.h" causes the preprocessor to add the definitions in the dlglogin.h header to the main.cpp at the compile time. This is how main.cpp "knows" about the dlgLogin class. If I were to put that same include line into Homestead.cpp no harm would be done.

It's now time to take a look at the Property tab of the Homestead window:

Qt4tut screen1.png

Visible is the search text box and the search button. Between them is the radio button group which is used to designate the type of data that was entered into the search text box. On the frame is the property information tab.

When the clerk selects the county name in the drop down combo box, the county number is set automatically in the text box just to the right of the combo box. To the extreme right of the county number box is an unlabeled and disabled text box which shows the proprty_id value. The big white box with the title LEGAL on its left side is a multi-line text box. Below it are three sets composed of a label and a text box, each surrounded by a red line. The red line represents a horizontal layout setting which binds the two widgets together. On either side of the middle set is an elastic spacer widget. Its purpose is to keep the three sets appropriately spaced when the main window is resized. Spacers between the buttons do the same thing. Horizontal and Vertical layouts represent Java type control placement. But, the rest of the widgets on this tab use absolute, or X,Y placement. Below the tab frame are two grids. The one on the left is the PersInfoGrid and the one on the right is the MultiPropGrid. Below the two grids is an elongated text box serving as a status line.

Here is the PersInfo frame:

Qt4tut screen2.png

This frame shows some dense columns of labels and text boxes. Each column is part of a vertical layout, which helps considerably in keeping the respective labels lined up with their columns. I tried to do a horizontal layout on each label + text box pair, but spacing between the widgets and the layout indicators were too difficult to control. So the "java-like" layout control is a mixed bag in Qt. Also, it's apparent that high density widgets suppress expression of layout indicators, i.e., the red boxes that are supposed to show layouts. This is a trivial Qt bug. Notice that as each tab is selected the form areas containing the search text box and button, and the grids, remain visible because they are on the form itself, and not on the tab frame. I won't show the Notes tab because it is merely one large multi-line edit box that is enabled when the Edit button on the property tab is clicked, and disabled when the Property save button is clicked. The Edit and Save button are the same. I use the label property of that button to indicate the state of the form and control which functions are executed.

The Utilities tab shows actions which clerks take either daily or at certain times in the process cycle.

Qt4tut screen3.png

Converting legacy applications to Qt4

Homestead is a perfect test bed for a GUI widget set because it is not a simple Address Book nor is it too complex. It is typical of many applications, and represents one where a complete solution can be programmed as a test in a relative short time. So is TimeRecs, our payroll application. On the other end of the spectrum, Gaming has 24 executables that display 61 forms, and most of those have a frame with at least 3 or more tabs. Gaming is built around almost 200 DBF tables, the principle ones having the following relationships:

 The Gaming Database Schema
    The following tables are the principal tables in the Gaming application.

              |-- f50a == licenses
              |
              |-- f50b --|-- f50bs1
              |           |== licenses
              |
              |-- f50f -- picmach
              |
              |         |-- f50c == licenses
              |         |-- f50e == licenses
              |         |-- f50h == licensees
    address --|-- f50 --|-- officers
              |         |-- pcprem      |-- bcprem == boccas
              |         |-- f50s1    --|
              |         |               |-- bcchair
              |         |
              |         |-- f50s2 ==|== f50d == licenses
              |         |            |== licenses
              |         |== licenses
              |
              |             |==indivadr
              |== owners ==|
              |             |== address
              |
              |           |== f50gs1 == licenses
              |           |== f50gs2 == licenses |== gs3lo
              |-- f50g --|-- g50gs3 =============|
              |           |-- cclotrep            |== gs3so
              |           |== licenses
              |
              |           |-- balfwd
              |-- f51 ---|
              |           |-- taxtable
              |
              |-- f50j
              |
              |-- f52
              |
              |-- ART
              |
              |-- ARS

       DTS ==|== address

    -- refers to relational links via the value of KeyFld. Since the DBC is not used in gaming,
       these joins are in the data environment of each form, and the data sessions are private.
    == refers to pseudo links via the value of other table fields, not relational.
       This violates 3rd NF, but sometimes one has to do that to avoid burdensome complexity

Not all of the gaming tables are shown because many are singletons. Gaming is obviously too complex to use as a test bed. Gaming took 5 years to develop using VFP 5.0 and 6.0. It is my estimate that converting Gaming to Qt4 and Oracle would probably take at least 12–18 months for a developer experienced with Gaming, Qt and SQL. The lessor time if the Crystal Reports forms were re-used and the longer time if reports were generated using another tool, or from scratch. This also assumes that the developer devotes full time to the task. TimeRecs is "hooked" to Windows because the output is an XLS spreadsheet that is uploaded into the NIS system. Converting it to Qt and Oracle would require that the XLS spreadsheet be created by passing the appropriate data to an OpenOffice spreadsheet via OO's exposed properties and methods using Universal Network Objects (UNO), which are similar to Microsoft's COM objects. Here is an example of how to do that using an XLS template and the toolkit is here.

Examples of Coding from Homestead

main.cpp, Homestead.h and Homestead.cpp.

As mentioned before, every C++ application must have a function called main(). This function is the one called when the application starts up. While one can put their entire application into main.cpp, for larger or more complicated applications, and ones which incorporated pre-designed classes, it is better to break apart the application into those parts that can fit into a header file and those than fit into an implementation file. Here is the Homestead.h header file:

#ifndef HOMESTEAD_H
#define HOMESTEAD_H
#include <QMainWindow>
#include <QString>
#include <QStringList>
#include <QSharedData>
#include <QtSql/QSqlDatabase>
#include <QtSql/QSqlQuery>
#include <QtSql/QSqlRecord>
#include <QtSql/QSqlQueryModel>
#include <QDateTime>
#include <QDate>
#include <QTableView>
#include <QMessageBox>
#include <QPrinter>
#include <QPainter>
#include <QHeaderView>
#include <QTextStream>
#include <QFile>
#include <QDialog>
#include <QInputDialog>
#include "ui_homestead.h"
class homestead : public QMainWindow
{
        Q_OBJECT
// header files only contain declarations of objects, global vars,
// and object types. NO actual code or assignment statements go here!
// data types assigned below should have relevant include statements above.
// only data TYPES are used in arguments to method declarations, NOT actual vars.
public:
        homestead(QWidget *parent = 0);
        QSqlQuery propQry;
        QSqlQuery persQry;
        QSqlQuery xtabQry;
        QSqlQuery multiQry;
        QSqlQuery AllPropQry;
        QSqlQuery PropPersInfo;
        QSqlQuery N458Qry;
        QSqlQuery owners;
        QTableView gridPersInfo;
        QTableView gridMultiProp;
        QString  persQryStr;
        QString  persPIDqryStr;
        QString  persPIDSSNqryStr;
        QString  persSSNqryStr;
        QString  persWNqryStr;
        QString  propPIDqryStr;
        QString  strQry;
        QString dbYear;
        QString RevID;
        QStringList sRpt;
        int Year65;
        QSqlQueryModel *model ;
        QPrinter printer;
        QPainter p( QPrinter );
        QHeaderView *viewhhdr;
        QTextStream out( QFile );
        QString HTML_Header1;
        QString HTML_Header2;
        QString HTML_Footer;

private:
         Ui::homesteadUi ui;
         void displayProperty( QSqlQuery );
         void enableProperty();
         void disableProperty();
         void UpdateProperty();
         int calcPctExm( int, int, float );

        void displayPersInfo( QSqlRecord );
        void enablePersInfo();
        void disablePersInfo();
        void UpdatePersInfo();
        QString itos( int );
        QString ftos( float );
        QString ftois( float );
        float Round(float, int );
        void displayPersGrid( QSqlQuery );
        void displayMultiGrid( QSqlRecord );
        void ZeroPartOne();
        void ZeroPartTwo();
        bool Between( float, float, float);
        bool calcPropRow( int, QDateTime, bool );
        bool insertEmptyPersInfoRec(QString, int, QDateTime);
        bool createConnection();

private slots:
        void QuitClicked();
        void searchAll();
        void editProperty();
        void undoProperty();
        void newProperty();
        void deleteProperty();
        void persinfoSwap();
        void editPersInfo();
        void Recalculate();
        void UndoPersInfo();
        void NewIncome();
        void DeleteIncome();
        void toggleVNI();
        void toggleReject();
        void passApptype ( int );
        void passMsNum( int );
        void passCntyNum( int );
        void displaySelectedMultiProp(const QModelIndex & );
        void displaySelectedPersInfo(const QModelIndex & );
        void CalcAllProperties();
        void calcThisProperty();
        void printDateBtchRpt();
        void printBatchBatchRpt();
        void partLostFocusAction();
        void GenerateXTabAllCounties();
        void GenerateXTabEachCounty();
        void SearchHistory();
        void generateN458files();
}; // end of class haptest

#endif

Homestead.h has an include section and a class definition session which contains a public subsection, a private subsection and a private slots subsection. No code should be placed in header files, only class and variable declarations! Public declarations are those that you wish to be visible from outside the application or from any function within the application. Private declarations are visible only from within the application. Notice that the first public variable is Homestead(QWidget *parent = 0);, the Homestead class itself! The first private variable is Ui::HomesteadUi ui; the GUI interface. The name HomesteadUi is what I assigned to the homestead.ui in the Designer. You can put too few declarations into the header. The compiler will tell you what you are missing, if anything. To be honest, I created variables and functions in homestead.cpp and put declarations in homestead.h when the compiler complained about them being undeclared.

The purpose of the #ifndef, #define, #endif compiler directives were explained earlier. If a Qt4 API class is referenced in the public or private sections of the header file then an include statement must be added so that the compiler will know where find and load the class definition. For example, several public variables were declared using QSqlQuery, but that class needs to be included only once as #include <QtSql/QSqlQuery>. If you forget to include an API class that you've used in a definition, or you forget to declare a variable, the compiler will tell you about it. For example, if I put a // in front of the QSqlQuery propQry; line, save it and recompile, the compiler will complain with:

Homestead.cpp(212) : error C2065: 'propQry' : undeclared identifier<tt>.

It sees an error at line 212 of <tt>Homestead.cpp because the first reference to propQuery outside of the header file is propQry.exec(queryStr);, at that line number. However, every other occurrence or reference to propQry will also generate at least one compiler error too, so always expect to see more than one error listed per bug. To the novice user of C++ this can make it appear as if your application is loaded with errors. Simply take the first error the compiler gives you and fix it, and any other related and obvious errors. Then recompile. The error listing will rapidly diminish. Repeat until you have a clean recompile. A side note: the compiler will generate one of two possible executables: the release version and the debug version. I've mentioned them before when discussing the project file, Homestead.pro. Which one gets generated? It depends on which Makefile the compiler reads from, and that depends on your compiler and/or project file setting. Under the Windows environment using MSVC++2003 you can bypass the default setting and choose by issuing either nmake -f Makefile.Debug or nmake -f Makefile.Release.

The Drop Down Combo Box

There are 93 counties in the state of Nebraska, and that number is not likely to change. That fact, plus a consideration for speed, led me to use a static array to load the cboCountyName combo box. cbo is notation for QComboBox To the right of that combo box is a QLineEdit widget called leCountyNumber. The le is notation for QLineEdit. When ever a new county name is selected in cboCountyName its county number will automatically appear in leCountyNumber. Here is how that is done. Since the county name data needs to be available all the time the Homestead application is running I will load the combo box with the data in the Homestead class constructor.

Homestead::Homestead(QWidget *parent) : QMainWindow(parent) {
        // Homestead constructor method
        // initialize global variables in this constructor
        ui.setupUi(this); // draw the gui interface
        // set entries to drop down combos.
        QStringList cntyList; // created on the stack
        cntyList << "OUTSTATE" << "ADAMS" << "ANTELOPE" << "ARTHUR" << "BANNER" << "BLAINE" << "BOONE"
                 << "BOX BUTTE" << "BOYD" << "BROWN" << "BUFFALO" << "BURT" << "BUTLER" << "CASS"
                 [snip]
                 << "PERKINS" << "PHELPS" << "PIERCE" << "PLATTE" << "POLK" << "RED WILLOW" << "RICHARDSON"
                 << "ROCK" << "SALINE" << "SARPY" << "SAUNDERS" << "SCOTTS BLUFF" << "SEWARD" << "SHERIDAN"
                 << "SHERMAN" << "SIOUX" << "STANTON" << "THAYER" << "THOMAS" << "THURSTON" << "VALLEY"
                 << "WASHINGTON" << "WAYNE" << "WEBSTER" << "WHEELER" << "YORK";
        ui.cboCountyName->addItems( cntyList );
         QStringList msList;
        msList << "Single" << "Married" << "Widow" << "Closely Related";
        ui.cboMS->addItems( msList );
        QStringList appList;
        appList << "Applicant" << "Applicant & Spouse" << "Spouse" << "Other Owner/Occupant";
        ui.cboAppType->addItems( appList );
        [snip]
}

A class constructor is coded as:

ClassName::ClassName( QObject *parent ) : QObject( parent )
{
   ...
   initialization code here
   ...
}

in its classname.cpp implementation. There are variations, depending on the base class. Here is the dlgLogin class definition:

dlgLogin::dlgLogin(QWidget *parent) : QDialog(parent) { ...

Notice that dlgLogin uses QWidget and QDialog instead of QObject.

Continuing with the combo boxes, the same method that was used to populate cboCountyName is used to populate the marital status and application type combo boxes, since they also represent static data. Were any of this data subject to clerical modification then an SQL query (QSqlQuery class) would have been used in the Edit mode function to populate the combo boxes with the latest data before the combo boxes were enabled. But, the data is static.

Also in the Homestead constructor is the connect code which connects the scrolling of the items in the cboCountyName combo box to the contents of leCountyNumber:

connect( ui.cboCountyName, SIGNAL(highlighted(int)), this, SLOT(passCntyNum(int)) );

The index of the cboCountyName list is passed by value as an int to the passCntyNum() method. Here is the definition of passCntyNum():

void Homestead::passCntyNum( int nNum ) {
       nNum += 1;
       ui.leCountyNumber->setText(itos(nNum));
       ui.leCountyNumber->repaint();
}

passCntyNum() is declared in the Homestead.h header file as: void passCntyNum( int ); under the private: label, making it inaccessible from outside. Since Adam is county number 1, etc., and Qt4 combo box indexes are zero based, I must add one to the int value passed before I pass it on to the leCountyNumber text box. As I scroll the combo box thumb wheel up and down each count is highlighted and the highlighted() signal gets emitted and passed to the passCntyNum() method, which processes it by changing the value in the county number widget to reflect the index of the cboCountyName widget plus one. Addendum: this function is no longer necessary since OUTSTATE was moved from position 94 to position 0 in the combobox list.

You may be asking why lecountyNumber, and the other widgets, have a ui. prefix. That is because they are members of the ui object that I had previously created in the Homestead constructor with the command ui.setupUi(this);. In Homestead.h is the include #include "ui_Homestead.h" directive, which tells the compiler to load that header file. That header file doesn't exist until it is created by the uic, when uic parses Homestead.ui, which was the output of the Qt4 Designer when I designed the Homestead form.Also in Homestead.h is the declaration Ui::HomesteadUi ui;, which names my graphical user interface as ui. The Ui is the Qt4 API GUI namespace. HomesteadUI is the name I gave my Homestead form in the Qt4 Designer. I chose ui as the instantiated class because it is only two characters and would be easy to type. Notice that everything is case sensitive, so Ui and ui refer to two different things. Also notice that I reference the Homestead class members with a "." and not a "->", which implies that Homestead is on the stack. The dlgLogin UI initialization code uses dui.setupUi(this); because the dlgLogin form is called dlgLoginUi and the dlgLogin.h header file contains Ui::dlgLoginUi dui;. The complete dlgLogin class definition was shown earlier in the complete dlgLogin.h listing.

The code in main.cpp first displays the dlgLogin GUI and if the proper information is entered it then the main window, Homestead, is displayed. Here is the relevant code in main.cpp:

1)  //main.cpp
2)  #include <QApplication>
3)  #include <QSqlDatabase>
4)  #include <QSqlError>
5)  #include <QMessageBox>
6)
7)  #include "homestead.h"
8)  #include "dlglogin.h"
9)  #include "wholenamedlg.h"
10) #ifdef _WIN32 // on Windows
11) #define DBDRIVER "QOCI"
12) #define DBHOST "orc1"
13) #define DBNAME "orc1"
14) #else // must be on Linux
15) #define DBDRIVER "QPSQL"
16) #define DBHOST "localhost"
17) #define DBNAME "homestead"
18) #endif
19)
20) int main( int argc, char * argv[] ) {
21)         QString strRejected = "";
22)         QApplication app(argc, argv);
23)         app.setQuitOnLastWindowClosed(false);
24)         dlgLogin dlg;
25)         if( dlg.exec() == QDialog::Accepted ){
26)                  QSqlDatabase hapdb = QSqlDatabase::addDatabase(DBDRIVER);
27)                  hapdb.setHostName(DBHOST);
28)                  hapdb.setDatabaseName(DBNAME);
29)                  hapdb.setUserName(dlg.dui.leUserName->text());
30)                  hapdb.setPassword(dlg.dui.leUserPassword->text());
31)                  if ( hapdb.open() ) {
32)                           homestead ht;
33)                           ht.RevID = dlg.dui.leUserName->text();
34)                           ht.show();
35)                           app.setQuitOnLastWindowClosed(true);
36)                           return app.exec();
37)                  } else {
38)                           strRejected = QString("The Login was rejected because:
    %1").arg(hapdb.lastError().text()).toLatin1();
39)                           QMessageBox::information(0,"Login Rejected!",strRejected,
40)                                   QMessageBox::Ok,QMessageBox::NoButton,QMessageBox::NoButton);
41)                           return 1;
42)                  }
43)         } else {
44)                  strRejected = QString("User Canceled the login!").toLatin1();
45)                  QMessageBox::information(0,"Login Rejected!",strRejected,
46)                                   QMessageBox::Ok,QMessageBox::NoButton,QMessageBox::NoButton);
47)                  return 2;
48)         }
49) }
  1. Lines 1–18 bring in the Qt APIs, homestead header files and complier defines.
  2. Line 20 begins the definition of the main() function.
  3. Line 22 creates the Qt application object.
  4. Line 23 prevents Homestead from closing when the first GUI, dlgLogin, closes and line 35 allows the Homestead instance, if it is created, to close after it's Quit button is clicked.
  5. Line 24 creates the dlgLogin instance, dlg, which displays the login form. Note that dlg.exec() calls dlg in the modal mode while dlg.show() would have opened it in the modeless mode. The user accepts or edits the offerings in the dlg text boxes and presses either the OK or the Cancel button.
  6. Lines 26 to 42 execute if the user pressed OK on the login form, otherwise the Homestead application closes.
  7. Line 26. The hapdb object to the Oracle database is created. Since this is the only database connection made in Homestead it becomes the default connection and all data-aware object will automatically connect to this database without having to use the hapdb object name. If more than one connection were made then the syntax would be modified to reflect the database object.
  8. Lines 27–30 sets up the hapdb connection information.
  9. Line 31. Open and test the hapdb connection. If the connection fails leave a message and then close Homestead.
  10. Line 32. Use the Homestead class to create an instance of Homestead called ht.
  11. Line 33. Assign the user's login name to the Homestead property RevID.
  12. Line 34. Show the instance of the Homestead form, ht, in the modeless mode.
  13. Now the Homestead form is up and running in the modeless mode, ready to edit the Homestead data.

Creating and Using Database Queries

Before the search command can find a person or property it must have access to the data in the database. This access is given using the Qt4 QSqlDatabase class, shown earlier, and the QSqlQuery class. In the Homestead.h header file the class declaration of Homestead lists the QSqlQuery propQry declaration under the public section, making it globally accessible.

In the Homestead::searchAll() method the query instances are used but need the support of some string objects:

QString queryStr = "";
QString seekWN = "'"; // set up for possible Whole Name search
QString requestString;

The QString variable, queryStr, is created on the stack and assigned an empty string. It is local to the searchAll() method. Since it and the other QString variables are automatic variables, they will be removed automatically when searchAll() returns. The seekWN variable is assigned a value of a single quote. After requestString is created it is assigned a value in a subsequent command, illustrating that creation and assignment of QString instances can be done with one or two commands.

requestString = "Searching for: ";
requestString.append(ui.leSearch->text()); // echo search request

After the requestString is created and assigned an initial string, further manipulations of its contents are done with methods inherited from the QString class. One method is append(), as is shown in the line above. Had I created requestString using the new command I would have had to call QString inherited append function using requestString->append(...), but I haven't so,

if (ui.rbProprtyID->isChecked()) {
   // user is searching for property id
   requestString.append(" in Property");
   ui.leStatus->setText(requestString);
   queryStr = this->propPIDqryStr;
   queryStr.append(ui.leSearch->text());
   propQry.exec(queryStr);
   if (propQry.next()) {

The next line of code checks the state of the radio button rbProprtyID. If it is checked then a query into the Property table of hapdb was requested and it will be created and executed. The purpose of requestString is to create a message which can be displayed in the leStatus text box so the user is kept informed as to what is happening. Then queryStr is assigned this->propPIDqryStr. What is propPIDqryStr? It is a class member of Homestead and was assigned an incomplete query select string in the Homestead constructor, Here is it's assignment statement:

// create query string that selects for proprty_id in property table
this->propPIDqryStr = "SELECT proprty_id, county, cntyname, txdistrict, legal, ";
this->propPIDqryStr.append("parcel_id, pvalue, pctext, totincome, entry_id, entry_date, ");
this->propPIDqryStr.append("appstatus, offuse, itotinc, ivalue, ipctex, notes FROM property ");
this->propPIDqryStr.append("WHERE proprty_id = ");

The this object refers to the ht instance of Homestead. Notice that the query is not complete. The actual value of a property_id in the Property table is not supplied. In the searchALL() method that value is supplied with the following command:

   queryStr.append(ui.leSearch->text());

which appends the numeric value of the proprty_id for a particular record in Property that the user entered into the leSearch text box and is wanting to retrieve and display. The next statement, propQry.exec(queryStr); actually submits the query to the Hapdb. The success of that query is determined by the next statement, if (propQry.next()) {,, and if the next() method inherited from the QSqlQuery class actually moves the query pointer off the initial phantom row of the query result onto the first row of real data and returns a true to the if statement. If no records are found a false is returned. Here is the remaining part of that if statement:

   // found property record, now fetch associated persInfo record
  foundProp = true;

A previously defined boolean which was set to false, foundProp, is set to true.

   queryStr = this->persPIDqryStr;
   queryStr.append(ui.leSearch->text());
   //QMessageBox::information(this,"Homestead",queryStr.right(20));
   persQry.exec(queryStr);
   if (persQry.next())
      foundPers = true;
}

The variable queryStr is reused with a different partial query string, persPIDqryStr, which will search for records in the PersInfo table which have a proprty_id value of that entered by the user into leSearch. Since there could be more than one applicant to a property there may be more than one record returned from the PersInfo table to the query. Like the propQry, if valid results are returned from the persQry.next() command then the remaining part of that if command is executed, which sets the boolean variable, foundPers, to true. It will be used later when the form widgets are updated to show the values the query returned. Here is a good time to mention that navigating a QSqlQuery instance can be done with next(), previous(), first(), last() and seek(). Notice that the use of the QMessageBox is commented out, but serves as an example of how to use that API. See the Assistant for more information.

Displaying Retrieved Data in the Form Widgets

The searchALL() function concludes with the following code:

if (!foundProp && !foundPers) {
   ui.leStatus->setText("Nothing Found!");
   return;
}
if (!foundProp && foundPers) {
   ui.leStatus->setText("Found Orphaned Persinfo record.");
   return;
}
if (foundProp) {
   displayProperty(propQry);
   if (foundPers) {
      // display first record of persQry on the form
      // displayPersInfo also displays gridMultiProp for
      // first record of persQry.   DisplayPersGrid displays
      // all applicaants related to a property
      QSqlRecord persRec = persQry.record();
      displayPersInfo(persRec);
      displayPersGrid(persQry);
   } else {
      ui.leStatus->setText("No persinfo record for this property ID.");
   } // end of if foundpers
} // end of if foundProp

As the comments point out, the persQry query may return with one or more records. The first record in the persQery record set is extracted using the QSqlRecord class, as the code shows. Records are also indexed using the zero base, so persQry.record(1) would return the second record of the persQry set of records. If no index value is passed then persQry.record(0) is assumed. The displayPersInfo() method is passed the persRec record, which will display the record field values in the appropriate form widgets. The displayPersGrid() function is passed the entire persQry set of records so that all of them can be displayed in the persInfoGrid widget. Here is the entire definition of the displayProperty() method, sized so that no line breaks occur:

void Homestead::displayProperty(QSqlQuery propQry) {
   ui.leProprtyID->setText(propQry.value(0).toString()); // proprty_id
   ui.leCountyNumber->setText(propQry.value(1).toString());
   ui.cboCountyName->setCurrentIndex(ui.cboCountyName->findText(propQry.value(2).toString()));
   ui.leTaxDistrict->setText(propQry.value(3).toString());
   ui.txtLegal->setPlainText(propQry.value(4).toString());
   ui.leParcelID->setText(propQry.value(5).toString());
   ui.leHomeValue->setText(propQry.value(6).toString());
   ui.leTYPctExemption->setText(propQry.value(7).toString());
   ui.leTYTotalIncome->setText(propQry.value(8).toString());
   ui.leClerkID->setText(propQry.value(9).toString());
   ui.dteEntryDate->setDateTime(propQry.value(10).toDateTime());
   ui.leAppStatus->setText(propQry.value(11).toString());
   ui.leTYOffUse->setText(propQry.value(12).toString());
   ui.leLYTotalIncome->setText(propQry.value(13).toString());
   ui.leLYHomeValue->setText(propQry.value(14).toString());
   ui.leLYPctExemption->setText(propQry.value(15).toString());
   ui.teNotes->setPlainText(propQry.value(16).toString());
}

First, it is declared in Homestead.h as void displayProperty( QSqlQuery );. The definition follows on that in Homestead.cpp as void Homestead::displayProperty(QSqlQuery propQry). Note that the declaration only uses the API class as a parameter, but the definition includes both the API class and the instance name as the parameters which are being passed. Notice the way I access the proprty_id field value using propQry.value(0).toString(), for example. I know that .value(0) refers to the proprty_id field, .value(1) is county, .value(2) is cntyname, etc. I chose this method because it I know the index values for the fields in the record of propQry. I know what you are thinking: "How do I use field names instead of indexes?" Trolltech explains their reasoning for using the value()'method in their Qt Assistant, which is their API documentation:

To access the data returned by a query, use value(int). Each field in the data returned by a SELECT statement is accessed by passing the field's position in the statement, starting from 0. This makes using SELECT * queries inadvisable because the order of the fields returned is indeterminate. For the sake of efficiency, there are no functions to access a field by name (unless you use prepared queries with names, as explained below). To convert a field name into an index, use record().indexOf(), for example:
QSqlQuery query("SELECT * FROM artist");
int fieldNo = query.record().indexOf("country");
while (query.next()) {
   QString country = query.value(fieldNo).toString();
   doSomething(country);
}

If the actual position of a field in a select, starting with zero, is used ...value(n)... works faster. Also notice that not all the propQry.value(0). commands end with toString(). One that I use ends with toDateTime(). Why do you need data type assignments at the end of a value method? Because data from a query is a QVariant type. Again I quote from the Qt Assistant:

The QVariant class acts like a union for the most common Qt data types. Because C++ forbids unions from including types that have non-default constructors or destructors, most interesting Qt classes cannot be used in unions. Without QVariant, this would be a problem for QObject::property() and for database work, etc. A QVariant object holds a single value of a single type() at a time. (Some type()s are multi-valued, for example a string list.) You can find out what type, T, the variant holds, convert it to a different type using convert(), get its value using one of the toT() functions (e.g., toSize()) and check whether the type can be converted to a particular type using canConvert().

So now you know everything I know about QVariant objects.

Notice the use of text() and setText(). Data is usually obtained from text boxes on the screen using the text() method and is sent to text boxes on the screen using the setText() method.

Also, notice the way the cboCountyName combo box is set to the value returned from the propQry:

ui.cboCountyName->setCurrentIndex(ui.cboCountyName->findText(propQry.value(2).toString()));

Since the highlighted() signal won't be triggered by programmatically setting the county name the setting for the county number is made by:

ui.leCountyNumber->setText(propQry.value(1).toString());

Loading a Grid With Values

The data from a query often needs to be displayed in a grid. displayPersGrid() does that.

void Homestead::displayPersGrid( QSqlQuery persQry ){
   QSqlQueryModel *model = new QSqlQueryModel(ui.gridPersInfo);
   model->setQuery(persQry);
   if (model->lastError().type() != QSqlError::NoError)
      ui.leStatus->setText(model->lastError().text());
   else
      ui.gridPersInfo->setModel(model);
}

The QTableView widget, ui.gridPersInfo, is passed as the parent to QSqlQueryModel and the new command is used to create an instance of QSqlQueryModel called model. Then, persQry is bound to the model instance by its own setQuery() method. If no error is generated by binding the query to the model, the model is then bound to gridPersInfo with the setModel() function using model as the parameter.

Picking Values from Cells in a Grid

Sometime you may want to use the information displaying in cells of a grid. For example: the persInfoGrid may be displaying three applicants for a property. When a property record is first displayed the information for the first applicant showing in the grid is displayed in the PersInfo tab. You want to be able to display another applicant's data by clicking on their SSN or Wholename in the persInfoGrid. In the private slots: section of the Homestead.h header file is the following declaration:

void displaySelectedPersInfo(const QModelIndex & );

In the Homestead.cpp file is the connect code related to the previously mentioned slot declaration:

connect(ui.gridPersInfo,SIGNAL(clicked(QModelIndex)),this,SLOT(displaySelectedPersInfo(QModelIndex)));

When the grid is clicked it emits a signal with the QModelIndex, which is passed to the slot.

Those two snippets of code make possible the following function:

void Homestead::displaySelectedPersInfo(const QModelIndex &QMI ){
   // display property depending on which row and column of
   // ui.gridPersInfo is clicked.
   QString sRec = "not valid column";
   QVariant value = ui.gridPersInfo->model()->data(QMI,0);
   if (value.isValid()) {
      if (QMI.column() == 2) ui.rbWholeName->click();
      if (QMI.column() == 1) ui.rbSSN->click();
      if (QMI.column() == 0 ) ui.rbProprtyID->click();
      if (QMI.column() == 0 || QMI.column() == 1 || QMI.column() == 2 {
         sRec = value.toString();
         ui.leSearch->setText(sRec);
         ui.btnSearch->click();
      } else {
         ui.leStatus->setText(sRec);
      }
   }
}

The declaration parameter is const QModelIndex &, which says that the reference to a constant QModelIndex instance will be passed to displaySelectedMultiProp(). The variable QMI contains row, column and data information. The function uses that information to set radio buttons and load the search text box with the information to query on. The string, sRec, is used to set leStatus, as in prior examples. The contents of the cell clicked on is retrieved using:

QVariant value = ui.gridPersInfo->model()->data(QMI,0);

and is a QVariant data type, if it is valid, which the next line of code test. Then, based on the column number, one of the radio buttons is clicked. This tells the search button what kind of data will be entered into the search text box. Then, if the column is either 0, 1 or 2, the string of the value is stored in sRec. Value itself is not changed. The contents of sRec is then moved to the leSearch text box and the btnSearch button is clicked. Otherwise, an error message in sRec is moved to the leStatus text box.

Pointers, References and Values

The displaySelectedPersInfo() method has a parameter which is passed by reference, that is, the memory address of the parameter is being passed, not its value. In C++ the symbol "&" designates a reference operator, and the memory address of QMI is being passed to the function. This is an example of passing by reference. When a function contains a parameter of the form somefunction(type var) then var is being passed by value. If the form is somefunction(type &var) then address of var is being passed, or var is being passed by reference.

One C++ reference I read stated:

A pointer parameter is similar to a reference parameter in that it points to an object of a specified type. However, pointer parameters require YOU to specify the object's address when calling the function. Reference parameters do so automatically,

which I am not sure I understand. But, much of the following discussion on pointers is a distillation of what I've read on the subject, expressed in my own words. If you see a mistake then it is my misunderstanding. If you read something familiar then we probably read the same source.

I've already discussed the new and delete operators, and how they are used to create variables. Now is the time to show how pointers are defined. A pointer is defined using the dereference operator: "*". In your pass programming experiences using linked lists, for example, you may have needed to use indirect referencing, which is using a variable to hold not a value, but the name of another variable. Pointers are like that. They don't hold values, but they hold the address where the value resided. Change the address in a pointer and you have changed value the pointer points to. Many times pointers point to complex objects, like arrays or structures, which are dynamic in nature, so their size or structure is constantly changing. Because of this, pointers are most often declared on the heap, not the stack. In other words, the new operator is used in their definition. Here is a simple example:

double *dp = new double;

That statement actually performs three distinct actions:

  • it declares a pointer named dp to a double object,
  • it uses new to allocate enough memory on the heap to hold one double object,
  • it assigns to dp the address of the allocated memory, and to *dp the data value.

Following the statement, dp points to a double object in memory. Stored in dp is the address of the first byte of that memory. Like an arrow pointing to an office in a building, a C++ pointer refers, or points, to an object in memory. Exactly where in the memory the object's bytes are allocated is unimportant. You don't have to declare or initialize a pointer in one step. You can declare the pointer ahead of time, and then elsewhere use new to allocate memory and assign its address to the pointer. This is a common technique for creating a dynamic object, demonstrated by this code fragment:

double *dp;
...
dp = new double;

Merely declaring the pointer in the first statement does not allocate memory for a double object. That takes place later in the statement that uses new. Until that second statement executes, the pointer dp is uninitialized, analogous to an arrow in an unfinished building pointing to a room not yet constructed. Following an arrow to a non-existent room could be hazardous to your health, and using an uninitialized pointer will cause a serious bug in your program. If enough memory is not available new throws an exception which, if not handled, abends the program. Refer to throw, try and catch. They are the C++ operators for handling exceptions, and are similar to their Java counterparts.

If there is one issue in C++ that confuses newbies it is the difference between *dp and dp. First, dp alone is not a double object. It is a pointer to a double object. To use dp as a double object requires dereferencing the pointer with the * operator. This statement, for example, assigns a value to the double object to which dp points:

*dp = 3.14159;

That copies the value 3.14159 into the location in memory addressed by dp. You might think of the dereference operator, *, as a conduit that creates a channel to the addressed memory location. Using the * operator is like following an arrow to its destination. Not using the dereferencing operator causes a compilation error:

db = 3.14159; // ??? This will error out when compiling

That doesn't compile because dp is a pointer. It holds a memory address, and 3.14159 is not a memory address value. That's the meaning of the second statement:

dp = new double;

The new operator fetches the address of an available location in the heap memory and stores it in dp. To store a value of a variable or literal of the same type at that address you must dereference it using *dp. Don't forget, only the address of the first byte of the allocated memory is stored in dp. The C++ compiler knows how to access and use the remaining bytes of the allocated memory. Also, you can now clearly see why the original statement:

double *dp = new double;

has created both the dp pointer and the dereferenced object, *dp, which hold the actual data.

You are responsible for deleting your objects. When we are finished using dp we delete it like this:

delete dp;
dp = 0;

Or, you can set the pointer to the predefined constant NULL:

delete dp;
dp = NULL;

Note that if dp is already 0, deleting it does no harm, so it is not necessary to test whether dp equals 0 before applying the delete operator. By resetting a deleted pointer to 0, other statements can test whether dp addresses an object in memory. For example, the following if statement assigns a value to the dynamic object only if dp is not null:

if (dp != 0)
   *dp = 3.14159;

You will often see statements like:

if (dp)
   *dp = 3.14159;

The expression (dp) equal the value of dp, so if dp is zero, the expression (dp) is equivalent to false. But, be aware that some C++ compilers make a distinction between zero and NULL.

Memory Leaks

Dynamic objects that are not deleted before they lose scope become memory leaks. A memory leaks is a condition where the memory used by an object is not returned to the heap because the pointer to that address has lost scope and is no longer visible to the program. The most common cause of memory leaks are poorly written functions like:

void f() {
   double *dp = new double;
   // ... other statements
   return; // memory leak here!
}

The correct way to write this function is:

void f() {
   double *dp = new double;
   // ... other statements
   delete dp;
   return;
}

Now the function properly deletes the allocated memory before going out of scope (returning). When you need a function to allocate memory and not delete it, one solution is to return the pointer as the function result:

double *f() {
   double *dp = new double;
   *dp = 3.14159;
   return dp;
}

There is no memory leak here because the function preserves the address of the allocated memory by returning it as the function result. Elsewhere in the program, a statement can declare a double pointer and call f() like this:

double *dynamic_dp = f();

What's going on here? Isn't dp a pointer which holds a memory address, and doesn't *dp hold data? Yes. So, how can f() return a pointer, dp, which then gets assigned to *dp, which holds values and not memory addresses? Well, f() is returning an address just like new double; does, and it is assigned to dp. However, recall in double *dp = new double;, we have *dp seemly being assigned an address by = new double;. Reviewing the three things this statement does, we see that one of them is creating the pointer db, in addition to creating *db, so two objects are created and dp holds the first byte of the memory address where the value for *db can be stored.

So, stated in another way, double *dp = new double; says that a pointer to a double object is created. dp is the pointer. What object holds the memory address? dp. What does dp point to? *dp. What object holds the data? *dp. When I return dp to a pointer declaration dp is behaving just like new double; and the three rules of pointer creation still apply.

The function f() allocates memory space for a double value, assigns 3.14159 to that object, and returns the pointer, which the program assigns to dynamic_dp, a pointer to *dynamic_dp. This plugs the leak, but the memory must still be deleted at some point, using the following statement:

delete dynamic_dp;

Another way is to declare *dp as global in the class constructor, or in main(), and put dp = new double; in some other function where access to dp is required. I use this method in Homestead for the QSqlQuery objects.

Other Pointer Constructs

The const keyword can be used to control pointer declarations:

const int result = 2;

states that result is a constant, so

result = 3; // illegal

is an illegal statement, given the declaration. Consider the following declaration:

const char *somestr = "Qt Widget set";

which says that the data pointed to by somestr is a constant, but somestr is not a constant! So, while somestr = &someotherstr; is legal, *somestr = "a different string"; is not legal.

What happens when you put const after the deference operator(*)? Given:

char * const somestr = "some string";

This construct says that somestr is a constant pointer that cannot be changed, but the data pointed to by it can be changed. So, somestr = &someotherstr; is illegal, but *somestr = "some other string"; is legal.

Taking this a step farther, in:

const char * const somestr = "some string";

we have a construct which creates a pointer (which cannot be changed) to a data item that cannot be changed.

Pointers are, in C++, the equivalent of arrays. The following construct creates a dynamic pointer to a character array:

char *astr = new char[10];

which is a string that hold up to 9 characters plus a string terminating null character. The following assignment, *astr = "abcdefghi";, is legal but *astr = "abcdefghij" is not. Also, note that when the astr variable is destroyed the following construct should be used:

delete[] astr;

which is a special form of the delete operator. The empty brackets informs the compiler that astr addresses an array. However, other types of arrays might require special handling during deletion so it's a good idea to use delete[] to delete dynamic arrays. If you were expecting astr to be a variable that would hold 10 strings you now understand the meaning of char, a holder of one character. An array of characters is a single string. To create a 10 character array of 15 strings the construct is:

char **astringarray = new char[10][15];

These more complex pointers and arrays are best made using Qt objects, not the C++ API, but I introduced them for their illustrative value. In the Homestead constructor is an example of a QObject string creation and use:

QStringList msList;
msList << "Single" << "Married" << "Widow" << "Closely Related";
ui.cboMS->addItems( msList );

Because QStringList was used, the delete[] command isn't necessary and msList will be destroyed automatically when the program closes. Although I never used it, consider using the Qt object QByteArray when you need a char array. It is a QMemArray<char> subclass.

Other examples from Homestead follow.

More Examples from Homestead

On the Utilities tab of Homestead is the btnCalcAllProperty button with the title "Calc ALL Property Info". Here is the connect statement in the Homestead constructor function:

connect(ui.btnCalcAllProperty,SIGNAL(clicked()),this,SLOT(CalcAllProperties()));

Therefore, clicking the button calls the CalcAllProperties() slot:

void Homestead::CalcAllProperties(){
       AllPropQry.prepare("SELECT proprty_id FROM PROPERTY");
       AllPropQry.exec();
       if (AllPropQry.isActive()){
               QDateTime dtNow = QDateTime::currentDateTime();
               int propertyid;
               int curcnt = 0;
               QString sCnt;
               bool updateOK;
               while (AllPropQry.next()) {
                      propertyid = AllPropQry.record().value(0).toInt();
                      // false means don't update property form with data
                      updateOK = calcPropRow(propertyid, dtNow, false);
                      curcnt += 1;
                      sCnt = "";
                      sCnt.append(itos(curcnt));
                      ui.lblPropertyCount->setText(sCnt);
                      ui.lblPropertyCount->repaint();
                      // put keyboard escape in here to avoid infinite loop lockout??
               } // while AllPropQry next
               AllPropQry.clear();
       } //if (AllPropQry.isActive()
}

Within the middle of that method is a call to another one, calcPropRow(). Here it is:

bool Homestead::calcPropRow(int propertyid, QDateTime dtDT, bool updateForm ) {
       // set good_app, totinc, highhec, highms for this property
       QString appstatus = "";
       QString offuse = "";
       double totinc = 0.0;
       float tytotinc = 0.0;
       int pctext = 0;
       int highhec = 1;
       int highms = 1;
       bool good_app = false;
       PropPersInfo.prepare("SELECT \
               hec, \
               ms, \
               totincome, \
               appstatus, \
               app_type \
               FROM persinfo WHERE proprty_id = " + itos(propertyid));
       PropPersInfo.exec();
       if (PropPersInfo.isActive()){
            while (PropPersInfo.next()){
                 //QMessageBox::information(this,"Homestead appstatus",
                   PropPersInfo.record().value(3).toString());
               if (PropPersInfo.record().value(3).toString() == "OK") {
                       good_app = true;
               }
               totinc = totinc + PropPersInfo.record().value(2).toDouble();
               //QMessageBox::information(this,"Homestead totinc",ftois(tytotinc));
               if (highhec < PropPersInfo.record().value(0).toInt()) {
                       highhec = PropPersInfo.record().value(0).toInt();
               }
               if (highhec == PropPersInfo.record().value(0).toInt()){
                       if (PropPersInfo.record().value(1).toInt() > 1) {
                               highms = 2;
                       } else {
                               highms = 1;
                       }
               }
            } // while (PropPersInfo.next())
       } // if (PropPersInfo.isActive())
       // All rows processed, now compute property values.
       if (highms > 1) {
               offuse = "M-";
       } else {
              offuse = "S-";
       }
       offuse.append(itos(highhec));
       totinc = floor(totinc + 0.5);
       tytotinc = (float) totinc;
       if (highhec == 5) {
              offuse = "VA";
              pctext = 100;
              appstatus = "OK";
       } else {
              if (good_app) {
                      pctext = calcPctExm(highms, highhec,tytotinc);
                      if (pctext > 0) {
                              appstatus = "OK";
                      } else {
                              appstatus = "HINC";
                      }
              } else {
                      appstatus = "NONE QUAL";
              }
       }
       // update property record values
       propQry.prepare("UPDATE property SET \
                              entry_date = :entry_date, \
                              appstatus = :appstatus, \
                              offuse = :offuse, \
                              totincome = :tytotinc, \
                              pctext = :pctext \
                              WHERE proprty_id = :propertyid");
       propQry.bindValue(":entry_date",dtDT);
       propQry.bindValue(":appstatus",appstatus);
       propQry.bindValue(":offuse",offuse);
       propQry.bindValue(":totinc",tytotinc);
       propQry.bindValue(":pctext",pctext);
       propQry.bindValue(":proprty_id",propertyid);
       if (!propQry.exec()) {
           QMessageBox::information(this,"Homestead", propQry.lastError().text());
           return false;
       };
       if (updateForm) {
              // if called from property form then update form values
              ui.dteEntryDate->setDateTime(dtDT);
              ui.leAppStatus->setText(appstatus);
              ui.leTYOffUse->setText(offuse);
              ui.leTYTotalIncome->setText(ftois(tytotinc));
              ui.leTYPctExemption->setText(itos(pctext));
       }
       return true;
}

I have shown this function and its child function because they illustrate the method I used to update data in an Oracle table. It is an example of a lot of smaller queries operating within a much larger query. First, calcAllProperties() creates a query containing all of the proprty_id values in the Property table:

AllPropQry.prepare("SELECT proprty_id FROM PROPERTY");
AllPropQry.exec();

Then, some counters, a boolean and a timestamp is initialized and the AllPropQry is cycled through:

QDateTime dtNow = QDateTime::currentDateTime();
int propertyid;
int curcnt = 0;
QString sCnt;
bool updateOK;
while (AllPropQry.next()) {

Then, for each record in Property, the proprty_id value is extracted so that the related PersInfo records for each Property record can, within calcPropRow(), be retrieved, eligibility evaluated and incomes totaled, and the results posted into the property table:

   propertyid = AllPropQry.record().value(0).toInt();
   // false means don't update property form with data
   updateOK = calcPropRow(propertyid, dtNow, false);
   curcnt += 1;
   sCnt = "";
   sCnt.append(itos(curcnt));
   ui.lblPropertyCount->setText(sCnt);
   ui.lblPropertyCount->repaint();
   // put keyboard escape in here to avoid infinite loop lockout??
} // while AllPropQry next

Using variables passed to it by value, calcPropRow() initializes some values and then fetches each set of records from PersInfo for the current proprty_id value passed by value to it from the CalcAllProperties() function:

PropPersInfo.prepare("SELECT \
       hec, \
       ms, \
       totincome, \
       appstatus, \
       app_type \
       FROM persinfo WHERE proprty_id = " + itos(propertyid));
PropPersInfo.exec();
if (PropPersInfo.isActive()){
       ...

The totals for a property's applicants are accumulated. The values to be posted to the Property table are computed. Then, the Property record for proprty_id is updated with the results:

// update property record values
propQry.prepare("UPDATE property SET \
        entry_date = :entry_date, \
        appstatus = :appstatus, \
        offuse = :offuse, \
        totincome = :tytotinc, \
        pctext = :pctext \
        WHERE proprty_id = :propertyid");
propQry.bindValue(":entry_date",dtDT);
propQry.bindValue(":appstatus",appstatus);
propQry.bindValue(":offuse",offuse);
propQry.bindValue(":totinc",tytotinc);
propQry.bindValue(":pctext",pctext);
propQry.bindValue(":proprty_id",propertyid);
if (!propQry.exec()) {
        QMessageBox::information(this,"Homestead", propQry.lastError().text());
        return false;
};

The QSqlQuery::bindValue() method is used to link the Property field names with the computed values.

if (updateForm) {
         // if called from property form then update form values
         ui.dteEntryDate->setDateTime(dtDT);
         ui.leAppStatus->setText(appstatus);
         ui.leTYOffUse->setText(offuse);
         ui.leTYTotalIncome->setText(ftois(tytotinc));
         ui.leTYPctExemption->setText(itos(pctext));
}
return true;

If the updateForm boolean is true the calcPropRow() method was called from the screen while setting on a particular Property record and the data showing on the screen for that record will be updated. If it is false the function was called from the Utilities button, which updates all records in the Property table. In that case we don't want the screen to be updated for each Property record. That would needlessly slow down the task.

Creating and Saving External Files

The following function illustrates how I created and saved an external HTML file.

void Homestead::printDateBtchRpt() {
        //ui.leStatus->setText("Printing Date Batch Report...");
        QString sBDate = ui.leBRDATE->text();
        QString dbrStr = "SELECT persinfo.*, property.county FROM persinfo, property WHERE
                         property.proprty_id = persinfo.proprty_id AND persinfo.bdate = '";
        dbrStr.append(sBDate);
        dbrStr.append("' order by persinfo.batch, persinfo.rpt" );
        ui.leStatus->setText(dbrStr);
        QSqlQueryModel *BRmodel = new QSqlQueryModel;
        BRmodel->setQuery(dbrStr);
        if (BRmodel->rowCount() > 0 ){
            // HTML reports solves the problem of creating a report for viewing and
            // another report for printing. Problems formatting for the printer are
            // avoided.
            QFile file("bdate_report.html");
            if (!file.open(QIODevice::WriteOnly | QIODevice::Text)){
                ui.leStatus->setText("Can't open 'bdate_report.html' for output");
                return;
            }
            QTextStream out(&file);
            int batchcnt = 0;
            int appcnt = 0;
            int inccnt = 0;
            long inctot = 0.0;
            int oldbatch = BRmodel->record(0).value("batch").toInt();
            // this->HTML_Header1 is idential to Homestead::HTML_Header1
            out << this->HTML_Header1 << "Homestead Batch Date Report";
            out << this->HTML_Header2 << "\n";
            out << "<TABLE width=100% align=center><TR><TH>Homestead 2005 Batch Report for BDate: ";
            out << sBDate << "</TH></TR></TABLE>" << "\n";
            out << "<TABLE width=100%><TR align=left><TH>Batch No: " << oldbatch << " Clerk: ";
            out << BRmodel->record(0).value("entint").toString() << "</TH></TR></TABLE>" << "\n";
            out << "<TABLE width=100%><TR align=left><TH>Prop ID</TH><TH>SSN</TH>";
            out << "<TH>NAME</TH><TH>Rpt</TH><TH>Inc/Type</TH>";
            out << "<TH>HEC</TH><TH>Prt</TH><TH>MS</TH>";
            out << "<TH>Income</TH><TH>Stat</TH><TH>BDate</TH></TR>" << "\n";
            for (int row = 0; row < BRmodel->rowCount(); ++row){
            if (BRmodel->record(row).value("batch").toInt() != oldbatch){
                // print old batch totals
                appcnt += batchcnt;
                inctot += inccnt;
                out << "</TABLE><TABLE width=100%><TR><TH>Count of Applicants: "<< appcnt ;
                out << " Batch Count: " << batchcnt << "    Count of Incomes: " << inccnt;
                out << "</TH></TR>" << "\n";
                out << "<TR> <TH> ___________________________________________________";
            out << "__________________________</TH></TR></TABLE>";
            // setup for new batches
            oldbatch = BRmodel->record(row).value("batch").toInt();
            batchcnt = 0;
            inccnt = 0;
            out << "<TABLE width=100%><TR align=left><TH>Batch No: " << oldbatch ;
            out << " Clerk: " ;
            out << Brmodel->record(row).value("entint").toString();
            out << "</TH></TR></TABLE>" << "\n";
            out << "<TABLE width=100%><TR align=left><TH>Prop ID</TH><TH>SSN</TH>";
            out << "<TH>NAME</TH><TH>Rpt</TH><TH>Inc/Type</TH>";
            out << "<TH>HEC</TH><TH>Prt</TH><TH>MS</TH><TH>Income</TH> <TH>Stat</TH>";
            out << "<TH>BDate</TH></TR>" << "\n";
        } // end batch test
        batchcnt += 1;
        // print a persinfo record
        out << "<TR width=100%><TD>" << Brmodel->record(row).value("proprty_id").toString();
        out << "</TD><TD>";
        out << BRmodel->record(row).value("ssn").toString() << "</TD><TD>";
        out << BRmodel->record(row).value("name").toString() << "</TD><TD>";
        out << BRmodel->record(row).value("rpt").toString() << "</TD><TD>" ;
        out << BRmodel->record(row).value("app_type").toString() << "</TD><TD>";
        out << BRmodel->record(row).value("hec").toString() << "</TD><TD>";
        out << BRmodel->record(row).value("part").toString() << "</TD><TD>";
        out << BRmodel->record(row).value("ms").toString() << "</TD><TD>";
        out << BRmodel->record(row).value("totincome").toString() << "</TD><TD>";
        out << BRmodel->record(row).value("appstatus").toString() << "</TD><TD>";
        out << BRmodel->record(row).value("bdate").toString() << "</TD></TR> " << "\n";
        if (BRmodel->record(row).value("totincome").toDouble() > 0.0)
            inccnt += 1;
        } // end for loop
        out << "</TABLE>" << "\n";
        appcnt += batchcnt;
        inctot += inccnt;
        // print grand totals
        out << "<TABLE width=100%>" << "<TR><TH align=center>Count of Applicants: " << appcnt ;
        out << " Batch Count: " << batchcnt << "    Count of Incomes: " << inccnt ;
        out << "</TH></TR> " << "\n";
        out << "<TR><TH align=center>________________________________________________";
        out << "_______________________________ </TH></TR>";
        out << "<TR></TR>" << "<TR><TH align=center>Total Applicant Count: " << appcnt ;
        out << " Total Incomes: ";
        out << inctot << this->HTML_Footer << "\n";
        file.close(); // close the html file
        // below is the hardcoded approach to accessing the html file for printing
        // the method 'printBatchBatchRpt shows how to a use dynamic location method
        // using the '/' separator eliminates having to use '\\' to escape the '\' char
        QString sIE = "C:/Program Files/Internet Explorer/IEXPLORE.EXE";
        QStringList sRpt; // required argument for QProcess execute
        sRpt << "E:/wc_qt4/Homestead/bdate_report.html";
        QProcess::execute(sIE, sRpt); // a simple string can't replace sRpt
    } else {
        ui.leStatus->setText(sBDate.prepend("No records found for: "));
  }
}

This is the usual slicing and dicing of HTML tags with application data, but a couple of items need to be pointed out. They are the following Qt API classes: QFile, QTextStream and QProcess. QFile is the Qt class used for creating external files. QTextStream is the Qt API class that allows streaming to and from files created with QFile. QProcess allows access to external executables which can run independently from your application.

Here is the code which creates the external file, in this case an text file:

QFile file("bdate_report.html");
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)){
    ui.leStatus->setText("Can't open 'bdate_report.html' for output");
    return;
}
QTextStream out(&file);

A variable containing the file name, file, is created. The file is only created with this command, it is not opened. Since file is a Qt object it has methods and properties. file.open(...) actually opens the file for processing. Now a conduit to file needs to be created, and that is what QTextStream out(&file); does. The conduit is an object called out and the file name is passed by reference, that is, the address of the file object is passed to out. All of the out << "some stuff"; commands are funneling data to the file through out. Notice the use of "\n" to send a line feed and carriage return to file. When the streaming is complete the file is closed with the file.close() method.

Following the closing of the file the remainder of the code is devoted to causing a web browser to open up and run the HTML code that was created. A QString containing the path to and the name of the web browser is created:

QString sIE = "C:/Program Files/Internet Explorer/IEXPLORE.EXE";

Then the specific directory and file is assigned to sRpt:

QStringList sRpt; // required argument for QProcess execute
sRpt << "E:/wc_qt4/Homestead/bdate_report.html";

And the web browser is executed, using the HTML file as the target:

QProcess::execute(sIE, sRpt); // a simple string can't replace sRpt

Note that sRpt can not be a simple QString.

A second HTML report generator uses a dynamic method of locating the current working directory by using the Qt object QDir, which eliminates the dependency of specific hard drive and pathway designations:

QDir qdWDir;
QString sWDir = qdWDir.absolutePath();
sWDir.append("/batchbatch_report.html");
QProcess qpWDir;
qpWDir.setWorkingDirectory(sWDir);

However, so far I have not been able to create a dynamic method for locating the browser executable. Instead, it relies on Internet Explorer being in a specific directory location. Also, using IE makes the code platform dependent and requires changing the browser to Konqueror or FireFox before compiling on a Linux platform. Another way to use QProcess is the following, which opens a PDF file using Acrobat reader:

QProcess *p = new QProcess();
p->start("C:/Program Files/Adobe/Acrobat 7.0/Reader/AcroRd32.exe",
QStringList() << "C:/a.pdf");

You can use the QFileInfo object to determine if a file exists. Prompt for "filename" and pass it to the QFileInfo object:

QFileInfo info(filename);
if (info.exists())
   .... your code...

I found this nifty little example of cross platform code on the QtCentre forum, but I haven't tried it:

bool open_browser(QWidget* parent, const QString& rUrl)
{
    bool result = false;
    QApplication::setOverrideCursor(Qt::BusyCursor);
#ifdef Q_WS_WIN
    result = int(ShellExecuteW(parent->winId(), 0, rUrl.ucs2(), 0, 0, SW_SHOWNORMAL)) > 32;
#else
    Q_UNUSED(parent);
    // Try a range of browsers available on UNIX, until we (hopefully)
    // find one that works. Start with the most popular first.
    QProcess process;
    bool process_started = false;
    process.setArguments(QStringList() << "konqueror" << rUrl);
    process_started = process.start();
    if (!process_started)
    {
        process.setArguments(QStringList() << "mozilla" << rUrl);
        process_started = process.start();
    }
    if (!process_started)
    {
        process.setArguments(QStringList() << "firefox" << rUrl);
        process_started = process.start();
    }
    if (!process_started)
    {
        process.setArguments(QStringList() << "netscape" << rUrl);
        process_started = process.start();
    }
    result = process_started;
#endif
    QApplication::restoreOverrideCursor();
    return result;
}

It would require that Q_WS_WIN be defined as a flag that is passed to the C++ compiler via modifications in either the project file or the Makefile. Would _WIN32 work as well?

Message Boxes and Modal Dialogs

On occasions it is necessary to inform the user of some event or query the user for additional information. The informational message box is easy to implement:

QMessageBox::information(this,"Homestead",queryStr.right(20));

In this example I am throwing up a dialog message box which is owned by this, has a title of "Homestead" and displays the last 20 characters of the queryStr string.

In another example:

QMessageBox::information(this, "Property MAX select", sNewPropID,
                         QMessageBox::Ok, QMessageBox::NoButton, QMessageBox::NoButton);

the value, sNewPropID, is being shown to the user. When buttons are required all three possibilities must be presented, but in this case only the "Ok" button is shown. The other two button positions are flagged as "NoButton" and won't show. Let's look at the Qt API to learn about the QMessageBox class. In the Qt Trolltech Menu group is an entry known as the "Assistant". Among other things it lists the Qt API. On the "Main Classes" page the "QMessageBox" entry can be found. Here is what the part of the first page of that API class contains:

QMessageBox Class Reference
The QMessageBox class provides a modal dialog with a short message, an icon, and some buttons.
...
#include <QMessageBox>
...
Inherits QDialog.
...
Public Types
  • enum Button { Ok, Cancel, Yes, No, ..., NoButton }
  • enum Icon { NoIcon, Question, Information, Warning, Critical }
Properties
  • icon : Icon
  • iconPixmap : QPixmap
  • text : QString
  • textFormat : Qt::TextFormat
  • 2 properties inherited from QDialog
  • 52 properties inherited from QWidget
  • 1 property inherited from QObject
Public Functions
  • QMessageBox ( QWidget * parent = 0 )
  • QMessageBox ( const QString & caption, const QString & text, Icon icon, int button0, int button1, int button2, QWidget * parent = 0, Qt::WFlags f = Qt::Dialog | Qt::MSWindowsFixedSizeDialogHint )
  • ~QMessageBox ()
  • QString buttonText ( int button ) const
  • Icon icon () const
  • QPixmap iconPixmap () const
  • void setButtonText ( int button, const QString & text )
  • void setIcon ( Icon )
  • void setIconPixmap ( const QPixmap & )
  • void setText ( const QString & )
  • void setTextFormat ( Qt::TextFormat )
  • QString text () const
  • Qt::TextFormat textFormat () const
  • 9 public functions inherited from QDialog
  • 183 public functions inherited from QWidget
  • 28 public functions inherited from QObject
  • 10 public functions inherited from QPaintDevice
Static Public Members
  • void about ( QWidget * parent, const QString & caption, const QString & text )
  • void aboutQt ( QWidget * parent, const QString & caption = QString() )
  • int critical ( QWidget * parent, const QString & caption, const QString & text, int button0, int button1, int button2 = 0 )
  • int critical ( QWidget * parent, const QString & caption, const QString & text, const QString & button0Text = QString(), const QString & button1Text = QString(), const QString & button2Text = QString(), int defaultButtonNumber = 0, int escapeButtonNumber = -1 )
  • int information ( QWidget * parent, const QString & caption, const QString & text, int button0, int button1 = 0, int button2 = 0 )
  • int information ( QWidget * parent, const QString & caption, const QString & text, const QString & button0Text = QString(), const QString & button1Text = QString(), const QString & button2Text = QString(), int defaultButtonNumber = 0, int escapeButtonNumber = -1 )
  • int question ( QWidget * parent, const QString & caption, const QString & text, int button0, int button1 = 0, int button2 = 0 )
  • int question ( QWidget * parent, const QString & caption, const QString & text, const QString & button0Text = QString(), const QString & button1Text = QString(), const QString & button2Text = QString(), int defaultButtonNumber = 0, int escapeButtonNumber = -1 )
  • QPixmap standardIcon ( Icon icon )
  • int warning ( QWidget * parent, const QString & caption, const QString & text, int button0, int button1, int button2 = 0 )
  • int warning ( QWidget * parent, const QString & caption, const QString & text, const QString & button0Text = QString(), const QString & button1Text = QString(), const QString & button2Text = QString(), int defaultButtonNumber = 0, int escapeButtonNumber = -1 )

There are two public types: six enumerated buttons and five enumerated icons. One of the buttons is an ellipsis. In my example an "Ok" button was activated by the following code: QMessageBox::Ok. The two other button possibilities were flagged as QMessageBox::NoButton. How do I know that there are only three button possibilities? Because the first class override lists them in the 4th, 5th and 6th positions as:

int button0, int button1, int button2,

A description of the first override of QMessageBox is given as:

Constructs a message box with a caption, a text, an icon, and up to three buttons.
The icon must be one of the following:
  • QMessageBox::NoIcon
  • QMessageBox::Question
  • QMessageBox::Information
  • QMessageBox::Warning
  • QMessageBox::Critical
Each button, button0, button1 and button2, can have one of the following values:
  • QMessageBox::NoButton
  • QMessageBox::Ok
  • QMessageBox::Cancel
  • QMessageBox::Yes
  • QMessageBox::No
  • QMessageBox::Abort
  • QMessageBox::Retry
  • QMessageBox::Ignore
  • QMessageBox::YesAll
  • QMessageBox::NoAll
Use QMessageBox::NoButton for the later parameters to have fewer than three buttons in your message box. If you don't specify any buttons at all, QMessageBox will provide an Ok button.
One of the buttons can be OR-ed with the QMessageBox::Default flag to make it the default button (clicked when Enter is pressed).
One of the buttons can be OR-ed with the QMessageBox::Escape flag to make it the cancel or close button (clicked when Escape is pressed).
   QMessageBox mb("Application Name",
                    "Hardware failure.\n\nDisk error detected\nDo you want to stop?",
                    QMessageBox::Question,
                    QMessageBox::Yes | QMessageBox::Default,
                    QMessageBox::No | QMessageBox::Escape,
                    QMessageBox::NoButton);
   if (mb.exec() == QMessageBox::No) {
          // try again
          ...
   }
If parent is 0, the message box becomes an application-global modal dialog box. If parent is a widget, the message box becomes modal relative to parent.

Notice that if the escape character is embedded within a string it will be executed. So, the sequence \n causes the QMessageBox to emit a carriage return and line feed, and "\n\n" causes two CR's and LF's. To actually cause the escape character to be printed and not executed it must be escaped. So, "\\n" doesn't cause a CR and LF, it causes "\n" to be printed in the message. Also notice that in the first example I gave, from Homestead, the QMessageBox was displayed immediately. In the example supplied in the API description an object, mb was created but is not displayed until an mb.exec() command is issued. How do I know that the exec() method is a member of the QMessageBox class? Well, because the example shows it. But, if there hadn't been an example showing it I could have learned it by following the documentation URL link: List of all members, including inherited members Also, I know that I must reference the exec() method using the "." operator and not the "->" operator because the object mb was created without using the "new" operator:

QMessageBox mb(...);

instead of

QMessageBox *mb = new QMessageBox(...);

Because QMessageBox inherits from QObject I know that when the mb object created by "new" loses focus it will be automatically destroyed by Qt's (provided that it has a parent). In the case where the message box was created in the stack and not the heap (no "new" was used), when the user clicks on one of the buttons an integer value is returned from the mb object which signifies which button was pressed, the then mb object is destroyed.

Another example of a dialog is one created by using the QDialog class. They can be modal or modeless. In Homestead I use two modal dialog classes. One to accept the login information, and one that allows the clerk to choose between applicant's with similar names. The first is called from within the main.cpp file and was explained earlier. The second is called wholenamedlg and is triggered when the clerk enters a partial whole name into the search text box and ends it with the a "%" character. For example: "MILLER/ED%". The searchAll() method looks for a "%" character when it is told to look for a whole name:

if (ui.rbWholeName->isChecked()) {
       //select proprty_id,ssn,wholename,city,address from persinfo_2006 t
       //WHERE wholename LIKE 'MILLER/ED%'
       //ORDER BY wholename
       QString partialName = ui.leSearch->text();
       if (partialName.contains('%')){
               wholeNameDlg dlg(this, partialName, this->dbYear);
               if( dlg.exec() == QDialog::Accepted ){
                      QString strSSN = dlg.resultSSN;
                      ui.leSearch->setText(strSSN);
                      ui.rbSSN->click();
                      ui.btnSearch->click();
                      return;
               } else {
                      ui.leStatus->setText("No partial name found!");
                      return;
               }
} else {
       // searching for an exact wholename match
       ...

To enable this functionality I created a class declaration file, wholenamedlg.h, a class definition file, wholenamedlg.cpp, and the GUI file, wholenamedlg.ui. The class wholenamedlg is not completely abstract, since it defaults specifically to field members of the table Persinfo_2006, so it does not qualify as a generic, drop-in reusable class. Here is wholenamedlg.h:

#ifndef WHOLENAMEDLG_H
#define WHOLENAMEDLG_H
#include <QDialog>
#include <QLineEdit>
#include <QPushButton>
#include <QTableView>
#include <QString>

#include "ui_wholenamedlg.h"

class wholeNameDlg : public QDialog
{
    Q_OBJECT
public:
   wholeNameDlg(QWidget *parent = 0, QString partName = "", QString strYear = "2006");
   QString resultSSN;
   QTableView wholeNameView;

private:
   Ui::wholeNameDlgUI wnui;

private slots:
   void copySSN(const QModelIndex &);
};

#endif

Here is wholenamedlg.cpp:

/*
Program:        wholenamedlg.cpp
Description:    A dialog class for picking a name out of a query
                created by the SQL LIKE % syntax.
Author:         Jerry L Kreps
Date:           11/10/05 - ff
*/

#include "wholenamedlg.h"

#include  <QString>
#include  <QTableView>
#include  <QSqlQueryModel>
#include  <QSqlError>
#include  <QHeaderView>

wholeNameDlg::wholeNameDlg(QWidget *parent, QString partName, QString strYear)
: QDialog(parent)
{
   wnui.setupUi(this);

   connect(this->wnui.wholeNameView, SIGNAL(clicked(QModelIndex)), this, SLOT(copySSN(
QModelIndex )));

  partName.prepend("'");
  partName.append("'");
  QString queryStr = "SELECT proprty_id,ssn,wholename,city,address FROM persinfo_";
  queryStr.append(strYear);
  queryStr.append(" WHERE wholename LIKE ");
  queryStr.append(partName);
  queryStr.append(" ORDER BY wholename");
  QSqlQueryModel *viewModel = new QSqlQueryModel(wnui.wholeNameView);
  viewModel->setHeaderData(0, Qt::Horizontal, "ID");
  viewModel->setHeaderData(1, Qt::Horizontal, "SSN");
  viewModel->setHeaderData(2, Qt::Horizontal, "WholeName");
  viewModel->setHeaderData(3, Qt::Horizontal, "City");
  viewModel->setHeaderData(4, Qt::Horizontal, "Address");
  viewModel->setQuery(queryStr);
  if (viewModel->lastError().type() == QSqlError::NoError){
  wnui.wholeNameView->setModel(viewModel);
    if (viewModel->rowCount() > 0){
       for (int i = 0; i < viewModel->rowCount(); ++i)
           this->wnui.wholeNameView->verticalHeader()->resizeSection(i,20);
       }
    }
}

void wholeNameDlg::copySSN(const QModelIndex &QMI) {
  this->resultSSN = "";
  QVariant value = this->wnui.wholeNameView->model()->data(QMI,0);
  if (value.isValid())
    if (QMI.column() == 1)
             this->resultSSN = value.toString();
}

Below is the wholenamedlgUI graphical interface:

Qt4tut signals3.png

It has three widgets on a window frame. The large widget is a QTableView object with a permanent horizontal scroll bar enabled. The other two widgets are buttons which were part of the dialog object when it was selected in the Qt Designer create option.The connection dialog option on the MSVC Qt menu produces the following display of "connect" options, showing below in pink and blue colors. The two blue ones are for the accept() or reject() slots of the dialog. The lone pink connection is left over from a "double click" experiment and I neglected to delete it after I used the single click feature, which I will describe below.

Qt4tut signals4.png

Notice how information is passed to and from the dialog. The "Cancel" button is connected to the reject() slot, which will cause the "if" test which follows the deployment of the wholnamedlg object to fail, closing the dialog:

if( dlg.exec() == QDialog::Accepted ){

If the "OK" button is clicked the dialog will close and pass the accept() signal to the calling program. Within the if test code the value passed back from the dialog is retrieved by referencing the text box in which it was stored when the SSN value in the grid was clicked, as shown by the connect code:

connect(this->wnui.wholeNameView, SIGNAL(clicked(QModelIndex)), this, SLOT(copySSN( QModelIndex )));

Notice that the void wholeNameDlg::copySSN(const QModelIndex &QMI) definition of copySSN() in wholenamedlg.cpp was declared in wholenamedlg.h as a slot with:

private slots:
      void copySSN(const QModelIndex &);

so the connect statement connects the click signal from the wholeNameView grid with the copySSN() slot, which complete ignores the doubleClicked() connection graphically plotted on the wholenamedlgUI's wholeNameView grid widget. The void means that copySSN() doesn't return anything to the calling function.

Also notice that in wholenamedlg.h there are default values assigned to the parameters passed to wholeNameDlg when it is declared:

public:
      wholeNameDlg(QWidget *parent = 0, QString partName = "", QString strYear = "2006");

and when the function is defined in wholenamedlg.cpp those values can be replaced by specific values:

wholeNameDlg::wholeNameDlg(QWidget *parent, QString partName, QString strYear)
: QDialog(parent)
{
      ...

as passed to it from homestead.cpp by:

QString partialName = ui.leSearch->text();
if (partialName.contains('%')){
      wholeNameDlg dlg(this, partialName, this->dbYear);
      if( dlg.exec() == QDialog::Accepted ){

Here, partialName passes something like "MILLER/ED%" to partName and this->dbYear passes the database schema year, which is appended to the table name in order to select the proper year's data. The variable called resultSSN is a property of wholeNameDlg class.

When the homestead.cpp function, searchALL(), loses focus the dlg object is destroyed.

And, finally, in order for my wholenamedlg.h code to see and build upon my wholeNameDlgUI graphical user interface I must insert #include "ui_wholenamedlg.h" before the class declaration. Where does the header, ui_wholenamedlg.h, come from? It's generated from wholenamedlg.ui by the moc, the Meta-Object Compiler before control is given to the C++ compiler. Remember, the moc takes non-standard QObject code, and its derivatives, and converts it to standard C++ code that the compiler will recognize.




Jerry L. Kreps 18 January 2006

Document license: GPL
Original document was posted here.