Miscellaneous hints, tips and tricks

This is a collection of hints,  tips and tricks for Delphi programming. I'll add more snippets as they become available.

Error messages

Control '' has no parent window

When you drop a windowed component on a form, it happens some time that the component does not instal and generates the error "Control ' ' has no parent window". This simply means that the constructor of the component contains statements that need a valid window handle. Because he window handle isn't created in the object constructor, and it is created "much" later, in CreateWnd(), move the offending statement(s) into an overridden CreateWnd() method and it will install.

Algorithms

Calculation of the date of occurence of Easter Sunday each year

The date of Easter Day was defined by the Council of Nicaea in AD325 as the Sunday after the first full moon which falls on or after the Spring Equinox. The Equinox is assumed to always fall on 21st March, so the calculation reduces to determining the date of the full moon and the date of the following Sunday. The algorithm used here was introduced around the year 532 by Dionysius Exiguus. Under the Julian Calendar (for years before 1753) a simple 19-year cycle is used to track the phases of the Moon. Under the Gregorian Calendar (for years after 1753 - devised by Clavius and Lilius, and introduced by Pope Gregory XIII in October 1582, and into Britain and its then colonies in September 1752) two correction factors are added to make the cycle more accurate.

The Delphi program that follows calculates the date of occurence of Easter Sunday given the year.

function Easter(Y : Integer) : TDateTime;
  var
  c,n,k,i,j,l,m,d : Integer;
begin
  c:= Y div 100;
  n:= Y mod 19;
  k:= (c-17) div 25;
  i:= (c - c div 4 - (c-k) div 3 + 19*n + 15) mod 30;
  i:= i - (i div 28) * (1- (i div 28) * (29 div (i+1)) * ((21-n) div11));
  j:= (Y + Y div 4 + i + 2 - c + c div 4) mod 7;
  l:= i - j;
  M:= 3 + (l+40) div 44;
  D:= l + 28 - 31 * (M div 4);
  Result:= EncodeDate(Y,M,D);
  end;

I don't recall where I found the formula but I know that it calculates the occurrence of the first Sunday following the spring full moon. It is valid only for the Gregorian calendar, i,e., for years after 1753.

Using a different algorithm programmed in PHP, you can find hereunder a way to find the date of Easter given the year:

----- Input a year: ----- Easter is on Sunday March 23, 2008 -----

How to programmatically find a node in a ShellTreeView component

The ShellTreeView component is a window, which displays a hierarchical tree view of the system's shell folders and closely mimics the left-hand pane of the Windows Explorer. Every entry shows the icon and the name of the folder. By double clicking an entry, the user can show or hide a list of the subfolders of the selected folder. Alternatively, the user can click the [+] or [-] buttons in front of an entry.

Technically speaking, the ShellTreeView component is a descendant of TCustomTreeView that implements the IShellFolder interface to communicates with the Windows shell. Each node is comprised of a Data record whose PathName member provides the path leading to the node. It is therefore extremely easy to obtain the path of a node using

Path:= TShellFolder(Node.Data).PathName

but the reverse is not as easy. The process takes the path of a folder as its argument and it returns a reference to the node of the tree that corresponds to this pat but all the work is performed by the internal function ScanNode(). The algorith involves a progressive segmentation of the elements of the path. For instance, the path

C:\Program Files\MyFolder\MySubFolder

contains 4 segments "C:\", "Program Files", "MyFolder" and "MySubFolder" separated by "\". The algorithms is performed in the procedure ScanNode() that is called recursively and stands as follows:

function TGtroCustomCheckShellTreeView.GetNodeFromPath(const Path: string): TTreeNode;
var
  FoundNode: TTreeNode;

function AppendStop(ANode: TTreeNode): string;
begin
  Result:= TShellFolder(ANode.Data).PathName;
  if '\' <> Copy(Result, Length(Result), 1) then
    Result:= Result + '\';
  end
    
procedure ScanNodes(Path: string; Segment: string; ANode: TTreeNode);
var
  P: Integer;
  Stop: string;
begin
  P:= Pos('\', Path);
  if P > 0 then
  begin
  // Split the path into segments (segments are separated by "\")
    Segment:= Segment + Copy(Path, 0, P); // First segment of the path
    Path:= Copy(Path, P+1, Length(Path)); // Remaining segments of the path
    ANode.Expand(False); // Expand the node
    ANode:= ANode.getFirstChild; // Move to the first child node
    Stop:= AppendStop(ANode); // Append "\" if not already there
    while Segment <> Stop do // Iterate on each child nodes
    begin
      ANode:= ANode.getNextSibling;
      if ANode <> nil then
        Stop:= AppendStop(ANode)
      else // Iterated beyond last child node
        Break; // No correspondence => out!
    end;
    if Path <> '' then // Path is not exhausted
    begin
      if ANode <> nil then
      begin
        ANode.Expand(False);
        ScanNodes(Path, Segment, ANode); // called recursively
      end;
    end
    else // Path is exhausted
      FoundNode:= ANode; // Node found
  end;
end;


begin
  ScanNodes(Path, '', Items[0]);
  Result:= FoundNode; //Reference to the node if found; nil otherwise
end;

Components

VCL Component's Icons

I wandered how to use the icons associated with the VCL components and I searched for an answer on the Web. I found out (Jeff Overcash) that they are stored in the design time packages of the VCL. One can use the resource explorer located in the demos directory to look at and save off resources from dlls (which is all a package really is).  The design time packages are in the bin directory and mostly start with dclxxx.bpl.

Miscellaneous

Formatting the display of a TTimeField

I wanted to format time input in a field of a TTable as 'hh:nn'. First, I put the DisplayFormat property of the field to 'hh:nn' at design time  and it looked fine. However, during execution, it behaved correctly (e.g. 18:45) until the component displaying the field got the focus  (it displayed 18:45:34 instead of the desired 18:45). I found out that this can be corrected using the OnGetText and the OnSetText of the underlying field as follows:

procedure TDM.TableItemsStartItemGetText(Sender: TField; 
var 
  Text: String;
  DisplayText: Boolean);
begin
  Text:= FormatDateTime('hh:nn',Sender.AsDateTime);
end;

and

procedure TDM.TableItemsStartItemSetText(Sender: TField;
const Text: String);
begin
  Sender.AsDateTime := Date + StrToTime(Text);
end

Get rid of flicker with LockWindowUpdate()

As I had implemented a text search over several records of a database, I wanted to find out how I could remove the flicker that occurs when the fields of the database are displayed in succession in a TDBRichEdit component. It was a real disturbance and my first move consisted in hiding the display for the duration of the each search but the approach did not appear to be very professionnal. My second move was to replace the display by a snapshot of itself (using the BitBlt() function of the Windows API) for the duration of the search but during the search I made on the Web, I found the LockWindowUpdate() function of the Windows API and my problem was solved.

Simple, simply use it as in the following code snippet:

procedure TAny.DoSomething(Argument: string);
  begin
  LockWindowUpdate(Window.Handle); // locks the window and prevents flicker
  // the argument specifies the window in which drawing will be disabled.
  try    ... // do something
  finally
  // enables drawing in the specified window.
    LockWindowUpdate(0);// unlocks the window
  end; // try...finally
end;

What does this function do?  When a window is locked, all attempt to draw into it or its children fail. Instead of drawing, the window manager remembers which parts of the window the application tried to draw into, and when the window is unlocked, those areas are invalidated so that the application gets another WM_PAINT message, thereby bringing the screen contents back in sync with what the application believed to be on the screen.

There is only one problem with this function. The documentation explicitly calls out that only one window (per desktop, of course) can be locked at a time. since the call to LockWindowUpdate(0)  at the end of the procedure would not know which window to unlock if there were many.

Database Programming with the BDE

BLOB has been modified

"Blob has been modified" ($3302) is an error that occurs when the BLOB portion of the record contained in the .DB file has become inconstent with the BLOB portion in the .MB file. This could occur when the write to the .DB file was successful but the .MB file did not get updated, or visa-versa. 

There are a few mechanisms to fix a table where these errors has occurred: 

  1. First try re-starting the application. It is possible that the BDE has become unstable and is reporting incorect errors. Also try opening the table with a different application. 
  2. Use Paradox 7 or 8 to run the Table Repair utility. Please see original documentation for more information. 
  3. Run TUtility and rebuild the table. TUtility is an unsupported utility available for download from the Inprise's web site in the Utilities, programs and updates section. 
  4. Delete all indexes and recreate them (Index out Date ($2F02) error only). To do this you'll need to know the structure of all your indexes (including primary) before recreating them, which means you need to know the structure of all indexes before the error occurs.

Avoiding data losses in Paradox tables

In a Paradox database, several records may be created or updated. If the BDE is shut down in an irregular manner, all of the new data is lost. but there is a way to prevent it: write the data to disk immediately after a record is updated. This can be done as follows: add BDE to the uses clause and in the AfterPost event handler, put

DBiSaveChanges(yourTableName.Handle);

This will save all data in buffers directly to the database thus preventing a loss of data should anything go wrong in the current database session.

This may be a way to avoid the "BLOB has been modified" error discussed in the previous section.

Check Table existence

In order to find if a table exists without raising any exception, iterate through the form's Components array property and check for the TTable.

for I := 0 to Pred(ComponentCount) do
  if Components[I] is TTable then
    if (Components[I] as TTable).Name = 'MyTTable' then ..

or use the FileExists function described in the on-line help.

Copy of the structure of a table

In order to copy the structure of a table to an empty table, use the following procedure:

procedure CopyStructure(Source, Target: string);
begin with TTable.Create(Application) do try TableName := Source; Open; FieldDefs.Update; IndexDefs.Update; Close; TableName := Target; CreateTable; finally Free; end; end;

Copying a table 

When trying to move the data from a table to another table with the same structure, one uses the TBatchMove component. It will even create the table for you if you set the Mode property to batCopy. One thing about using BatchMove to Create or Copy an existing Table, is that none of the Indexes or Keys are copied. 

Where Table1 exists and Table2 does not exist, but has a valid DataBaseName (Alias) and a new (typed in) TableName, use the following procedure:

procedure TForm1.CreateTblButton1Click(Sender: TObject);
begin
 // This creates Table2 from Table1 with primary and secondary indexes
    Table2.FieldDefs := Table1.FieldDefs;
    Table1.IndexDefs.Update;
    Table2.IndexDefs.Assign(Table1.IndexDefs);
    Table2.CreateTable;
    Table2.Open;
 // This copies all the records from Table1 to Table2
    BatchMove.Source:=Table1;      // can be set in object inspector
    BatchMove.Destination:=Table2; // can be set in object inspector
    BatchMove.Mode := batAppend;   // can be set in object inspector
    BatchMove1.Execute;
 end;

Another way to copy a table with all its content, structure, indexes and keys of non-SQL databases is to use dbiCopyTable(). See what Borland proposes.

Compacting a table

The dataset components (TTable, TQuery, etc.) in Delphi are designed to be as generic as possible, for use with the many different table types that can be accessed. This means that some of the operations specific to only one type of table (such as a pack for a dBASE table) would be excess baggage for all other table types. Because of this, these unique operations were not built into the stock dataset components. If you need such operations in a component, you can easily create a custom component that implements the needed functionality or use the BDE functions directly. If it is a dBASE table, you have to use the BDE API function DbiPackTable(). If it is a Paradox table, use the BDE API function DbiDoRestructure() function. The bPack field of the pTblDesc parameter must be set to True to trigger the pack. If the table is of any other type, see the documentation from the vendor for the specific database system.

Find hereunder a procedure that I have developed that packs a Paradox table

procedure TDM.PackTable(Table: TTable);
var cProps: CURProps; hDB: hDBIDb; TableDesc: CRTblDesc; begin if Table.Active then Table.Close;
Table.Exclusive:= true;
try Table.Open; // Open exclusive Check(DbiGetCursorProps(Table.Handle, cProps)); // Get table properties if (cProps.szTableType = szParadox) then // Table is Paradox table? begin FillChar(TableDesc, SizeOf(TableDesc), 0); // blank out the structure Check(DbiGetObjFromObj(hDBIObj(Table.Handle),objDATABASE, hDBIObj(hDb))); // Get the Database handle StrPCopy(TableDesc.szTblName, Table.TableName); //put Table name in structure StrPCopy(TableDesc.szTblType, cProps.szTableType); //put table type in structure TableDesc.bPack:= true; // set pack option to true in structure Table.Close; // close the table so the restructure can complete Check(DbiDoRestructure(hDb, 1, @TableDesc, nil, nil, nil, false));
end //if else ShowMessage(strParadox);
finally Table.Exclusive:= false; Table.Open; end; end;

First, the table must be closed and re-opened exclusive. You get the handle of the table end verify that you are dealing with a Paradox table. Then, the packing process is prepared and executed. After that, the table is closed and re-opened non-exclusive.

MySQL databases

On the Web today, content is king. After you've mastered HTML and learned a few neat tricks in JavaScript and Dynamic HTML, you can probably build a pretty impressive-looking Web site design. But then comes the time to fill that fancy page layout with some real information. Any site that successfully attracts repeat visitors has to have fresh and constantly updated content. In the world of traditional site building, that means HTML files and lots of them

The MySQL database is a free database engine under the GNU General Public License model (although you can also purchase a commercial license) and its amazing how well-known the name of MySQL has become in just a few years. It is available on both Windows and Linux, and is available as the first choice on a large number of web servers. Apart from that, MySQL offers a lot as a relational DBMS, including a very good reputation when it comes to the speed of reading records (other operations are fast as well, but MySQL seems to shine especially when reading data), and support for SQL, client/server development, and even transactions.

Table types

MySQL has six distinct table types (MyISAM, InnoDB, MERGE, ISAM, HEAP and Berkely DB) but two of them attract special interest:.

As my intent was the development of a rather simple database, I have chosen the MyISAM type of table for the purpose of my project despite is lack of support for referential integrity.

Accessing MySQL databases with Delphi

The Borland Database Engine (BDE) has long been the number one choice for quick-and-dirty data access, based on the dBASE and Paradox table formats. But now the BDE is officially frozen and SQL Links is even deprecated. In other words, there will be no further development of and enhancements added to the BDE, so we should be seriously looking at alternatives for data access in Delphi.

There are various way of accessing MySQL from Delphi.

Catching exceptions in threads

It does not need much experience in Delphi programming to recognize what happens when a procedure with a long loop in it is executed: the application stops receiving messages and appears to hang. The most simple way of dealing with this situation is to make a call to Application.ProcessMessages() within the body of the loop so that the application can still receive messages from external sources. In some cases, this may be practically useless mainly if some steps of the loop take several seconds to execute. In such cases, using threads is the answer. It frees the user interface because the process is running completely separate from the main thread of the program where the interface resides. So regardless of what you execute within a loop that is running in a separate thread, the user interface will never get locked up. 

Although the Win32 API provides comprehensive multithreading support, when it comes to the creation and destruction of threads, the VCL has a useful class, TThread, which abstracts away many of the technicalities of creating a thread, provides some useful simplifications, and tries to prevent the programmer from falling into some of the more unpleasant traps that this new discipline provides. The Delphi help files provide reasonable guidance when creating a thread class, so I won't mention much about the sequence of menu actions required to create a thread apart from suggesting that the reader select File | New... and then choose Thread Object.

To create a multithreaded application, the easiest way is to use the TThread Class. This class permits the creation of an additional thread (alongside the main thread) in a simple way. Normally you only have to override 2 methods: the Create constructor, and the Execute method.

There is indeed a problem: If exceptions are raised in a thread that is not the primary thread, and that exception is not handled in code, no dialog box appears to tell the user what happened. The program happily continues while the thread that raised the exception dies. This is not always what the programmer wants. The trick to avoid this is to get the exception shown from the context of the primary thread and that's exactly what the synchronize keyword is meant to do. 

I found a solution to this problem in an article on www.wehlou.com. Instead of deriving a thread classes directly from the TThread class, it is proposed to derive it  from TThreadGT class as follows:. 

type
  TThreadGT = class(TThread)
private fException : Exception; procedure PrimaryHandleException; protected procedure HandleException; end;

procedure TThreadGT.PrimaryHandleException; begin if GetCapture() <> 0 then SendMessage(GetCapture(), WM_CANCELMODE, 0, 0); if fException is Exception then begin if Assigned(ApplicationShowException) then ApplicationShowException(fException); end else SysUtils.ShowException(fException, nil); end; procedure TThreadGT.HandleException; begin fException := Exception(ExceptObject); try if not (fException is EAbort) then Synchronize(PrimaryHandleException); finally fException := nil; end; end;

Then in your very own Execute() method, make sure TThreadGT's HandleException() method is called:

procedure TMyThread.Execute;
begin
  inherited;
  try
    while not Terminated do 
    begin
... end; except HandleException(); end; end;

Warning!
This code was developed for the pleasure of it. Anyone who decides to use it does so at its own risk and agrees not to hold the author responsible for its failure.


Questions or comments?
E-Mail
Last modified: September 3rd 2014 12:08:56. []