0

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.

MartynA
  • 30,454
  • 4
  • 32
  • 73
  • I'm sorry, we cannot see your screen or read your mind. Please provide more information about how your application is working. As of now, I cannot make heads or tails out of what your application does. – Jerry Dodge Jul 30 '16 at 17:08
  • Sorry. but for delphi programmers i thought a TDatasetdelete, which is a default TAction, was quit familiar. Sorry about that. – user3415259 Jul 30 '16 at 18:52
  • 1
    @KenWhite: Actually (and much to my surprise), the OP seems to have a point - please see the update to my answer. – MartynA Jul 31 '16 at 10:20

1 Answers1

4

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;
MartynA
  • 30,454
  • 4
  • 32
  • 73
  • The strength of this Tdatasetdelete is that if you have several datasources that could be bound to this action, Delphi knows what datasource is active. So having one delete button with one TDatasetdelete TAction and a dozen tdatasets, the datasource is set dynamicaly at runtime. My question is: how can I read the active datasource-componentname – user3415259 Jul 30 '16 at 19:09
  • @user3415259 I have no idea why you still think that Delphi has some sort of magical mind reader which can detect which dataset is "active". It has no idea. It's up to you to implement. Delphi is a language which expects you to pay close attention to details, and write code or make appropriate changes as needed. Some tasks even require you to override the Delphi RTL or VCL, etc. in order to accomplish what you want. It's just a framework, not artificial intelligence. – Jerry Dodge Jul 31 '16 at 02:53
  • Mindreader are your words. Try it you will see Delphi knows which component is active and connects the datasource of that component to the TDatasetaction in runtime. So it is very convenient to use for the developer. Accept if he wants some interaction as in my case. The datasource is nil if i want to use it. So my question still is: is there a possibility (except for writing my own code to get this done) to see what datasource is active. If i'm totally wrong i would appreciatie some clarity about this TAction. – user3415259 Jul 31 '16 at 08:52
  • "you will see Delphi knows ..." Did you do my test with the breakpoint? Did you get Nil or not? – MartynA Jul 31 '16 at 08:56
  • Sure i did. The datasource remains nil but the action still knows what to do. So i'm really confused about the behavior of this Tdatasetaction. – user3415259 Jul 31 '16 at 09:00
  • I think I know the answer, but first: Do you have DBGrids connected to the datasources in the project you are testing with? – MartynA Jul 31 '16 at 09:44
  • See update. If the Update section answers your point, if you like I'll see if I can edit your q to make it a bit clearer and maybe that'll remove the downvotes. – MartynA Jul 31 '16 at 10:25
  • Yes i Use a grid: devexpress components. A devexpress grid can have several levels with connected datasources/datasets. – user3415259 Jul 31 '16 at 11:14
  • Of course I undestand Delphi can't figure out without interaction what datasource will be connected. But it understands which is connected at the time TDatasetaction will be executed. I understand that the active component is responsible for that. I still dont get how i can read the active datasource at the time of executing the action to have some interaction. – user3415259 Jul 31 '16 at 11:22
  • Why do you need to worry about that (apart from out of curiosity) when you can set the DataSetDelete's active datasource in your own code? – MartynA Jul 31 '16 at 11:25
  • Because on the delete action i want a "are you sure" dialog. So on onexecute i want some interaction. If i do that the execution is interrupted and i have to do the dataset.delete myself. That wouldnt be a problem if i know which datasource is connected. Now, executingTDatasetdelete.datasource,dataset.delete would all solve this. But the datasource is nil. – user3415259 Jul 31 '16 at 11:53
  • Because now i can have one set of controlbuttons for a dozen datasources. Saves a lot of work setting the datasource myself. – user3415259 Jul 31 '16 at 12:15
  • See Update2 section in my answer. If that doesn't satisfy you, then I give up. – MartynA Jul 31 '16 at 12:28
  • Btw, feel free to accept this answer (by clicking the tick icon next to the vote count) if it helped you. – MartynA Aug 01 '16 at 10:36