Board index » delphi » More info re TUpdateSQL use

More info re TUpdateSQL use

Thank you for your interest.

The purpose of the section is to use a DBGrid to select one or more
medications to be acted upon.  A RadioGroup is used to 0) multiselect
medications 1) change the medication stop date field from null to
whatever 2) insert a copy of an old record with some fields containing
old information and some fields containing new information regarding
refills
3) insert a copy of an old record with some fields containing old
information and some fields containing new information about dosage
changes 4) insert a completely new medication record.

Delphi 3, BDE version 2.5, TQuery with SQL selecting a subset of
records, TQuery.Filtered is false, and the code is as follows:

procedure TForm1.DBGrid7Enter(Sender: TObject);
begin
  With RadioGroup1 do
  Case ItemIndex of
    0: begin
         MedUpdateQuery.CachedUpdates := False;
         MedUpdateQuery.CachedUpdates := True;
       end;
    1: begin
         MedUpdateQuery.CachedUpdates := True;
       end;
    2: begin
         MedUpdateQuery.CachedUpdates := True;
       end;
    3: begin
         MedUpdateQuery.CachedUpdates := True;
       end;
    4: begin
         MedUpdateQuery.CachedUpdates := False;
         LastMedOrder.Close;
         LastMedOrder.IndexName := 'ByMedOrder';
         LastMedOrder.Open;
         LastMedOrder.FindLast;
         LastOrder := LastMedOrderLastMedOrder.AsInteger;
         MedUpdate.Close;
         MedUpdate.Filtered := True;
         MedUpdate.Open;
         MedUpdate.Insert;
      end;
   end;                
end;

procedure TForm1.DBGrid7Exit(Sender: TObject);
begin
  MedUpdateQuery.Filtered := False;
end;

procedure TForm1.DBGrid7CellClick(Column: TColumn);
begin
  if (RadioGroup1.ItemIndex = 0) then
    begin
      MedUpdateQuery.Filtered := False;
      MedUpdateQuery.CachedUpdates := True;
      If DBGrid7.SelectedRows.CurrentRowSelected
        then MedUpdateQuerySelected.NewValue := True
        else MedUpdateQuerySelected.NewValue := Falsee;
    end;
end;

procedure TForm1.MedUpdateQueryUpdateRecord(DataSet: TDataSet;
  UpdateKind: TUpdateKind; var UpdateAction: TUpdateAction);
begin
 with MedUpdateQuery.UpdateObject as TUpdateSQL do
 begin
   If (RadioGroup1.ItemIndex = 0) and (UpdateKind = ukModify) then
     begin
       MedUpdateQuerySelected.NewValue := False;
       UpdateSQL1.Apply(ukModify);
       MedUpdateQuery.ApplyUpdates;
     end;
   If (RadioGroup1.ItemIndex = 1) and (UpdateKind = ukModify) then
     begin
       MedUpdateQuery.UpdateRecordTypes := [rtModified];
       MedUpdateQueryMedStop.NewValue :=
MedUpdateQueryVisitDate.OldValue;
       UpdateSQL1.Apply(ukModify);
       MedUpdateQuery.ApplyUpdates;
     end;
   If (UpdateKind = ukInsert) and (RadioGroup1.ItemIndex = 2) then
     begin
       MedUpdateQueryMedication.NewValue :=
MedUpdateQueryMedication.OldValue;
       MedUpdateQueryDose.NewValue := MedUpdateQueryDose.OldValue;
       MedUpdateQueryFrequency.NewValue :=
MedUpdateQueryFrequency.OldValue;
       MedUpdateQueryPRN.NewValue := MedUpdateQueryPRN.OldValue;
       MedUpdateQueryRefillDate.NewValue :=
MedUpdateQueryVisitDate.NewValue;
       MedUpdateQueryMedCode.NewValue := MedUpdateQueryMedCode.OldValue;
       MedUpdateQueryMedOrder.NewValue :=
MedUpdateQueryMedOrder.OldValue;
       MedUpdateQueryRefillDate.NewValue := Now;
       MedUpdateQuerySelected.NewValue :=
MedUpdateQuerySelected.OldValue;
      Apply(ukInsert);
      MedUpdateQuery.ApplyUpdates;
     end;
 If (RadioGroup1.ItemIndex = 3) and (UpdateKind = ukInsert) then
     begin
       MedUpdateQueryMedication.NewValue :=
MedUpdateQueryMedication.OldValue;
       MedUpdateQuerySelected.NewValue :=
MedUpdateQuerySelected.OldValue;
       MedUpdateQueryRefillDate.NewValue := Now;
       UpdateSQL1.Apply(ukInsert);
       MedUpdateQuery.ApplyUpdates;

     end;  
  If (RadioGroup1.ItemIndex <> 4) then
    UpdateAction := uaApplied;
    Medupdatequery.ApplyUpdates;
  end;
end;

Thoughts?

 

Re:More info re TUpdateSQL use


Jeffery,

This message was a lot of work for you and I have to tell you that I
almost missed it. I was watching the thread that you started with your
last message and you started a new thread with this one. If you hadn't
put "TUpdateSQL" in the title of this one it would have been lost in
the hundreds of messages on this group and I wouldn't have seen it. In
the future, just reply to the message you get and it will find itself
safely in the right thread.

Anyhow, let's see if I can help you with the cached updates that don't
update. (Any lurkers who want to jump in are welcome<g>.)

My comments are below, in your code. Before that, however, I have this
general comment. You're doing a lot more here than I think you need to
do, there's a lot of detail and some I don't understand. There's too
much here to fix in one swipe anyway, so I'll make comments geared
toward rethinking it and then you may be able to clean this up some
and try again if it doesn't work. Here goes.

Quote
Jeffrey Berlant <jberl...@pol.net> wrote:
>Thank you for your interest.

>The purpose of the section is to use a DBGrid to select one or more
>medications to be acted upon.  A RadioGroup is used to 0) multiselect
>medications 1) change the medication stop date field from null to
>whatever 2) insert a copy of an old record with some fields containing
>old information and some fields containing new information regarding
>refills
>3) insert a copy of an old record with some fields containing old
>information and some fields containing new information about dosage
>changes 4) insert a completely new medication record.

>Delphi 3, BDE version 2.5, TQuery with SQL selecting a subset of
>records, TQuery.Filtered is false, and the code is as follows:

>procedure TForm1.DBGrid7Enter(Sender: TObject);
>begin
>  With RadioGroup1 do
>  Case ItemIndex of
>    0: begin
>         MedUpdateQuery.CachedUpdates := False;
>         MedUpdateQuery.CachedUpdates := True;
>       end;
>    1: begin
>         MedUpdateQuery.CachedUpdates := True;
>       end;
>    2: begin
>         MedUpdateQuery.CachedUpdates := True;
>       end;
>    3: begin
>         MedUpdateQuery.CachedUpdates := True;
>       end;
>    4: begin
>         MedUpdateQuery.CachedUpdates := False;
>         LastMedOrder.Close;
>         LastMedOrder.IndexName := 'ByMedOrder';
>         LastMedOrder.Open;
>         LastMedOrder.FindLast;
>         LastOrder := LastMedOrderLastMedOrder.AsInteger;
>         MedUpdate.Close;
>         MedUpdate.Filtered := True;
>         MedUpdate.Open;
>         MedUpdate.Insert;
>      end;
>   end;                
>end;

I'm a bit confused at this point. Here's why:
You've got several queries here (MedUpdateQuery, MedUpdate,
LastMedOrder). I don't know which is attached to the grid7 or what
roles the others play.

But there's a more important issue. You're turning
MedUpdateQuery.CachedUpdates off and on and I don't know why. It's
possible to do this, of course, but it's not normal in my experience
and I think it would require a very special kind of circumstance.

The _ordinary_ way to handle cached updates is to set the property for
the dataset (Tquery in this case) and leave it alone. A dataset is
either cached updates or it is not. The reason for choosing cached
updates or not usually has to do with the business requirement of the
dataset which typically doesn't change within a session. Trying to
toggle it only makes your job harder.

For example, suppose we want to maintain a list of customer names and
addresses. This requires one query and a grid attached to that query.
If we set cached updates to false, then the records are updated each
time you move from record to record. If cached updates is on, then all
the updates (inserts, deletes, changes) take place when you call
ApplyUpdates. There's no reason in this example to toggle cached
updates on or off during the session.

Also, cached updates must be on before you open the dataset and you
can't toggle it on and off during one Open-ing. This is not the way
cached updates was designed to be used and I suspect that the results
would be, as we say, undefined or unpredictable - that means terrible.

Quote

>procedure TForm1.DBGrid7Exit(Sender: TObject);
>begin
>  MedUpdateQuery.Filtered := False;
>end;

>procedure TForm1.DBGrid7CellClick(Column: TColumn);
>begin
>  if (RadioGroup1.ItemIndex = 0) then
>    begin
>      MedUpdateQuery.Filtered := False;

You always set this value to false for this query. Perhaps you set it
to true in other places in your code not shown here. If not, then just
set this property to False in the Object Inspector and delete these
references. They would not be needed in this case. Anyhow, it's a good
idea to set Filtered to False in any dataset where cached updates is
true. The two properties don't like each other sometimes.
Quote
>      MedUpdateQuery.CachedUpdates := True;

If MedUpdateQuery is already open then this may cause trouble. You
need to set cached updates before opening.
Quote
>      If DBGrid7.SelectedRows.CurrentRowSelected
>    then MedUpdateQuerySelected.NewValue := True
>    else MedUpdateQuerySelected.NewValue := Falsee;
>    end;
>end;

>procedure TForm1.MedUpdateQueryUpdateRecord(DataSet: TDataSet;
>  UpdateKind: TUpdateKind; var UpdateAction: TUpdateAction);
>begin
> with MedUpdateQuery.UpdateObject as TUpdateSQL do
> begin
>   If (RadioGroup1.ItemIndex = 0) and (UpdateKind = ukModify) then
>     begin
>       MedUpdateQuerySelected.NewValue := False;
>       UpdateSQL1.Apply(ukModify);

the line above is the one that actually updates the database, the next
line is not needed - see below.
Quote
>       MedUpdateQuery.ApplyUpdates;

This is exactly the wrong place to put this method.
MedUpdateQuery.ApplyUpdates starts the MedUpdateQuery OnUpdateRecord
event, so you are making a recursive call here by asking the event to
fire itself. ApplyUpdates should be called outside this event.
Quote
>     end;
>   If (RadioGroup1.ItemIndex = 1) and (UpdateKind = ukModify) then
>     begin
>       MedUpdateQuery.UpdateRecordTypes := [rtModified];
>       MedUpdateQueryMedStop.NewValue :=
>MedUpdateQueryVisitDate.OldValue;
>       UpdateSQL1.Apply(ukModify);
>       MedUpdateQuery.ApplyUpdates;

Delete this^^^^^^^^^^^^^^^^^^^^^^
Quote
>     end;
>   If (UpdateKind = ukInsert) and (RadioGroup1.ItemIndex = 2) then
>     begin

All of these assignment are very interesting and this is certainly
part of the problem you are seeing. With cached updates, if Updatekind
is ukInsert, then _by definition_ OldValue is blank (or null) and
NewValue contains the value you want to insert. This is the state of
things _before_ OnUpdateRecord. So here you are saying NewValue :=
OldValue and so you are setting all your values to blank and then
inserting blanks into the database. If you get any result at all here,
I would expect it to be a bunch of blank rows in the database.

Anyway, that's what you are telling cached updates to do even though
it's not what you wanted. What should have happened at this point is
that the new values were already added to the grid (I'm assuming that
the grid7 dataset is MedUpdateQuery). And so the new data is already
in the NewValue properties. And so all these assignments not needed.
Just delete them and go with UpdateSQL1.Apply(ukInsert);

- Show quoted text -

Quote
>       MedUpdateQueryMedication.NewValue :=
>MedUpdateQueryMedication.OldValue;
>       MedUpdateQueryDose.NewValue := MedUpdateQueryDose.OldValue;
>       MedUpdateQueryFrequency.NewValue :=
>MedUpdateQueryFrequency.OldValue;
>       MedUpdateQueryPRN.NewValue := MedUpdateQueryPRN.OldValue;
>       MedUpdateQueryRefillDate.NewValue :=
>MedUpdateQueryVisitDate.NewValue;
>       MedUpdateQueryMedCode.NewValue := MedUpdateQueryMedCode.OldValue;
>       MedUpdateQueryMedOrder.NewValue :=
>MedUpdateQueryMedOrder.OldValue;
>       MedUpdateQueryRefillDate.NewValue := Now;
>       MedUpdateQuerySelected.NewValue :=
>MedUpdateQuerySelected.OldValue;
>      Apply(ukInsert);
>      MedUpdateQuery.ApplyUpdates;

Delete this^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Quote
>     end;
> If (RadioGroup1.ItemIndex = 3) and (UpdateKind = ukInsert) then
>     begin
>       MedUpdateQueryMedication.NewValue :=
>MedUpdateQueryMedication.OldValue;
>       MedUpdateQuerySelected.NewValue :=
>MedUpdateQuerySelected.OldValue;
>       MedUpdateQueryRefillDate.NewValue := Now;
>       UpdateSQL1.Apply(ukInsert);
>       MedUpdateQuery.ApplyUpdates;

Delete this^^^^^^^^^^^^^^^^^^^^^^^^
Quote

>     end;  
>  If (RadioGroup1.ItemIndex <> 4) then
>    UpdateAction := uaApplied;
>    Medupdatequery.ApplyUpdates;

Delete this^^^^^^^^^^^^^^^^^^^^^

Quote
>  end;
>end;

>Thoughts?

Well, you've seen them and I hope you're not discouraged. Delphi
documentation for this feature is not all it should be and you remind
me of me the first time I tried it.

You should redo this code with these three thoughts:
1. a dataset.CachedUpdates is either true or false throughout the
session, not both.
2. Use ApplyUpdates _outside_ the OnUpdateRecord
3. When you call ApplyUpdates and so start OnUpdateRecord, cached
updates already knows what records have been changed, added or deleted
and has already set the NewValue and OldValue values appropriately.
You don't have to set them. The only time you need to fiddle with them
is when you want to make exceptions.

Good Luck.

Phil Cain

Re:More info re TUpdateSQL use


Quote
Philip Cain wrote:

> Jeffery,

> This message was a lot of work for you and I have to tell you that I
> almost missed it. I was watching the thread that you started with your
> last message and you started a new thread with this one. If you hadn't
> put "TUpdateSQL" in the title of this one it would have been lost in
> the hundreds of messages on this group and I wouldn't have seen it. In
> the future, just reply to the message you get and it will find itself
> safely in the right thread.

> Anyhow, let's see if I can help you with the cached updates that don't
> update. (Any lurkers who want to jump in are welcome<g>.)

> My comments are below, in your code. Before that, however, I have this
> general comment. You're doing a lot more here than I think you need to
> do, there's a lot of detail and some I don't understand. There's too
> much here to fix in one swipe anyway, so I'll make comments geared
> toward rethinking it and then you may be able to clean this up some
> and try again if it doesn't work. Here goes.

> Jeffrey Berlant <jberl...@pol.net> wrote:

> >Thank you for your interest.

> >The purpose of the section is to use a DBGrid to select one or more
> >medications to be acted upon.  A RadioGroup is used to 0) multiselect
> >medications 1) change the medication stop date field from null to
> >whatever 2) insert a copy of an old record with some fields containing
> >old information and some fields containing new information regarding
> >refills
> >3) insert a copy of an old record with some fields containing old
> >information and some fields containing new information about dosage
> >changes 4) insert a completely new medication record.

> >Delphi 3, BDE version 2.5, TQuery with SQL selecting a subset of
> >records, TQuery.Filtered is false, and the code is as follows:

> >procedure TForm1.DBGrid7Enter(Sender: TObject);
> >begin
> >  With RadioGroup1 do
> >  Case ItemIndex of
> >    0: begin
> >         MedUpdateQuery.CachedUpdates := False;
> >         MedUpdateQuery.CachedUpdates := True;
> >       end;
> >    1: begin
> >         MedUpdateQuery.CachedUpdates := True;
> >       end;
> >    2: begin
> >         MedUpdateQuery.CachedUpdates := True;
> >       end;
> >    3: begin
> >         MedUpdateQuery.CachedUpdates := True;
> >       end;
> >    4: begin
> >         MedUpdateQuery.CachedUpdates := False;
> >         LastMedOrder.Close;
> >         LastMedOrder.IndexName := 'ByMedOrder';
> >         LastMedOrder.Open;
> >         LastMedOrder.FindLast;
> >         LastOrder := LastMedOrderLastMedOrder.AsInteger;
> >         MedUpdate.Close;
> >         MedUpdate.Filtered := True;
> >         MedUpdate.Open;
> >         MedUpdate.Insert;
> >      end;
> >   end;
> >end;

> I'm a bit confused at this point. Here's why:
> You've got several queries here (MedUpdateQuery, MedUpdate,
> LastMedOrder). I don't know which is attached to the grid7 or what
> roles the others play.

> But there's a more important issue. You're turning
> MedUpdateQuery.CachedUpdates off and on and I don't know why. It's
> possible to do this, of course, but it's not normal in my experience
> and I think it would require a very special kind of circumstance.

> The _ordinary_ way to handle cached updates is to set the property for
> the dataset (Tquery in this case) and leave it alone. A dataset is
> either cached updates or it is not. The reason for choosing cached
> updates or not usually has to do with the business requirement of the
> dataset which typically doesn't change within a session. Trying to
> toggle it only makes your job harder.

> For example, suppose we want to maintain a list of customer names and
> addresses. This requires one query and a grid attached to that query.
> If we set cached updates to false, then the records are updated each
> time you move from record to record. If cached updates is on, then all
> the updates (inserts, deletes, changes) take place when you call
> ApplyUpdates. There's no reason in this example to toggle cached
> updates on or off during the session.

> Also, cached updates must be on before you open the dataset and you
> can't toggle it on and off during one Open-ing. This is not the way
> cached updates was designed to be used and I suspect that the results
> would be, as we say, undefined or unpredictable - that means terrible.

> >procedure TForm1.DBGrid7Exit(Sender: TObject);
> >begin
> >  MedUpdateQuery.Filtered := False;
> >end;

> >procedure TForm1.DBGrid7CellClick(Column: TColumn);
> >begin
> >  if (RadioGroup1.ItemIndex = 0) then
> >    begin
> >      MedUpdateQuery.Filtered := False;
> You always set this value to false for this query. Perhaps you set it
> to true in other places in your code not shown here. If not, then just
> set this property to False in the Object Inspector and delete these
> references. They would not be needed in this case. Anyhow, it's a good
> idea to set Filtered to False in any dataset where cached updates is
> true. The two properties don't like each other sometimes.
> >      MedUpdateQuery.CachedUpdates := True;
> If MedUpdateQuery is already open then this may cause trouble. You
> need to set cached updates before opening.
> >      If DBGrid7.SelectedRows.CurrentRowSelected
> >       then MedUpdateQuerySelected.NewValue := True
> >       else MedUpdateQuerySelected.NewValue := Falsee;
> >    end;
> >end;

> >procedure TForm1.MedUpdateQueryUpdateRecord(DataSet: TDataSet;
> >  UpdateKind: TUpdateKind; var UpdateAction: TUpdateAction);
> >begin
> > with MedUpdateQuery.UpdateObject as TUpdateSQL do
> > begin
> >   If (RadioGroup1.ItemIndex = 0) and (UpdateKind = ukModify) then
> >     begin
> >       MedUpdateQuerySelected.NewValue := False;
> >       UpdateSQL1.Apply(ukModify);
> the line above is the one that actually updates the database, the next
> line is not needed - see below.
> >       MedUpdateQuery.ApplyUpdates;
> This is exactly the wrong place to put this method.
> MedUpdateQuery.ApplyUpdates starts the MedUpdateQuery OnUpdateRecord
> event, so you are making a recursive call here by asking the event to
> fire itself. ApplyUpdates should be called outside this event.
> >     end;
> >   If (RadioGroup1.ItemIndex = 1) and (UpdateKind = ukModify) then
> >     begin
> >       MedUpdateQuery.UpdateRecordTypes := [rtModified];
> >       MedUpdateQueryMedStop.NewValue :=
> >MedUpdateQueryVisitDate.OldValue;
> >       UpdateSQL1.Apply(ukModify);
> >       MedUpdateQuery.ApplyUpdates;
> Delete this^^^^^^^^^^^^^^^^^^^^^^
> >     end;
> >   If (UpdateKind = ukInsert) and (RadioGroup1.ItemIndex = 2) then
> >     begin
> All of these assignment are very interesting and this is certainly
> part of the problem you are seeing. With cached updates, if Updatekind
> is ukInsert, then _by definition_ OldValue is blank (or null) and
> NewValue contains the value you want to insert. This is the state of
> things _before_ OnUpdateRecord. So here you are saying NewValue :=
> OldValue and so you are setting all your values to blank and then
> inserting blanks into the database. If you get any result at all here,
> I would expect it to be a bunch of blank rows in the database.

> Anyway, that's what you are telling cached updates to do even though
> it's not what you wanted. What should have happened at this point is
> that the new values were already added to the grid (I'm assuming that
> the grid7 dataset is MedUpdateQuery). And so the new data is already
> in the NewValue properties. And so all these assignments not needed.
> Just delete them and go with UpdateSQL1.Apply(ukInsert);
> >       MedUpdateQueryMedication.NewValue :=
> >MedUpdateQueryMedication.OldValue;
> >       MedUpdateQueryDose.NewValue := MedUpdateQueryDose.OldValue;
> >       MedUpdateQueryFrequency.NewValue :=
> >MedUpdateQueryFrequency.OldValue;
> >       MedUpdateQueryPRN.NewValue := MedUpdateQueryPRN.OldValue;
> >       MedUpdateQueryRefillDate.NewValue :=
> >MedUpdateQueryVisitDate.NewValue;
> >       MedUpdateQueryMedCode.NewValue := MedUpdateQueryMedCode.OldValue;
> >       MedUpdateQueryMedOrder.NewValue :=
> >MedUpdateQueryMedOrder.OldValue;
> >       MedUpdateQueryRefillDate.NewValue := Now;
> >       MedUpdateQuerySelected.NewValue :=
> >MedUpdateQuerySelected.OldValue;
> >      Apply(ukInsert);
> >      MedUpdateQuery.ApplyUpdates;
> Delete this^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> >     end;
> > If (RadioGroup1.ItemIndex = 3) and (UpdateKind = ukInsert) then
> >     begin
> >       MedUpdateQueryMedication.NewValue :=
> >MedUpdateQueryMedication.OldValue;
> >       MedUpdateQuerySelected.NewValue :=
> >MedUpdateQuerySelected.OldValue;
> >       MedUpdateQueryRefillDate.NewValue := Now;
> >       UpdateSQL1.Apply(ukInsert);
> >       MedUpdateQuery.ApplyUpdates;
> Delete this^^^^^^^^^^^^^^^^^^^^^^^^

> >     end;
> >  If (RadioGroup1.ItemIndex <> 4) then
> >    UpdateAction := uaApplied;
> >    Medupdatequery.ApplyUpdates;
> Delete this^^^^^^^^^^^^^^^^^^^^^
> >  end;
> >end;

> >Thoughts?

> Well, you've seen them and I hope you're not discouraged. Delphi
> documentation for this feature is not all it should be and you remind
> me of me the first time I tried it.

> You should redo this code with these three thoughts:
> 1. a dataset.CachedUpdates is either true or false throughout the
> session, not both.
> 2. Use ApplyUpdates _outside_ the OnUpdateRecord
> 3. When you call ApplyUpdates and so start OnUpdateRecord, cached
> updates already knows what records have been changed, added or deleted
> and has already set the NewValue and OldValue values appropriately.
> You don't have to set them. The only time you need to fiddle with them
> is when you want to make exceptions.

> Good Luck.

> Phil Cain

Phil:

My God, you're good!  Thank you enormously for your very clear comments.
The odd usage for CachedUpdates := True that you caught was grasped at
in my desperation due to the sentence on p. 13-3 of the ...

read more »

Re:More info re TUpdateSQL use


Quote
Jeffrey Berlant <jberl...@pol.net> wrote:
>The odd usage for CachedUpdates := True that you caught was grasped at
>in my desperation due to the sentence on p. 13-3 of the Developer's
>Guide that says,  "When you set CachedUpdates to True, the dataset's
>OnUpdateRecord event is triggered if you provide it."  Since I had no
>other clue as to how to call the OnUpdateRecord event, I figured that
>was the secret.

Well... the folks at Inprise know their stuff when it comes to OOP
but, like most techno-businesses, fall short on editorial style. One
day, maybe they'll learn that gaffs like this cost them market share
<sigh>.

In the meantime, I wish you success in your project and with cached
updates.

Good luck.

Phil

Other Threads