Delphi default dataset action Tdatasetdelete

751 Views Asked by At

One of the wonderful things of Delphi is a TActionlist. Even better are default TActions like TDataset-actions. I have one form with several simple tables. So I let Delphi decide which datasource/table is active with several TDatasetinsert/delete/edit etc.

But now I want the delete action having a dialogbox "Are you sure" or something. If I interfere on the execute event of the action, the action does seem to stop after the dialog. So I want to do the delete action myself like somedatasource.dataset.delete. But I can't figure out which datasource is active for this TDatasetdelete.

The TDatasetdelete has a datasource property but this defaults to nil and reading it gives an access violation. Even if I leave it unassigned, a data row is deleted from one of my datasources when the TDatasetdelete executes. In those circumstances, how do I find out which datasource is "active", in other words, which datasource it will use when it executes.

1

There are 1 best solutions below

14
On

Update: I think I now know understand what you're actually asking, which is that even if you leave the DataSetDelete action's DataSource unassigned, how is it that the action manages somehow to "know" which datasource to operate upon?

There is an explanation for this if any of your datasources are connected to TDBGrids or any other DB-aware component whose datalink contains code similar to the following:

function TCustomDBGrid.UpdateAction(Action: TBasicAction): Boolean;
begin
  Result := (DataLink <> nil) and DataLink.UpdateAction(Action);
end;

If you put a breakpoint on the Result := ... you will find that it is called repeatedly while the app is running.

Now try this with my code below:

  1. Disconnect the two DBGrids from their repective datasource, then compile and run:

    Result: The DataSetDelete menu item is disabled(!).

  2. Next connect DBGrid2 to DataSource2. Compile and run.

    Result: The DataSetDelete menu item is enabled and clicking it deletes the current row from CDS2.

  3. Next connect DBGrid1 to DataSource1. Compile and run.

    Result: The DataSetDelete menu item is enabled and clicking it deletes the current row from CDS1.

As you can see, unless your code explicitly sets the DataSetAction's DataSource property, this action operates on the datasource of the first datalink which returns True from a DB-aware component's UpdateAction function.

In other words, it's not so much that the DataSetDelete action "knows" which datasource to use if you leave the DataSetDelete action's DataSource property unassigned, but rather that the DB-aware components' datalinks which tell the action which datasource is active.

Update 2 To see what I mean, remove any handler you have have at the moment for the DataSetDelete's OnExecute. Then, put a breakpoint on TDataSetDelete.ExecuteTarget in DBActns.Pas. When it trips, look at the call stack, and you'll find that it is being called from TCustomDBGrid.ExecuteAction, so the identity of the dataset is being passed to the DataSetDelete action, so I think that there is no way to find out the dataset's identity from the DataSetDelete action itself.

However, there is a simple way around this. Attach the following BeforeDelete handler to each of your datasets:

procedure TCDSForm.CDS1BeforeDelete(DataSet: TDataSet);
begin
  if MessageDlg(DataSet.Name + ': Delete record?', mtConfirmation, [mbYes, mbNo], 0) <>  mrYes then
    Abort;
end;

Then, whether you attempt to delete a dataset record, whether using the DataSetDelete action or not, this event handler will be called, and if the user doesn't respond "Yes", the Abort procedure is called, raising a silent exception which prevents the deletion from proceeding.

==============

Original answer follows. I'll tidy it up later

If I'm understanding you correctly, the sample project below should do what you want.

My question is: how can I read the active datasource-componentname

The answer to this is that the TDataSetDelete action has a DataSource property and it's just a question of setting that to which datasource you want to be active.

"Delphi knows what datasource is active" No, of course it doesn't, how could it possibly until you tell it what datasource you want the DataSetDelete action to operate upon? And the way you do that is to set its DataSource property. To follow what I mean, compile the code below, set a breakpoint on

Caption := 'Execute'

inside DataSetDelete1Execute, then compile & run the project and click the DataSetDelete1Execute menu item. When the breakpoint trips, evaluate `TDataSetDelete(Sender).DataSource. You will find the value is Nil, because you haven't (yet) told the action which datasource the action is to operate upon.

Then, uncomment the line

SetDataSource(DataSource1);

in FormCreate and repeat the evaluation. Now the action knows, because you've told it, which datasource it should consider to be active.

In case you missed it first time, it is the line

  DatasetDelete1.DataSource := FDataSource;

in procedure TCDSForm.SetDataSource(const Value: TDataSource) which sets the dataset that the DatasetDelete1 action uses.

Btw, there is no "magic" to any of this - look at the Delphi source

function TDataSetAction.GetDataSet(Target: TObject): TDataSet;
begin
  { We could cast Target as a TDataSource since HandlesTarget "should" be
    called before ExecuteTarget and UpdateTarget, however, we're being safe. }
  Result := (Target as TDataSource).DataSet;
end;

and

procedure TDataSetDelete.ExecuteTarget(Target: TObject);
begin
  GetDataSet(Target).Delete;
end;

Code (Updated):

unit cdsActionListu;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  Grids, DBGrids, DB, DBClient, StdCtrls, ExtCtrls, DBCtrls, ActnList,
  DBActns, Menus;

type

  TCDSForm = class(TForm)
    CDS1: TClientDataSet;
    DataSource1: TDataSource;
    DBGrid1: TDBGrid;
    DBNavigator1: TDBNavigator;
    DBGrid2: TDBGrid;
    CDS2: TClientDataSet;
    DataSource2: TDataSource;
    DBNavigator2: TDBNavigator;
    ActionList1: TActionList;
    DataSetDelete1: TDataSetDelete;
    MainMenu1: TMainMenu;
    actSelectDataSource: TAction;
    ListBox1: TListBox;
    DataSetDelete11: TMenuItem;
    procedure DataSetDelete1Execute(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure ListBox1Click(Sender: TObject);
  private
    FDataSource: TDataSource;
    procedure SetDataSource(const Value: TDataSource);
  public
    property ActiveDataSource : TDataSource read FDataSource write SetDataSource;
  end;

var
  CDSForm: TCDSForm;

implementation

{$R *.DFM}


function CreateField(AFieldClass : TFieldClass; AOwner : TComponent; ADataSet : TDataSet;
AFieldName, AName : String; ASize : Integer; AFieldKind : TFieldKind) : TField;
begin
  Result := AFieldClass.Create(AOwner);
  Result.FieldKind := AFieldKind;
  Result.FieldName := AFieldName;
  Result.Name := AName;
  Result.Size := ASize;
  Result.DataSet := ADataSet;
end;

procedure TCDSForm.DataSetDelete1Execute(Sender: TObject);
begin
  Caption := 'Execute';
  { uncomment the following to actually delete the record
      if MessageDlg('Delete record?', mtConfirmation, [mbYes, mbNo], 0) = mrYes then begin
          TDataSetDelete(Sender).DataSource.DataSet.Delete;
        end;
  }

end;

procedure TCDSForm.FormCreate(Sender: TObject);
var
  Field : TField;
begin
  Field := CreateField(TIntegerField, Self, CDS1, 'ID', 'CDS1ID', 0, fkData);
  Field := CreateField(TStringField, Self, CDS1, 'StringField', 'CDS1Stringfield', 40, fkData);

  CDS1.CreateDataSet;
  CDS1.InsertRecord([1, 'CDS1 Value1']);
  CDS1.InsertRecord([2, 'CDS1 Value2']);

  Field := CreateField(TIntegerField, Self, CDS2, 'ID', 'CDS2ID', 0, fkData);
  Field := CreateField(TStringField, Self, CDS2, 'StringField', 'CDS2Stringfield', 40, fkData);

  CDS2.CreateDataSet;
  CDS2.InsertRecord([1, 'CDS2 Value1']);
  CDS2.InsertRecord([2, 'CDS2 Value2']);

  Listbox1.Items.AddObject(Datasource1.Name, DataSource1);
  Listbox1.Items.AddObject(Datasource2.Name, DataSource2);

//  SetDataSource(DataSource1);
end;

procedure TCDSForm.ListBox1Click(Sender: TObject);
var
  Index : Integer;
begin
  Index := Listbox1.ItemIndex;
  SetDataSource(TDataSource(Listbox1.Items.Objects[Index]));
end;

procedure TCDSForm.SetDataSource(const Value: TDataSource);
var
  Index : Integer;
begin
  FDataSource := Value;
  DatasetDelete1.DataSource := FDataSource;
  Index := ListBox1.Items.IndexOf(Value.Name);
  if Index <> ListBox1.ItemIndex then
    ListBox1.ItemIndex := Index;
  Caption := 'Active DS ' + FDataSource.Name;
end;