2

I am using a piece of software that David Heffernan has shared here:

How can I allow a form to accept file dropping without handling Windows messages?

I have changed the component from a Form to a Panel, and that works fine in the MainForm.

However, when using in a ShowModal() environment, I do not have access to the values of components in the modal Form.

I made a small demo to show the problem.

Unit1:

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls, Unit2;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
  protected
  Public
  End;

var
  Form1: TForm1;

implementation
{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
Var aForm: TForm2;
Begin
  aForm := TForm2.Create(Nil);
  Try
    aForm.ShowModal;
  Finally
    aForm.Free;
  End;
End;

End.

Unit2:

unit Unit2;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.StdCtrls, DragAndDrop;

type
  TmyPanel = class(TPanel, IDragDrop)
  private
    FDropTarget: TDropTarget;
    // implement IDragDrop
    function DropAllowed(const FileNames: array of string): Boolean;
    procedure Drop(const FileNames: array of string);
  protected
//    procedure WMDropFiles(var Message: TWMDropFiles); message WM_DROPFILES;
    procedure CreateWindowHandle(Const Params: TCreateParams); override;
    procedure DestroyWindowHandle; override;
  end;

  TForm2 = class(TForm)
    DropRefPanel: TPanel;
    Label1: TLabel;
    Button1: TButton;
    procedure FormCreate(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure FormActivate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    { Private declarations }
  public
    { Public declarations }
    DropPanel: TmyPanel;
  end;

var
  Form2: TForm2;

implementation

{$R *.dfm}

procedure TmyPanel.CreateWindowHandle(Const Params: TCreateParams);
begin
  inherited;
  FDropTarget := TDropTarget.Create(WindowHandle, Self);
end;

procedure TmyPanel.DestroyWindowHandle;
begin
  FreeAndNil(FDropTarget);
  inherited;
end;

function TmyPanel.DropAllowed(const FileNames: array of string): Boolean;
begin
  Result := True;
end;

procedure TmyPanel.Drop(const FileNames: array of string);
Var i: Integer;
begin
//  Form2.Label1.Caption := 'Drop';
  begin
    for i := 0 to Length(FileNames)-1 do
      ShowMessage(Form2.Label1.Caption + ': ' + FileNames[i]);
  end;
end;

procedure TForm2.Button1Click(Sender: TObject);
begin
  showMessage(Label1.Caption);
  Label1.Caption := 'Button';
end;

procedure TForm2.FormActivate(Sender: TObject);
begin
  Label1.Caption := 'Activation';
end;

procedure TForm2.FormCreate(Sender: TObject);
begin
  Label1.Caption := 'FormCreate';

  DropRefPanel.Color := $00BBF7B3 -50;
  DropPanel := TmyPanel.Create(Form2);
  with DropPanel do
  begin
    Parent := DropRefPanel;
    Left := 3;
    Top := 3;
    Width := DropRefPanel.Width - 6;
    Height := DropRefPanel.Height - 6;
    Visible := True;
    ParentBackground := False;
    Color := $00BBF7B3;
    BevelOuter := bvLowered;
    Caption := 'DropBox';
  end;
end;

procedure TForm2.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  FreeAndNil(DropPanel);
end;

end.

The DragAndDrop unit has the unchanged code as made by David.

I have demonstrated the problem here with Label1. When I am in the Drop() procedure and ask for the Form2.Label1.Caption, it shows the Label1 value from design-time, or the value as assigned in FormCreate(), but not the value from FormActivate() or assigned later in the program.

It looks like there are 2 forms.

PS1: Works fine when in the MainForm.

PS2: Changing Form2 to nil in TmyPanel.Create(Form2) does not change the behaviour.

PS3: Moving the Create() function to OnActivate does not change the behaviour.

PS4. I'm using Delphi XE4.

Does anyone have an idea what is wrong in this ShowModal() example?

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
Jan
  • 23
  • 3
  • Does FormActivate exist in the Unit2.dfm file? Alternatively: Set this value in FormCreate: `OnActivate := FormActivate`. I suspect the value assignment has been lost. – USauter Sep 08 '21 at 13:28
  • @Usauter Thanks for your reaction. Formactivate does exist in the .dfm file. – Jan Sep 08 '21 at 15:37

1 Answers1

3

When showing the modal Form, you are creating a new instance of TForm2 but not assigning it to the global Form2 variable that Drop() is trying to access. By default, that global variable is used only for an auto-created (ie design-time) TForm2 object, not a dynamically created object.

You are trying to assign the created TForm2 object as the Owner of the TmyPanel object, but you are using the global Form2 variable (which doesn't point at the created TForm2 object!) when you should be using FormCreate()'s implicit Self parameter instead, eg:

DropPanel := TmyPanel.Create({Form2}Self);

Now, Drop() can access the Form via the panel's Owner property, eg:

procedure TmyPanel.Drop(const FileNames: array of string);
var
  i: Integer;
  myFrm: TForm2;
begin
  myFrm := Owner as TForm2;
  myFrm.Label1.Caption := 'Drop';
  begin
    for i := 0 to Length(FileNames)-1 do
      ShowMessage(myFrm.Label1.Caption + ': ' + FileNames[i]);
  end;
end;
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Thanks for your reaction. Unfortunately this does not solve my problem. I understand your suggestion. The label1.caption assign in the drop procedure was commented out because I used it for testing. With your suggestion the original label1 on the form is still not changed and I can not access it via de drop-procedure. – Jan Sep 08 '21 at 16:28
  • @Jan "*this does not solve my problem*" - Then you have not presented your *actual* problem correctly. Please edit your question to clarify the issue better. However, what I described IS nonetheless a bug in the code you have shown, since `Drop()` is expecting `Form2` to point at a different object than it is actually pointing at. So you need to fix that anyway. "*the original label1 on the form is still not changed*" - why should it change? You commented that out! You are not making much sense. "*I can not access it via de drop-procedure.*" - I showed you exactly how to do that. – Remy Lebeau Sep 08 '21 at 16:41
  • @Jan nevermind, I just saw another bug in your code. I have updated my answer to address that. – Remy Lebeau Sep 08 '21 at 16:47
  • Thanks for the update. Works like a charm. Sorry for the misunderstanding. I commented it out and you did not, so that made the confusion. I played with self, form2 and nil, but had no succes due to the problem that is covered your 1st solution. I am gratefull. PS. I know there is a voting system. How can I vote for you? – Jan Sep 08 '21 at 17:16
  • @ remy. I have voted, but need to communicate more before it is actually accepted. – Jan Sep 08 '21 at 17:34
  • "I played with self, form2 and nil" - this is no way to go about programming. Understanding the problem is the best solution. – MartynA Sep 08 '21 at 17:40
  • @martynA. I agree – Jan Sep 08 '21 at 17:44
  • @Jan [What should I do when someone answers my question?](https://stackoverflow.com/help/someone-answers) – Remy Lebeau Sep 08 '21 at 18:10