The DVD Catalog Application

The DVD Catalog is a simple database application that illustrates how to do the following:

  • Define the structure of the TClientDataSet component

  • Load data from and save TClientDataSet data to XML files

  • Filter items in the database

  • Find a specific item in the database

  • Keep track of changes made to the database

  • Iterate through all records

  • Optimize the size of the database

Defining the Structure of the TClientDataSet

First, you need to drop the TDataSource and TClientDataSet components on the Designer Surface and link them by assigning the TClientDataSet component to the DataSet property of the TDataSource component. You can also rename the TClientDataSet component to CDS to reduce typing.

Now, select the TClientDataSet component on the Designer Surface, select the FieldDefs property in the Object Inspector, and click the (…) button to display the FieldDefs Collection Editor.

image from book
Figure 19-5: The FieldDefs Collection Editor

To add new fields to the table, click the Add New button or press Insert on the keyboard. Since we only need to store the movie's ID and name, click the Add New button twice to create two undefined fields.

image from book
Figure 19-6: New fields

To complete the process of adding new fields to the TClientDataSet component, you need to change several properties of both fields. First, you should rename the first field MovieID and set its DataType property to ftInteger. Then, change the name of the second field to MovieName, set its DataType property to ftString, and, since movie names are sometimes pretty long, assign 100 to its Size property. Finally, close the FieldDefs Collection Editor, right-click the TClientDataSet component on the Designer Surface, and select Create DataSet to create the new dataset. You need to do this if you want to assign the TClientDataSet's fields to data-aware controls at design time, which is what we'll do in a moment.

Creating the Application's User Interface

To create the user interface of the DVD Catalog application displayed in Figure 19-1, you simply need to drop several components on the Designer Surface.

First, drop the TActionManager, TActionMainMenuBar, and TActionToolBar components from the Additional category. The TActionMainMenuBar and TActionToolBar components will automatically align themselves to the top of the form.

Then, drop two TGroupBox components from the Standard category, add a TEdit and a TListBox to the left TGroupBox component, and add two data-aware TDBEdit components to the right TGroupBox component. Finally, add as many TLabel components as you need to describe the purpose of these text boxes (see Figure 19-7).

image from book
Figure 19-7: The application's user interface

Finally, drop a TStatusBar component from the Win32 category on the Designer Surface and set its SimplePanel property to True. Also, connect the data-aware components in the right TGroupBox with the TDataSource component, assign the MovieID field to the DataField property of the first TDBEdit, and assign the MovieName field to the DataField property of the second TDBEdit.

Utility Methods

Before adding actions to the TActionManager component, we need to create three utility methods for loading and saving data and for displaying all movie names in a TListBox. First, here's the DisplayDataset method that displays all movie names in a TListBox component:

procedure TMainForm.DisplayDataset; begin   MovieList.Clear;   MovieList.Items.BeginUpdate;   CDS.First;   while not CDS.Eof do   begin     MovieList.Items.Add(CDS.FieldByName('MovieName').AsString);     CDS.Next;   end;   MovieList.Items.EndUpdate;   if MovieList.Items.Count = 0 then     StatusBar.SimpleText := 'The disc catalog is empty.'   else     StatusBar.SimpleText := 'Discs in catalog: ' +       IntToStr(MovieList.Items.Count); end; 

As you can see, the DisplayDataset method is pretty simple because its only job is to loop through all records in the TClientDataSet and copy anything stored in the MovieName field to the TListBox component.

To begin copying the data from the MovieName field, the DisplayDataset method calls the TClientDataSet's First method to move to the first record. Then it enters the while not Eof loop, which loops through all records in the TClientDataSet. The FieldByName method is used to find the MovieName field in the active record and then, in order to add the value of the MovieName field to a TListBox, the field's AsString property is used to treat the field's value as a string. Finally, the while loop calls the TClientDataSet's Next method, which moves to the next record in the table and makes it the active record.

The OpenCatalog and SaveCatalog methods for loading and saving data are likewise simple:

procedure TMainForm.OpenCatalog(const AFileName: string); begin   CDS.Close;   CDS.FileName := AFileName;   { call CreateDataSet to create an empty dataset }   if not FileExists(AFileName) then CDS.CreateDataSet;   CDS.Open;   { call DisplayDataSet to automatically display the data     when a file is opened or when a new file is created }   DisplayDataset; end; procedure TMainForm.SaveCatalog(const AFileName: string); begin   CDS.FileName := AFileName;   CDS.SaveToFile(CDS.FileName, dfXML); end;

The TClientDataSet's SaveToFile method accepts two parameters: the destination file name and the format in which you want to save the TClientDataSet's data. You can save the data in any of the three formats provided by the TDataPacketFormat enumeration:

TDataPacketFormat = (dfBinary, dfXML, dfXMLUTF8);

The default dfBinary value is used to save data into binary CDS files, which are smaller than XML files but cannot be easily viewed and edited in a text editor. The two XML values allow you to save data in XML format.

Creating Actions and Menus

Now it's time to add several actions to the TActionManager component. Double-click the TActionManager component on the Designer Surface and then click the New Action button five times to add five new actions for the New, Open, Save, Save As, and Exit commands of the File menu, as shown in Figure 19-8.

image from book
Figure 19-8: Adding actions to the TActionManager

After you've created the five actions, it's time to define their categories. Since we're going to use these actions to implement the File menu commands, we need to select all five actions and set their Category properties to "File" (see Figure 19-9).

image from book
Figure 19-9: Placing the actions in a category

To select these five actions, first click on Action1, then press and hold Shift on the keyboard, and finally click on Action5. The selected actions will be placed into the File category after you type File in the Category property and press Enter.

Categorizing actions in the TActionManager helps you to easily build the main menu. For instance, to create the entire File menu, you only have to select the File category in the TActionManager and drag and drop it to the TActionMain- MenuBar component on the Designer Surface. Note that Figure 19-10 shows a TMainMenuBar component on which the File category was already dropped from the TActionManager component.

image from book
Figure 19-10: Creating a menu by dropping a category from the TActionManager to the TActionMainMenuBar

To completely implement these actions, you have to change their Caption properties, add a TOpenDialog and a TSaveDialog component to the Designer Surface, and create an OnExecute event handler for each action. These OnExecute event handlers are displayed in Listing 19-1.

image from book
Figure 19-11: File menu actions

Since we're only going to work with XML files, set the DefaultExt property of both common dialogs to "xml" and the Filter property to "MyBase XML Table

Listing 19-1: OnExecute event handlers

image from book
procedure TMainForm.NewActionExecute(Sender: TObject); begin   if OpenDialog1.Execute then     OpenCatalog(OpenDialog1.FileName); end; procedure TMainForm.OpenActionExecute(Sender: TObject); begin   if OpenDialog1.Execute then     OpenCatalog(OpenDialog1.FileName); end; procedure TMainForm.SaveActionExecute(Sender: TObject); begin   if CDS.FileName <> '' then     SaveCatalog(CDS.FileName)   else     SaveAsAction.Execute; end; procedure TMainForm.SaveAsActionExecute(Sender: TObject); begin   if SaveDialog1.Execute then     SaveCatalog(SaveDialog1.FileName); end; procedure TMainForm.ExitActionExecute(Sender: TObject); begin   Close; end;
image from book

Adding Records

To add new records to the TClientDataSet you need to create a simple dialog box to enable the user to enter the ID and name values for the new movie (see Figure 19-12).

image from book
Figure 19-12: The Add New Movie dialog box

The OK button's ModalResult property is set to mrNone because it must first check whether both the movie's name and ID are entered and only then return mrOK in ModalResult to let us know everything is OK. The Cancel button's ModalResult property can automatically be set to mrCancel to notify us when the user decides not to add a new record to the TClientDataSet.

The OK button's OnClick event handler, which checks if everything is entered, as well as the OnClick event handlers of the + and – buttons are displayed in the following listing.

Listing 19-2: The application logic of the Add New Movie dialog box

image from book
procedure TAddMovieForm.MinusButtonClick(Sender: TObject); var   Number: Integer; begin   Number := StrToInt(MovieID.Text) - 1;   if Number < 1 then   begin     Number := 1;     MessageDlg('Disc ID cannot be less than 1!', mtInformation, [mbOK], 0);   end;   MovieID.Text := IntToStr(Number); end; procedure TAddMovieForm.PlusButtonClick(Sender: TObject); var   Number: Integer; begin   Number := StrToInt(MovieID.Text) + 1;   MovieID.Text := IntToStr(Number); end; procedure TAddMovieForm.OKButtonClick(Sender: TObject); const   ALLOW: array[Boolean] of TModalResult = (mrNone, mrOK); begin   { allow adding the item to the database only     if both ID and name are properly defined }   ModalResult := ALLOW[(MovieID.Text <> '') and (MovieName.Text <> '')];   if ModalResult = mrNone then     MessageDlg('Please enter both ID and disc name before clicking OK.',       mtInformation, [mbOK], 0); end;
image from book

Now that you have the dialog box, you can create the Add action that will display the dialog box to enable the user to enter the required data, check if the user pressed OK on the dialog box, and call the TClientDataSet's AppendRecord method to add the user's data to the TClientDataSet. After you create the Add action, you can drop it on the TActionToolBar component to create the Add button.

Here's the OnExecute event handler of the Add action:

procedure TMainForm.AddActionExecute(Sender: TObject); begin   with TAddMovieForm.Create(Self) do   begin     MovieID.Text := IntToStr(Succ(MovieList.Items.Count));     ShowModal;     if ModalResult = mrOK then     begin       CDS.AppendRecord([StrToInt(MovieID.Text), MovieName.Text]);       FindEdit.Clear; { remove the filter to see all items }       { refresh the list box }       DisplayDataset;     end;         // if ModalResult     Free;   end;           // with end;

The following figure shows the Add New Movie dialog box and how the entire application looks and works at this stage.

image from book
Figure 19-13: The Add New Movie dialog box at run time

Searching for Records

To search for records in a TClientDataSet, you can use the Locate method and pass the name of the field you want to search as the first parameter, the value you want to find as the second parameter, and search options as the last parameter:

function TCustomClientDataSet.Locate(const KeyFields: string;   const KeyValues: Variant; Options: TLocateOptions): Boolean; 

The TLocateOptions type is a set that enables you to perform a case-insensitive search and to search using only part of the value:

type   TLocateOption = (loCaseInsensitive, loPartialKey);   TLocateOptions = set of TLocateOption;

We need to call the Locate method in the OnClick event handler of the list box to activate the appropriate record in the TClientDataSet when the user selects an item in the list. Here's the OnClick event handler of the list box:

procedure TMainForm.MovieListClick(Sender: TObject); begin   if MovieList.ItemIndex <> -1 then     CDS.Locate('MovieName', MovieList.Items[MovieList.ItemIndex], []); end;

If you run the application now and click on an item in the list box, the Locate method will find and activate the appropriate record, and you'll be able to see the selected item's details in the data-aware components in the right group box, as shown in Figure 19-14.

image from book
Figure 19-14: Using the Locate method to activate the selected item

Deleting Records

To delete the active record, we need to create the Delete action, which will call the TClientDataSet's Delete method to remove the active record only when a valid item is selected in the list.

Here are the OnExecute and OnUpdate event handlers of the Delete action:

procedure TMainForm.DeleteActionExecute(Sender: TObject); const   DELETE_MOVIE = 'Do you really want to delete disc:'#13; begin   if MessageDlg(DELETE_MOVIE +     MovieList.Items[MovieList.ItemIndex] + '?',     mtConfirmation, mbYesNo, 0) = mrYes then   begin     CDS.Delete;     DisplayDataset;   end; // if MessageDlg end; 

The following figure shows the Delete action at run time.

image from book
Figure 19-15: Deleting records


The two TClientDataSet properties that enable us to filter its records are Filter and Filtered. If you want to display only records that meet a certain condition, you have to set the Filtered property to True and write a filter string in the Filter property. For instance, if you want to display only the movie that's called "A" (without quotes), write the following filter:

MovieName = 'A'

We are now going to implement an incremental filter, which will filter the TClientDataSet's records as we type it in the Find edit box. To do this, we need to use the Substring method in the Filter string. For instance, to check if the first two characters in the movie name are "ab", we have to write the following filter:

Substring(MovieName, 1, 2) = 'ab'

The following listing shows the OnChange event handler of the Find edit box.

Listing 19-3: Incremental filtering

image from book
procedure TMainForm.FindEditChange(Sender: TObject); const   SUBSTRING = 'Substring(MovieName, 1, %d) = ''%s'''; var   selectedMovie: string; begin   CDS.Filter := Format(SUBSTRING, [Length(FindEdit.Text), FindEdit.Text]);   { enable filtering only if there's text in the FindEdit edit box }   CDS.Filtered := FindEdit.Text <> '';   { call DisplayDataSet to show only the filtered items }   DisplayDataSet;   selectedMovie := CDS.FieldByName('MovieName').AsString;   MovieList.ItemIndex := MovieList.Items.IndexOf(selectedMovie); end;
image from book

To see how filtering works, take a look at the following figure.

image from book
Figure 19-16: Incremental filtering at run time

Accepting and Discarding Changes

The TClientDataSet stores data in two separate packets called Data and Delta. The Data packet contains the current state of the data, and the Delta packet logs changes made to the TClientDataSet and holds inserted, updated, and deleted records. The Delta packet is very useful at run time since we can determine how many records were modified and we can select whether we want to accept or discard changes made to the data. However, the Delta packet, like normal data, is also written to the file, which increases the size of the file unnecessarily. The following figure shows a image from book SampleDB.xml file that has both normal data and a change log (the PARAMS tag and the RowState attribute).

image from book
Figure 19-17: An XML file that contains both normal data and the change log

To have the application work properly and to reduce the size of the file on disk, we need to call the MergeChangeLog method before saving the data in the SaveCatalog method. Here's the updated SaveCatalog method:

procedure TMainForm.SaveCatalog(const AFileName: string); begin   CDS.FileName := AFileName;    { call MergeChangeLog to apply changes to     the database to reduce its size and to have the     OnCloseQuery event handler work properly }   CDS.MergeChangeLog;   CDS.SaveToFile(CDS.FileName, dfXML); end;

Finally, the last thing we have to do is write an OnCloseQuery event handler that will ask the user if he or she wants to save or discard changes made to the data. To see if the data was changed, check the TClientDataSet's ChangeCount property, which holds the number of changes in the change log. Note that in order to have this property report the correct number of changes, the file loaded from disk must not contain the change log (this is why we have to merge changes before saving the data to disk).

If the user wants to save changes made to the data, call the SaveAction's OnExecute handler. If the user wants to discard the changes, call the TClientDataSet's CancelUpdates method.

Here's the entire OnCloseQuery event handler:

procedure TMainForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean); begin   if CDS.ChangeCount > 0 then   begin     case MessageDlg('Save changes to the database?',       mtConfirmation, mbYesNoCancel, 0) of         mrYes: SaveAction.Execute;         mrNo: CDS.CancelUpdates;         mrCancel: CanClose := False;     end;         // case   end;           // if end;

Inside Delphi 2006
Inside Delphi 2006 (Wordware Delphi Developers Library)
ISBN: 1598220039
EAN: 2147483647
Year: 2004
Pages: 212
Authors: Ivan Hladni

Similar book on Amazon © 2008-2017.
If you may any questions please contact us: