Google
 

Wednesday, October 31, 2007

Minimize Child Forms Independent of the Main Form

Bagaimana jika child form dari MDI form tetap tampil saat MDI Form di minimize ?

By design, Delphi applications only have one button on the Windows Task Bar for the whole application. When you, for example, minimize the main form of an SDI application, Delphi minimizes the main form and than hides all other windows in the application as well.

SDI? MDI?

SDI means Single Document Inteface. By default, the FormStyle property of all Delphi forms added to the application is set to fsNormal, so that the IDE assumes that all new applications are SDI applications.

Being an SDI application, does not mean you can not have more than one form in the application. In most cases, of course, you will have more than one form - but only one form is the main form of the application.

The main form is the first form created in the main body of the application. When the main form closes, the application terminates. As stated, in an SDI application, when the user minimizes the main form, all other forms get hidden too. Also, only the main form (to be precise, the application) will have a button on the task bar. When a child form is minimized it gets minimized to the Windows Desktop.

Note: contrary to the SDI, in an MDI application, more than one document or child window can be opened within a single parent window. FormStyle property for a MDI parent is fsMDIForm.

Power to the Child Forms!

Let's say you do not want child forms to hide when the main form minimizes. What you need to do is to change the ExStyle of the Params property in the overriden CreateParams method. What's more, you also need to change the parent window of a child form - set it to Windows Desktop.

This is how the CreateParams should look:

interface

type
___TChildForm = class(TForm)
...
protected
___procedure CreateParams(var Params: TCreateParams) ; override;
...

implementation

procedure TChildForm.CreateParams(var Params: TCreateParams) ;
begin
___inherited;

___Params.ExStyle := Params.ExStyle or WS_EX_APPWINDOW;

___Params.WndParent := GetDesktopWindow;
end;
By setting the child form's parent window handle to the Desktop, you remove the link that would normally force the whole application to come to the top when this form comes to the top.
By design, all secondary forms point to the main form of the application for parentage. Clicking on a secondary form's taskbar button while another application is active will bring all the applications forms to front. By using GetDesktopWindow API you change this behaviour.

That's it. Simply override the CreateParams of every child form type in your project and have them minimize independently of the main form.

Note: you could have a base child form class with overriden CreateParams, then have all your child forms inherit the base class.

Implementing OnActivate / OnDeactivate for MDI Child Forms

Find out what MDI Child WAS Active

In an MDI application, more than one document or child window can be opened within a single parent window. If you need to react when a certain MDI child form becomes active you can handle the OnActivate event of the child form. The OnActivate event is fired when the form (MDI child in this case) receives the input focus. If you need to react when a certain MDI child is being deactivated you can handle the OnDeactivate event. OnDeactivate is fired when the form transitions from being the active form to another form in the same application becoming the active form.

What if you need to extract some data from the MDI child form being deactivated from the form being activated?

You need to handle a special MDI Windows message: WM_MDIACTIVATE.

Note: every (MDI parent) Delphi form exposes the ActiveMDIChild property.

ActiveMDIChild basically returns the active MDI child form.

What we are after here is implementing something like OnActiveMDIChildChange event!

What MDI Child is Being Deactivated?

The WM_MDIACTIVATE message can be handled on the MDI parent level and on the MDI child level. MDI parent level means writing code to handle the message in the MDI parent form. MDI child level means handling the message on the child level - inside every MDI child form.

On the MDI parent level the message can be used to identify the MDI child window being activated.

More interestingly, on the MDI child level the WM_MDIACTIVATE message caries the HWND (handle) of the MDI child form being deactivated. This means that from the active MDI child form handling the WM_MDIACTIVATE message you can get your hands on the previously active MDI child window!

Note: in any MDI application you should have one base form for all your MDI child forms - then use form inheritance (VFI) to extend the functionality for each particular MDI child type form.

You will then write the code to handle the WM_MDIACTIVATE message only once - inside the base MDI child form class.

message WM_MDIACTIVATE

To handle WINDOWS message write a message method for this message:
type
//MDI child form
___TMDIChild = class(TForm)
private
___procedure WMMDIACTIVATE(var msg : TWMMDIACTIVATE) ; message
...
end;

WM_MDIACTIVATE;
In the implementation get the MDI child that was previously activate ("now" deactivated) and get some data from it:
procedure TMDIChild.WMMDIACTIVATE(var msg: TWMMDIACTIVATE) ;
var
___deactivated : TWinControl;
___deactivatedChild : TMDIChild;
begin
//find the control (form) being deactivated
___deactivated := FindControl(msg.DeactiveWnd) ;

//if deactivated is a TMDIChild form .. do something ...
___if Assigned(deactivated) AND (deactivated is TMDIChild) then
___begin
______deactivatedChild := TMDIChild(deactivated) ;

______Caption := 'deactivated: ' + deactivatedChild.Caption;
___end;
end;

The msg procedure parameter holds the info related to the WM_MDIACTIVATE message.

Note that WM_MDIACTIVATE is first sent to the child window being deactivated and then to the child window being activated.

The above code uses FindControl to get the TWinControl descendant whose window handle is identified by the DeactiveWnd HWND value. Since DeactiveWnd identifies the MDI child window being deactivated, FindWindow will locate the MDI child that was previously active.

When you make sure the previously active MDI child was found, you might need to check its type and do some special processing ... I'll leave this up to you...

Tuesday, October 30, 2007

Highlighting Delphi's DBGrid Row On Mouse over

Memberikan efek Highlight pada DbGrid pada event on mouse over

Hot Tracking for TDBGrid

Delphi's TDBGrid displays and manipulates records from a dataset in a tabular grid.

Contrary to what most novice developers think, the DBGrid component allows various customizations. changing the color of a specific cell or a column or even a row is not complicated at all.
What most are not aware of, is that you can even implement the OnMouseHover (hot tracking)change the display (color, font, etc.) of the DBGRid's (data) row underneath the mouse - *not* the currently selected row - thus making it look like today's web driven interfaces.
behavior to
I'm sure you've seen this behavior many times - many tables on the Web change the background color of their rows as mouse hovers over them.

Coloring DBGrid's Row On Mouse Hover

To follow the source code provided later on, create a new application. On the form ("Form1") drop a DBGrid ("DBGrid1"). Use any type of TDataset descentand and connect to some database data to make sure the dbgrid has some data to show.

Note: All he code goes inside the Form1's unit source!!

For a start, prepare the protected hack for the DBGrid component. In the interface section, just add the following line:

type
___THackDBGrid = class(TDBGrid) ;

Next, add a private integer property "MouseOverRow" - you'll use it to track the index position of the row the mouse is over.

private fMouseOverRow: integer;
procedure SetMouseOverRow(const Value: integer) ;
property MouseOverRow : integer read fMouseOverRow write
SetMouseOverRow;
To be able to track the row the mouse is over you need to handle the OnMouseMove DBGrid1's event.
//DBGrid1 OnMouseMove
procedure TForm1.DBGrid1MouseMove(Sender: TObject;Shift: TShiftState;X, Y: Integer) ;
var
___ gc: TGridCoord;
begin
___gc := DBGrid1.MouseCoord(x, y) ;
___MouseOverRow := gc.Y;
end
The MouseOverRow property's setter calls the Refresh method that will in turn fire the DrawColumnCell event.
procedure TForm1.SetMouseOverRow(const Value: integer) ;
begin
___if fMouseOverRow <> Value then
___begin
______fMouseOverRow := Value;
______DBGrid1.Repaint;
___end;
end;
As expected the tricky part goes inside the OnDrawColumnCell event handling procedure.
//DBGrdi1 OnDrawColumnCell
procedure TForm1.DBGrid1DrawColumnCell(Sender: TObject;const Rect: TRect;DataCol: Integer;Column: TColumn;State: TGridDrawState) ;
begin
___if NOT ((gdFocused in State) or (gdSelected in State)) AND (MouseOverRow = 1 + THackDBGrid(DBGrid1).DataLink.ActiveRecord) then
___begin
______with DBGrid1.Canvas do
______begin
_________Brush.Color := clSilver;
_________Font.Color := clNavy;
______end;
___end;

___DBGrid1.DefaultDrawColumnCell(Rect, DataCol, Column, State) ;
end;

What we want is to change the drawing color of the row the mouse is over.

Therefore:

If MouseOverRow matches the ActiveRecord value of the protected DataLink property and the cell being drawn does not have the focus and is not selected: change the Canvas's coloring to actually "draw" the row highlighted.

The tricky part here was the protected DataLink property and the ActiveRecord value. The DataLink property helps the data-aware grid manage its link to the data source and respond to data events. The ActiveRecord specifies the index of the current record within the internal record buffer maintained by the dataset for the Owner of the TDataLink object.

You might think that ActiveRecord points to the currently selected record for the data displayed in the grid, but it is not.

Note: you might ask yourself: "how does he know this". Here's the answer: I read Delphi Help files and browse the VCL source code

While Delphi draws data the DBGrid displays it changes the ActiveRecord to match the row being drawn.

And this is the trick: match the currently drawn row woth the one the mouse is over. That's it. Beauty!

Tbutton Dengan multiline Caption

The next procedure sets Captions on all TButton Controls on a given Parent to be multi-lined...


procedure SetMultiLineButton(AParent: TWinControl) ;
var
___j : integer;
___ah : THandle;
begin
___for j := 0 to AParent.ControlCount - 1 do
___begin
______if (AParent.Controls[j] is TButton) then
______begin
_________ah := (AParent.Controls[j] as TButton).Handle;
_________SetWindowLong(ah, GWL_STYLE,GetWindowLong(ah, GWL_STYLE) OR BS_MULTILINE) ;
______end;
___end;
end;


{
usage: suppose there is a
Button1 and Button2 on Form1,
Button3 and Button4 on Panel1 on Form1
by calling the next line
}

SetMultiLineButton(Panel1);

{only Button3 and Button4 will have
multi-lined caption.
}
~~~~~~~~~~~~~~~~~~~~~~~~~

Set File Date (created)

Here's a procedure to change the created date "attribute" for a given file:

function SetFileDate(Const FileName : String;Const FileDate : TDateTime): Boolean;
var
___FileHandle : THandle;
___FileSetDateResult : Integer;
begin
___try
______try
_________FileHandle := FileOpen(FileName,fmOpenWrite OR fmShareDenyNone) ;
_________if FileHandle > 0 Then
_________begin
____________FileSetDateResult :=
____________ FileSetDate(FileHandle,DateTimeToFileDate(FileDate)) ;
____________result := (FileSetDateResult = 0) ;
_________end;
_________except
_________Result := False;
______end;
___finally
______FileClose (FileHandle) ;
___end;
end;

{Usage:}
SetFileDate('c:\mydir\myfile.ext', Now)

Store User and Application Data in the Correct Location

SHGetFolderPath retrieves the full path of a known folder identified.

Here's a custom wrapper function around the SHGetFolderPath API to help you get any of the standard folders for all or the currently logged Windows user.

uses SHFolder;

function GetSpecialFolderPath(folder : integer) : string;
const
___SHGFP_TYPE_CURRENT = 0;
var
___ path: array [0..MAX_PATH] of char;
begin
___if SUCCEEDED(SHGetFolderPath(0,folder,0,SHGFP_TYPE_CURRENT,@path[0])) then
______ Result := path
___else
______Result := '';
___end;
Here's an example of using the SHGetFolderPath function:
  • Drop a TRadioButtonGroup (name: "RadioGroup1") on a form
  • Drop a TLabel (name: "Label1") on a form
  • Add 5 items to the radio group:
    1. "[Currenty User]\My Documents"
    2. "All Users\Application Data"
    3. "[User Specific]\Application Data"
    4. "Program Files"
    5. "All Users\Documents"
  • Handle the RadioGroup's OnClick event as:

Note: "[Current User]" is the name of the currently logged in Windows user.

//RadioGroup1 OnClick
procedure TForm1.RadioGroup1Click(Sender: TObject) ;
var
___index : integer;
___specialFolder : integer;
begin
___if RadioGroup1.ItemIndex = -1 then Exit;

___index := RadioGroup1.ItemIndex;

___case index of
______//[Current User]\My Documents
______0: specialFolder := CSIDL_PERSONAL;
______//All Users\Application Data
______1: specialFolder := CSIDL_COMMON_APPDATA;
______//[User Specific]\Application Data
______2: specialFolder := CSIDL_LOCAL_APPDATA;
______//Program Files
______3: specialFolder := CSIDL_PROGRAM_FILES;
______//All Users\Documents
______4: specialFolder := CSIDL_COMMON_DOCUMENTS;
___end;

___Label1.Caption := GetSpecialFolderPath(specialFolder) ;
end;
Note: The SHGetFolderPath is a superset of SHGetSpecialFolderPath.

You should not store application-specific data (such as temporary files, user preferences, application configuration files, and so on) in the My Documents folder. Instead, use an application-specific file that is located in a valid Application Data folder.

Always append a subfolder to the path that SHGetFolderPath returns. Use the following convention: "\Application Data\Company Name\Product Name\Product Version".





Monday, October 29, 2007

Executing and Running Applications and Files from Delphi Code

Executing and Running Applications and Files from Delphi Code

How many times have you wanted to execute some program from your Delphi application?

Let's say we have a database application that uses some external backup utility. The back up utility takes parameters from your application, archives data, while your program waits until backup finishes.

On the other hand, have you ever needed to open documents presented in a file listbox, just by double clicking on them, without opening the associated program first?

Finally, imagine a link-label in your program that will take the user to your home page (by executing the Internet Explorer). Moreover, what do you say about sending an e-mail directly from your Delphi application through default Windows e-mail client program (like MS Outlook Express).

ShellExecute

To launch an application or execute a file in Win32 environment we will use the ShellExecute Windows API function.
Check out the help on ShellExecute for full description of parameters and error codes returned.
As you will see we can open any type of document from our program without knowing which program is associated with it (this link is defined in the Windows Registry).

Let's see some shell action!
Be sure to add ShellApi to your Unit's uses clause.


Run Notepad

uses ShellApi;
...
ShellExecute(Handle, 'open', 'c:\Windows\notepad.exe', nil, nil, SW_SHOWNORMAL) ;

Open SomeText.txt with Notepad

ShellExecute(Handle,'open', 'c:\windows\notepad.exe','c:\SomeText.txt', nil, SW_SHOWNORMAL) ;

Display the contents of the "DelphiDownload" folder

ShellExecute(Handle,'open', 'c:\DelphiDownload', nil, nil, SW_SHOWNORMAL) ;

Execute a file according to its extension.

ShellExecute(Handle, 'open', 'c:\MyDocuments\Letter.doc',nil,nil,SW_SHOWNORMAL) ;
Open web site or a *.htm file with the default web explorer

ShellExecute(Handle, 'open', 'http://delphi.about.com',nil,nil, SW_SHOWNORMAL) ;
Send an e-mail with the subject and the message body

var em_subject, em_body, em_mail : string;
begin
em_subject := 'This is the subject line';
em_body := 'Message body text goes here';

em_mail := 'mailto:delphi.guide@about.com?subject=' +
em_subject + '&body=' + em_body ;

ShellExecute(Handle,'open',
PChar(em_mail), nil, nil, SW_SHOWNORMAL) ;
end;

Execute a program and wait until it has finished. The following example uses the ShellExecuteEx API function.

// Execute the Windows Calculator and pop up
// a message when the Calc is terminated.
uses ShellApi;
...
var
SEInfo: TShellExecuteInfo;
ExitCode: DWORD;
ExecuteFile, ParamString, StartInString: string;
begin
ExecuteFile:='c:\Windows\Calc.exe';

FillChar(SEInfo, SizeOf(SEInfo), 0) ;
SEInfo.cbSize := SizeOf(TShellExecuteInfo) ;
with SEInfo do begin
fMask := SEE_MASK_NOCLOSEPROCESS;
Wnd := Application.Handle;
lpFile := PChar(ExecuteFile) ;
{
ParamString can contain the
application parameters.
}
// lpParameters := PChar(ParamString) ;
{
StartInString specifies the
name of the working directory.
If ommited, the current directory is used.
}
// lpDirectory := PChar(StartInString) ;
nShow := SW_SHOWNORMAL;
end;
if ShellExecuteEx(@SEInfo) then begin
repeat
Application.ProcessMessages;
GetExitCodeProcess(SEInfo.hProcess, ExitCode) ;
until (ExitCode <> STILL_ACTIVE) or
Application.Terminated;
ShowMessage('Calculator terminated') ;
end
else ShowMessage('Error starting Calc!') ;
end;

Sunday, October 28, 2007

Running Flash Animation With Delphi

Running Flash animations with Delphi
How to display Macromedia Flash animations (swf) inside a Delphi application. Grasp the fundamentals of integrating Macromedia Flash animations and Delphi.


Scenario
Suppose you are developing some kind of a Delphi application designed to display various graphics file formats. By default, Delphi lets you handle and show BMP, JPG, WMF and other "standard" picture file formats, and even animations. If you want to take you application to the next level, sooner or later, you'll want to include an option to play Flash animations. Macromedia Flash animations (*.swf) are those fancy movies you see everyday while surfing the Internet.

How to prepare for Flash animations
To display a Flash animation in a Delphi application, you'll need to import Macromedia Flash ActiveX control. The Flash ActiveX is titled 'SWFLASH.OCX' and it will probably be located somewhere inside the C:\Windows\System\Macromed\Flash folder.

First, let's see how to import the control into Delphi - of course, you only need to do this once:

  1. Start Delphi and select Component | Import ActiveX Control... from the main menu
    Delphi - Component - Import Activex
  2. Look for the 'Shokwave Flash (Version x.x)' and simply click on Install.
    Delphi Import ActiveX
  3. Select the Component palette location in which you want to place selected library. Maybe the best is to leave the ActiveX option selected.
  4. Click on Install.
  5. Select a package where the new component must be installed or create a new package for the control. By default, Delphi will propose the dclusr.dpk package.
  6. Click on OK.
  7. Delphi will prompt you whether you want to rebuild the modified/new package or not. Click on Yes.
  8. After the package is compiled, you'll get a message saying that the new TShokwaveFlash component was registered and already available as part of the VCL.
  9. Close the package detail window, allowing Delphi to save the changes to it.
  10. The component is now available in the ActiveX tab (if you didn't change this setting in step 4)
How to play Flash animations with Delhi
You are now ready to create your fist Flash enabled Delphi application. Here's how:
  1. Select the ActiveX tab on the Component palette.
  2. Pick the TShokwaveFlash component.
  3. Drop the component on a form.
  4. Select the component you just dropped on a blank form.
  5. Using the object inspector, set the Movie property to the name of an existing swf file on your system. Note that you must set it with the full path name.
  6. Make sure that the Playing property is set to True.
  7. Run the project, and here it is! A sample swf file is running below, though you do not see it running :)
    Flash in Delphi application

Flash to the max
Now, if you need to distribute you own very special Flash movie with your application, one way to go is to include a swf file inside your Delphi application exe file, as a resource.

If there is a need to register an ActiveX control on your user machine (while deploying your application) than you need to make sure the user has the ocx you need. See how to "Registering DLL and ActiveX controls from code"

And here's how to work with other properties and methods of the Flash component: ActiveX and Macromedia Flash.

QuickSort Sorting Algorithm in Delphi

One of the common problems in programming is to sort an array of values in some order (ascending or descending).

While there are many "standard" sorting algorithms, QuickSort is one of the fastest. Quicksort sorts by employing a divide and conquer strategy to divide a list into two sub-lists.

QuickSort Algorith

The basic concept is to pick one of the elements in the array, called a pivot. Around the pivot, other elements will be rearranged. Everything less than the pivot is moved left of the pivot - into the left partition. Everything greater than the pivot goes into the right partition. At this point each partition is recursively "quick sorted".

Here's QuickSort algorithm implemented in Delphi:

procedure QuickSort(var A: array of Integer; iLo, iHi: Integer) ;
var
___Lo, Hi, Pivot, T: Integer;
begin
___Lo := iLo;
___ Hi := iHi;
___Pivot := A[(Lo + Hi) div 2];
___repeat
______while A[Lo] <>do Inc(Lo) ;
______while A[Hi] > Pivot do Dec(Hi) ;
______if Lo <= Hi then
______begin
_________T := A[Lo];
_________A[Lo] := A[Hi];
_________A[Hi] := T;______
_________Inc(Lo) ;
_________Dec(Hi) ;
______end;
___until Lo > Hi;
___if Hi > iLo then QuickSort(A, iLo, Hi) ;
___if Lo <>then QuickSort(A, Lo, iHi) ;
end;

Usage:

var
___intArray : array of integer;
begin
___SetLength(intArray,10) ;

//Add values to intArray
___intArray[0] := 2007;
___...
___intArray[9] := 1973;

//sort
___QuickSort(intArray, Low(intArray), High(intArray)) ;

end;

Note: in practice, the QuickSort becomes very slow when the array passed to it is already close to being sorted.

How to enhance the functionality of a TDBgrid component using colors

Adding color to your database grids will enhance the appearance and differentiate the importance of certain rows or columns within the database.

Since the DBGrid is a great user interface tool for displaying data, this article will focus on questions like "How do I change the color of particular row / column / cell in a DBGrid?"

Preparations
This article assumes that you know how to connect a database to DBGrid component. The easiest way to accomplish this is to use the Database Form Wizard.
Select the employee.db from DBDemos alias. Select all fields except EmpNo.

Note: if you do not wont to work with the BDE, consider the chapters of the Free Database Course For Beginner Delphi Developers - Focus on ADO.

Coloring Columns
The first (and the easiest) thing you can do, to visually enhance the user interface, is to color individual column in the data-aware grid.

We will accomplish this through TColumns property of the grid. Select the grid component in the form and invoke the Columns editor by double clicking on the grid's Columns property in the Object Inspector. For more information on Columns editor look for "Columns editor: creating persistent columns" in your Delphi help files.

Now, everything you have to do is to specify the background color of the cells of the particular column. For text foreground color, see the font property.

Object Inspector Columns editor

This is what I have done, after few clicks... You have to agree that this is much better that the standard black'n'white grid (of course, use colors if you really need them).

Form - coloring columns

Coloring Rows
If you want to color the selected row in a DBGrid but you don't want to use the dgRowSelect option because you want to be able to edit the data you should use the DBGrid.OnDrawColumnCell event.

This technique demonstrates how to dynamically change the color of text in a DBGrid:

procedure TForm1.DBGrid1DrawColumnCell
(Sender: TObject; const Rect: TRect;
DataCol: Integer; Column: TColumn;
State: TGridDrawState);
begin
if Table1.FieldByName('Salary').AsCurrency>36000 then
DBGrid1.Canvas.Font.Color:=clMaroon;
DBGrid1.DefaultDrawColumnCell
(Rect, DataCol, Column, State);
end;
If an employee's salary is greater than 36 thousand, its row is displayed in Maroon colored text.

Next technique demonstrates how to dynamically change the color of row in a DBGrid:

procedure TForm1.DBGrid1DrawColumnCell
(Sender: TObject; const Rect: TRect;
DataCol: Integer; Column: TColumn;
State: TGridDrawState);
begin
if Table1.FieldByName('Salary').AsCurrency>36000 then
DBGrid1.Canvas.Brush.Color:=clWhite;
DBGrid1.DefaultDrawColumnCell
(Rect, DataCol, Column, State);
end;
If an employee's salary is greater than 36 thousand, its row is displayed in White.

Form - coloring rows

Coloring Cells
And finally: if you want to change the background color of the cells of the particular column (+ text foreground color), this is what you have to do:

procedure TForm1.DBGrid1DrawColumnCell
(Sender: TObject; const Rect: TRect;
DataCol: Integer; Column: TColumn;
State: TGridDrawState);
begin
if Table1.FieldByName('Salary').AsCurrency>40000 then
begin
DBGrid1.Canvas.Font.Color:=clWhite;
DBGrid1.Canvas.Brush.Color:=clBlack;
end;
if DataCol = 4 then //4 th column is 'Salary'
DBGrid1.DefaultDrawColumnCell
(Rect, DataCol, Column, State);
end;
If an employee's salary is greater than 40 thousand, its Salary cell is displayed in Black and text is displayed in White.

Conclusion
Techniques described in this article can really distinguish you from others, but be careful: too much coloring is too much... Now, when I look, there are far too much colors in these examples.

How about graphics?

Image in a TDBGrid cell

component has many nice features and is more powerful than you would have thought. The "standard" DBGrid does its job of displaying and manipulating records from a dataset in a tabular grid. However, there are many ways (and reasons) why you should consider customizing the output of a DBGrid...

Thursday, October 25, 2007

New...Access Database from Delphi

The Delphi Project
At design time Our task is to have Delphi do all the work. We want to create a new database from code, add all three tables from code, add indexes from code and even set up a referential integrity between those tables - again from code.

As usual, have an empty Delphi form. Add two button component. Add a TADOConnection, TADOCommand. We'll use TADOCommand with a DDL language to create and link tables. Add a TADOXCatalog component (ActiveX page). The TADOXCatalog will do the trick of creating a new database. Let the name of the first button be btnNewDatabase (caption: 'Create database'), the second one should be called btnAddTables (caption: 'Create and link tables'). All the other components should have the default name.
In this chapter we'll link those components from code. Therefore, you do not need to set up a ConnectionString for the ADOConnection component and the Connection property for the ADOCOmmand component now.

New...Database
Before we move on to creating tables and linking them we have to create a new (empty) database. This is the code:

procedure TForm1.btnNewDatabaseClick(Sender: TObject);
var
DataSource : string;
dbName : string;
begin
dbName:='c:\aboutdelphi.mdb';

DataSource :=
'Provider=Microsoft.Jet.OLEDB.4.0' +
';Data Source=' + dbName +
';Jet OLEDB:Engine Type=4';

ADOXCatalog1.Create1(DataSource);
end;

Would you belive - simple as that. Obviously the ADOXCatalog has a method called Create1 that creates a new database. Pretty unusual since we've accustomed that Create methods are used to create an object from a class. The ADOXCatalog really has a Create method which has nothing in common to Create1.

The variable DataSource looks pretty much like a standard connection string for the TADOConnection component. There is only one addition, the Jet OLEDB:Engine Type=X part. Engine type 4 is used to create an MS Access 97 database, type 5 is for MS Access 2000.

Note that the above code does not check for the existance of the c:\aboutdelphi.mdb database. If you run this code twice it will complain that the databae already exists.

Add table, create index, set referential integrity
The next step is to create all tables (three of them), add indexes, and create referential integrity. Even though we could use ADOX, that is, TADOXTable, TADOXKey, etc. I'm somehow more familiar with the (standard) DDL language and the TADOCommand component. Back in the chapter 11 of this course we discussed database tables porting issues. This time we'll create tables from nothing.
The following peaces of code are to be placed inside the button's btnAddTables OnClick even handler, I'll slice the code and add some explanations in between.

First, we need to connect to the newly created database with the TADOConnection component. Since we've left the ADOCommand unattached to ADOConnection - we'll link them from code (this should be obvious by now):

procedure TForm1.btnAddTablesClick(Sender: TObject);
var
DataSource : string;
cs : string;
begin
DataSource :=
'Provider=Microsoft.Jet.OLEDB.4.0'+
';Data Source=c:\aboutdelphi.mdb'+
';Persist Security Info=False';

ADOConnection1.ConnectionString := DataSource;
ADOConnection1.LoginPrompt := False;
ADOCommand1.Connection := ADOConnection1;
...

Second, we create both Types and Authors tables, the structures are given in the first chapter. To build a new table with DDL by using the Jet SQL, we use the CREATE TABLE statement by providing it the name the table, name the fields, and fiedl type definitions. Then, the Execute method of the ADOCommand component is used.

...
cs:='CREATE TABLE Types (typename TEXT(50))';
ADOCommand1.CommandText := cs;
ADOCommand1.Execute;


cs:='CREATE TABLE Authors (' +
'authorname TEXT(50),' +
'email TEXT(50),' +
'web TEXT(50))';
ADOCommand1.CommandText := cs;
ADOCommand1.Execute;
...

Next, we add indexes to those two tables. When you apply an index to a table, you are specifying a certain arrangement of the data so that it can be accessed more quickly. To build an index on a table, you must name the index, name the table to build the index on, name the field or fields within the table to use, and name the options you want to use. You use the CREATE INDEX statement to build the index. There are four main options that you can use with an index: PRIMARY, DISALLOW NULL, IGNORE NULL, and UNIQUE. The PRIMARY option designates the index as the primary key for the table.

...
cs:='CREATE INDEX idxPrimary '+
'ON Types (typename) WITH PRIMARY';
ADOCommand1.CommandText := cs;
ADOCommand1.Execute;

cs:='CREATE INDEX idxPrimary '+
'ON Authors (authorname) WITH PRIMARY';
ADOCommand1.CommandText := cs;
ADOCommand1.Execute;
...

Finally, we add the last table. Applications table is linked with both Types and Authors in a master detail relationship. Back in the last chapter we were discussing one-to-many relationships that define the following: for every record in the master table, there are one or more related records in the child table. In our case, one Author (Authors table) can post more Applications; and the Application can be of some type.
When defining the relationships between tables, we use the CONSTRAINT declarations at the field level. This means that the constraints are defined within a CREATE TABLE statement.

...
cs:='CREATE TABLE Applications ('+
' Name TEXT(50),'+
' Description TEXT(50),'+
' Author TEXT(50) CONSTRAINT idxauthor '+
'REFERENCES Authors (authorname),'+
' Type TEXT(50) CONSTRAINT idxtype '+
'REFERENCES Types (typename),'+
' [Size] FLOAT,'+
' Cost CURRENCY,'+
' DateUpl DATETIME,'+
' Picture LONGBINARY)';

ADOCommand1.CommandText := cs;
ADOCommand1.Execute;

end;//btnAddTablesClick

That's it. Now run the project, click the btnNewDatabase button, click the btnAddTables button and you have a new (empty) aboutdelphi.mdb database in the root of the C disk. If you have MS Access installed on your system you can open this database with it and check that all thje tables are here and in the Relationships window all the tables are linked.

Implementing "Contains Focus" for Delphi's Container Controls: TPanel, TGroupBox

In complex form designs where you have dozens of controls contained in several containers it is sometimes necessary to find out whether the focused control (the one with the input focus) is a child of some overall parent of a group of controls.

Picture the next design: you are using frame objects to group controls together (or any other type of container control). You are dynamically instatiating the frame object(s) to be arranged on the form at run-time.

At some point in the life of the application you need to figure out if a specific frame has the input focus - or better to say - if the frame contains the control with the input focus.

TWinControl.Contains Focus?

The ContainsFocus focus function returns true if the control, or one of its child controls, currently has the input focus.
function ContainsFocus(control : TWinControl) : boolean;
var
. focusedHandle : HWND;
. focusedControl : TWinControl;
begin
. focusedHandle := Windows.GetFocus() ;

. focusedControl := FindControl(focusedHandle) ;

. if focusedControl = nil then
. result := false
. else
. begin

. result := control.ContainsControl(focusedControl) ;
. end;
end;
Let's say you have a few Edit boxes inside a Panel inside a GroupBox (GroupBox1).

If you need to figure out whether the GroupBox contains the control with the input focus (active control) you can call the ContainsFocus, as in:

if ContainsFocus(GroupBox1) then
begin
// read from all edit boxes and
// store the values somewhere

end;

Wednesday, October 24, 2007

Virtual Key Codes

Windows defines special constants for each key the user can press. The virtual-key codes identify various virtual keys. These constants can then be used to refer to the keystroke when using Delphi and Windows API calls or in an OnKeyUp or OnKeyDown event handler. Virtual keys mainly consist of actual keyboard keys, but also include "virtual" elements such as the three mouse buttons. Delphi defines all constants for Windows virtual key codes in the Windows unit.

Here are some of the Delphi articles that deal with the keyboard and VK codes:
· Keyboard Symphony
Delphi For Beginners: Get familiar with the OnKeyDown, OnKeyUp, and onKeyPress event procedures to respond to various key actions or handle and process ASCII characters along with other special purpose keys.
· How to Translate a Virtual Key Code to a Character
Windows defines special constants for each key the user can press. The virtual-key codes identify various virtual keys. In Delphi, the OnKeyDown and OnKeyUp events provide the lowest level of keyboard response. To use OnKeyDown or OnKeyUp to test for keys the user presses, you must use Virtual key codes to get the key pressed. Here's how to translate the virtual key code to the corresponding Windows character.
· Touch Me - I'm Untouchable
Intercepting keyboard input for controls that cannot receive the input focus. Working with keyboard hooks from Delphi.
· ENTERing TAB
Using the Enter key like a Tab key with Delphi controls.
· Abort a Loop by Pressing a Key
Use the VK_ESCAPE to abort a (for) loop.
· Use Arrow Keys to Move Between Controls
The UP and DOWN arrow keys are virtually useless in edit controls. So why not use them for navigating between fields.
· Shift, Ctrl, Alt Key Checking
Using virtual key codes to check the key state.
· Simulating keystrokes from code
A handy function to simulate the pressing of keyboard keys.

The following table shows the symbolic constant names, hexadecimal values, and keyboard equivalents for the virtual-key codes used by Windows. Some Windows 2000 and OEM specific constants are missing, the entire list is available from Microsoft. The codes are listed in numerical order.

Symbolic
constant name
Value
(hexadecimal)
Keyboard (or mouse) equivalent
VK_LBUTTON 01 Left mouse button
VK_RBUTTON 02 Right mouse button
VK_CANCEL 03 Control-break processing
VK_MBUTTON 04 Middle mouse button (three-button mouse)
VK_BACK 08 BACKSPACE key
VK_TAB 09 TAB key
VK_CLEAR 0C CLEAR key
VK_RETURN 0D ENTER key
VK_SHIFT 10 SHIFT key
VK_CONTROL 11 CTRL key
VK_MENU 12 ALT key
VK_PAUSE 13 PAUSE key
VK_CAPITAL 14 CAPS LOCK key
VK_ESCAPE 1B ESC key
VK_SPACE 20 SPACEBAR
VK_PRIOR 21 PAGE UP key
VK_NEXT 22 PAGE DOWN key
VK_END 23 END key
VK_HOME 24 HOME key
VK_LEFT 25 LEFT ARROW key
VK_UP 26 UP ARROW key
VK_RIGHT 27 RIGHT ARROW key
VK_DOWN 28 DOWN ARROW key
VK_SELECT 29 SELECT key
VK_PRINT 2A PRINT key
VK_EXECUTE 2B EXECUTE key
VK_SNAPSHOT 2C PRINT SCREEN key
VK_INSERT 2D INS key
VK_DELETE 2E DEL key
VK_HELP 2F HELP key

30 0 key

31 1 key

32 2 key

33 3 key

34 4 key

35 5 key

36 6 key

37 7 key

38 8 key

39 9 key

41 A key

42 B key

43 C key

44 D key

45 E key

46 F key

47 G key

48 H key

49 I key

4A J key

4B K key

4C L key

4D M key

4E N key

4F O key

50 P key

51 Q key

52 R key

53 S key

54 T key

55 U key

56 V key

57 W key

58 X key

59 Y key

5A Z key
VK_NUMPAD0 60 Numeric keypad 0 key
VK_NUMPAD1 61 Numeric keypad 1 key
VK_NUMPAD2 62 Numeric keypad 2 key
VK_NUMPAD3 63 Numeric keypad 3 key
VK_NUMPAD4 64 Numeric keypad 4 key
VK_NUMPAD5 65 Numeric keypad 5 key
VK_NUMPAD6 66 Numeric keypad 6 key
VK_NUMPAD7 67 Numeric keypad 7 key
VK_NUMPAD8 68 Numeric keypad 8 key
VK_NUMPAD9 69 Numeric keypad 9 key
VK_SEPARATOR 6C Separator key
VK_SUBTRACT 6D Subtract key
VK_DECIMAL 6E Decimal key
VK_DIVIDE 6F Divide key
VK_F1 70 F1 key
VK_F2 71 F2 key
VK_F3 72 F3 key
VK_F4 73 F4 key
VK_F5 74 F5 key
VK_F6 75 F6 key
VK_F7 76 F7 key
VK_F8 77 F8 key
VK_F9 78 F9 key
VK_F10 79 F10 key
VK_F11 7A F11 key
VK_F12 7B F12 key
VK_F13 7C F13 key
VK_F14 7D F14 key
VK_F15 7E F15 key
VK_F16 7F F16 key
VK_F17 80H F17 key
VK_F18 81H F18 key
VK_F19 82H F19 key
VK_F20 83H F20 key
VK_F21 84H F21 key
VK_F22 85H F22 key
VK_F23 86H F23 key
VK_F24 87H F24 key
VK_NUMLOCK 90 NUM LOCK key
VK_SCROLL 91 SCROLL LOCK key
VK_LSHIFT A0 Left SHIFT key
VK_RSHIFT A1 Right SHIFT key
VK_LCONTROL A2 Left CONTROL key
VK_RCONTROL A3 Right CONTROL key
VK_LMENU A4 Left MENU key
VK_RMENU A5 Right MENU key
VK_PLAY FA Play key
VK_ZOOM FB Zoom key