  ==================================================
                  RSD Tips&Tricks
               Based on the questions 
            to the RSD Consulting Service
                 (free for a while)
  ==================================================
  Authors:                             Supported by:
  Andrey Telnov And Roman Eremin        RSD software
  rsd@tibc.tula.ru              http://www.rsd.pp.ru
  ..................................................
  April 24, 1997                RSD Tips&Tricks  #02
  ..................................................

Contents 

1. Avoid the Key violation error  (demo)
2. Create Sorted DBGrid (demo + component, TSortDBGrid )
3. Sort the detail dataset (master-detail relationship) (demo)
4. Use subclassing to add new features to Delphi wincontrols  (demo + component, TTextMacros)

1. Avoid the Key violation error 
Q:
How can I perform a record duplication check?  Before posting a record to the database, I want to make sure that particular record doesn't exist in the database.
I did try to use the BeforePost event and FindKey and Locate but it's doesn't work.  Please give advise. (K.C.W.)
A:
You have not to use the FindKey and Locate methods in the BeforePost event. Because that methods will call post method if the dataset  in dsEdit or dsInsert states. So the Delphi tries to perform post method twice if you call the locate (GotoNearest, FindKey and so on) method in the BeforePost event.
The best way to resolve that problem is in creating the exception block where the Post method is called:

procedure TForm1.PostButtonClick(Sender : TObject)
begin
  try
    {Try to post}
    Query1.Post;
  except
     on E: EDBEngineError do begin
       if(E.ErrorCount > 0) And
        {DBIERR_KEYVIOL = (ERRBASE_INTEGRITY + ERRCODE_KEYVIOL); - Key violation error}
        (E.Errors[0].ErrorCode = DBIERR_KEYVIOL) then
           ShowMessage('You''ve inserted the duplicate record');
    end;
end;

If you use the DBGrid to edit your DB then you have to use the  PostError event:

procedure TForm1.Table1PostError(DataSet: TDataSet; E: EDatabaseError;
  var Action: TDataAction);
Var
 verr : EDBEngineError;
begin
  {Try to convert to EDBEngineError type}
  if(E is EDBEngineError) then
    verr := EDBEngineError(E);
  with verr do begin
    if(ErrorCount > 0) And
    {DBIERR_KEYVIOL = (ERRBASE_INTEGRITY + ERRCODE_KEYVIOL); - Key violation error}
    (Errors[0].ErrorCode = DBIERR_KEYVIOL) then begin
      ShowMessage('You''ve inserted the duplicate record. You have to correct it');
      Action := dafail;
    end;
  end;

See example #005 (avoid the Key violation error) for more information.

2.
Q:
I have a following problem:
I am presenting my data in a DBGrid. Now I would like to sort the data
according to the field which header has been clicked like e.g. in
Explorer and hundreds of other W95 applications. I wonder why there is
no OnHeaderClick event in DBGrid. (M. K.)
A: 
The Delphi DBGrid doesn't do it, but what is a problem ? Lets do it  by ourself !
We need create the new component (TSortDBGrid) which will be inherited from TDBGrid.
We have to override the MouseUp method and add new event.

{Declare new type for sortedDbGrid event}
type  
{Field is the new sorted field}
THeaderDBGridClickEvent = procedure (Sender: TObject; Field : TField;
    Var Sorted : Boolean) of object;

{Declare new component}
  TSortDBGrid = class(TDBGrid)
  private
    FHeaderClick : THeaderDBGridClickEvent;

  protected
    {Override mouseup method}
    procedure MouseUp(Button: TMouseButton; Shift: TShiftState;
      X, Y: Integer); override;
  published
    {Declare}
    property OnHeaderClick : THeaderDBGridClickEvent read FHeaderClick write FHeaderClick; 
  end;

{MouseUp method}
procedure TSortDBGrid.MouseUp(Button: TMouseButton; Shift: TShiftState;
      X, Y: Integer);
Var
  Point : TGridCoord;
  Sorted : Boolean;
begin
  {Get mouse cordinates}
  Point := MouseCoord(X, Y);
  {call old MouseUp method}
  inherited;
  {Get true column index}
  if(dgIndicator in Options) then Dec(Point.X;);
  {If the left mouse button was pressed  on the caption DBGrid then}
  if (Button = mbLeft) And (Point.Y = 0) then
    {Do it at run time only}
    if Not (csDesigning in ComponentState) then begin
      Sorted := False;
      {If assign HeaderClick event and DataSet is Active}
      if Assigned(FHeaderClick) And (DataLink.Active) then
        FHeaderClick(self, Columns[Point.X].Field, Sorted);
    end;
  end;
end;

Install: sortdbgr.pas and see example srtdbgr.dpr:
Example # 006  (TSortDBGrid component)

3. Sort the detail dataset (master-detail relationship)
Q:
I have two tables linked by fields mannum and dmnum which produces the correct search results but when I print the results using QuickReports I end-up with the data being in an unsorted print-out. I was wondering how I could perform a sort before the printing of the results. (D.N.L.)
A:
As I understand the problem is to add the index to the detail dataset. The easy way to avoide this problem is in using Query as the detail dataset instead of Table. You have to assign the Master Source to DataSource property of the detail  Query and add the condition to the SQL. 
For example: CUSTOMER.DB and ORDERS.DB from DBDEMOS. 
They are linked in this way: CUSTOMER.custno <--->> ORDERS.custno.
The SQL of the detail Query (ORDERS) have to be:
Select * From ORDERS
Where custno = :custno

Now if the cursor of the master dataset moves the parameter 'custno' would be changed and detail Query Refreshed. 

And at last add the 'order' to the SQL. For example lets sorted by saledate:
Select * From ORDERS
Where custno = :custno
Order by saledate


Note:
The detail Query will be worked a little slowly with the Local DB then detail Table but if you use the Client/Server architecture the speed may increase in some times. 

See example # 007. 


4. Use subclassing to add new features to Delphi wincontrols
Add new features to wincontrols not overriding them
(Without question. It is our own opinion in using subclassing)
Very often we need to add new features in the standard Delphi components. As usual people create new components based on the components from Delphi VCL. As result we have a lot of components which has the alike features. 
But there is a way to add new features to the wincontrols and doesn't create new wincontrol. You have to replace the WndProc method of the wincontrols - SUBCLASSING.

I've done the TTextMacros components for example. It adds the new feature to TCustomEdit control:
Macros. You can define the macros: The key and text. For example:
The Macro is:
'B,BORLAND = Borland created a lot of great tools for programming. Delphi is one of the most exciting things.'
The key is ALT + B
User may press ALT + B and in the Edit control the next macros text will be added from the cursor position: 
'BORLAND = Borland created a lot of great tools for programming. Delphi is one of the most exciting things.'

I create this component inherited from TCOMPONENT (not from TCustomEdit). In this case TTextMacros will work with all components inherited from TCustomEdit (TEdit, TDBEdit, TMemo, TDBMemo and  any Third Party components which are inherited from TCustomEdit)

The component and the sample with it you can find in sample # 008
Install textmacr.pas unit and see example.

The next code you have to include in any case if you want to use subclassing:

1) Declare the new component

type
  TMyNewComponent = class(TComponent)
  private
    {The name of the WndControl Control}
    FEditControl : TCustomEdit;
    {instance for EditWndProc}
    ObjectInstance : Pointer;
    {the addres of the EditControl wndproc }
    FEditWndProcAdd : Pointer;

    procedure SetEditControl(Value : TCustomEdit);
  protected
    {your wndproc}
    procedure EditWndProc(var Message: TMessage);
  public
    constructor Create(AOwner : TComponent); override;
    destructor Destroy; override;
    procedure Notification(AComponent: TComponent; Operation: TOperation); override;
  published
    property EditControl : TCustomEdit read FEditControl write SetEditControl;
  end;

2) rewrite constructor and destructor

constructor TMyNewComponent.Create(AOwner : TComponent);
begin
  inherited Create(AOwner);
  {Create object instance for EditWndProc}
  ObjectInstance := MakeObjectInstance(EditWndProc);
end;

destructor TMyNewComponent.Destroy;
begin
  {Free object instance}
  if (ObjectInstance <> Nil) then
    FreeObjectInstance(ObjectInstance); 
  inherited Destroy;
end;

3) Write your WndProc

procedure TMyNewComponent.EditWndProc(var Message: TMessage);
begin
  with TMessage(Message) do begin
    {******************************}
    {HERE YOU CAN ADD YOUR OWN CODE}
    {******************************}
    {call the correct wndproc}
    Result := CallWindowProc(FEditWndProcAdd, FEditControl.Handle, Msg, WParam, LParam);
    {******************************}
    {HERE YOU CAN ADD YOUR OWN CODE}
    {******************************}
  end;
end;

4) Replace and restore the wndproc of the control

procedure TMyNewComponent.SetEditControl(Value : TCustomEdit);
begin
  if(FEditControl = Value) then exit;
  {Restore the correct wndproc of the old EditControl}
  if(FEditControl <> Nil) And (FEditWndProcAdd <> Nil) then begin
    SetWindowLong(FEditControl.Handle, GWL_WNDPROC, LongInt(FEditWndProcAdd));
    FEditWndProcAdd := Nil;
  end;
  FEditControl := Value;
  {Save the wndproc of the new EditControl and replace it by our own procedure }
  if(FEditControl <> Nil) And  Not (csDesigning in FEditControl.ComponentState) then begin
    FEditWndProcAdd := Pointer(GetWindowLong(FEditControl.Handle, GWL_WNDPROC));
    SetWindowLong(FEditControl.Handle, GWL_WNDPROC, LongInt(ObjectInstance));
  end;
end;

5) And at last rewrite Notification method to be shure in the correct work of the component

procedure TTMyNewComponent.Notification(AComponent: TComponent; Operation: TOperation);
begin
  inherited;
  {Is the removed component EditControl ?}
  if(AComponent = FEditControl) And (Operation = opRemove) then
    FEditControl := Nil;
  {Is the removed component self component ?}
  if(AComponent = self) And (Operation = opRemove) then
    {call SetEditControl procedure}
    EditControl := Nil;
end;

Install textmacr.pas and see example # 008 for more information.
The another example of using subclassing is RSD TGridSearch component (FREE). This component work with any DBGrid inherited from TCustomDBGrid. You can download it from our web site.


Any comments and sugestions
please e-mail: rsd@tibc.tula.ru 

For questions about Delphi
please e-mail: rsd@tibc.tula.ru 