update: RSP-11456

update: RSP-11456

The use of TDropTarget can be a bit fragile in XE4, FMX. (Don't know if it works better in XE8.)

Below is my isolated test case:

First drag an image to the DropTarget - it works - FileName is shown in Caption.

Then call DropTarget.BringToFront and it no longer works.

//Image1: TImage;
//DropTarget1: TDropTarget;
//Button1: TButton;

procedure TForm1.FormCreate(Sender: TObject);
begin
  DropTarget1.Filter := '*.jpeg;*.jpg;*.png';
  Image1.Align := TAlignLayout.alClient;
end;

procedure TForm1.DropTarget1Dropped(
  Sender: TObject;
  const Data: TDragObject;
  const Point: TPointF);
var
  fn: string;
begin
  if Length(Data.Files) = 1 then
  begin
    fn := Data.Files[0];
    fn := ExtractFileName(fn);
    Caption := fn;
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  DropTarget1.BringToFront;
end;

(To work around the issue I have to create and setup a new TDropTarget at runtime every time I want to drop something. That is because my real application is more complicated and I need to call BringToFront, SendToBack and similar things.)

Comments

  1. This is really a problem with Align.  Once an object align property is set it gets all drag drop for that point...So all the drag drops are going to your TImage...The work around is to set the DropTarget1.Parent := Image1;


    procedure TForm4.FormCreate(Sender: TObject);
    begin
      DropTarget1.Filter := '*.jpeg;*.jpg;*.png';
      DropTarget1.Parent := Image1;
      Image1.Align := TAlignLayout.alClient;
    end;

    ReplyDelete
  2. Making the Image the Parent of the DropTarget does the trick. (But the problem is now somewhere else.)

    In the extended test case below, the DropTarget works, even when calling BringToFront on it, but it is then invisible. I mean the visual representation of the DropTarget itself can no longer be seen. DropTarget.BringToFront does not help if Image is Parent.

    XE4 / Win32:

    procedure TForm1.FormCreate(Sender: TObject);
    begin
      DropTarget1.Filter := '*.jpeg;*.jpg;*.png';

      //uses System.UIConsts
      Rectangle1.Fill.Color := claRed;
      Viewport3D1.Color := claNull;

      Rectangle1.Parent := self;
      Image1.Parent := self;
      Layout1.Parent := self;
      Viewport3D1.Parent := self; //or Layout1;
      Text1.Parent := self; //or Viewport3D1
      DropTarget1.Parent := Image1; //!

      Rectangle1.Align := TAlignLayout.alClient;
      Image1.Align := TAlignLayout.alClient;
      Layout1.Align := TAlignLayout.alClient;
      Viewport3D1.Align := TAlignLayout.alClient;
      Text1.Align := TAlignLayout.alClient;
      DropTarget1.Align := TAlignLayout.alClient;

      //intended Z-Order
    {
      DropTarget1.SendToBack;
      Text1.SendToBack;
      Viewport3D1.SendToBack;
      Layout1.SendToBack;
      Image1.SendToBack;
      Rectangle1.SendToBack;
    }

    //  Rectangle1.Visible := False;
      Button1.BringToFront;
    end;

    (DropTarget appears again if Rectangle1.Visible is set to false.)

    ReplyDelete
  3. Your using Align alClient wrong...Only one object should be set to alClient...Set up an observer pattern where everyone who needs to be resized gets notified and resizes when your object resizes...In your example...who is supposed to get the Drag Drops is not obvious, if you have code that changes who is alClient, then set your DropTargets Parent to that object when you change the Align property, or better yet drop a tpanel on the form.  Set everyone to be children of the panel and resize when the panel resizes.

    ReplyDelete
  4. Only Viewport3D is alClient in my real application. Viewport can be transparent. Image will show through from behind, this is a feature.
    Had a regression when I added the Image. Image is now the parent of DropTarget. This seems to be necessary.

    TDropTarget.Create/Free at runtime is my workaround.

    Note that I want the 3D-Image to have a transparent background. The Rectangle behind the Viewport3D will serve as a solid color background for the 3D stuff - if the Image is not present. This is why I want to keep the Rectangle in the extended test.

    Layout and Text can be removed from the test.

    Updated test:

    procedure TForm1.MakeEqualSize(t: TControl);
    var
      s: TControl; //source
    begin
      s := Viewport3D1; //AlignControl
      t.Position.X := s.Position.X;
      t.Position.Y := s.Position.Y;
      t.Width := s.Width;
      t.Height := s.Height;
    end;

    //AlignControl is Viewport3D
    Viewport3D1.Align := TAlignLayout.alClient;

    Make EqualSize(Rectangle1);
    Make EqualSize(Image1);
    Make EqualSize(Layout);
    //Make EqualSize(Viewport3D1);
    Make EqualSize(Text1);
    //Make EqualSize(DropTarget1);

    (DropTarget remains hidden behind Rectangle in the test.)

    ReplyDelete
  5. Test results:

    1) DropTarget.Parent := Viewport3D;

    No aligning any more. This time I have set up everything at design time in OI. The elements overlapp partially.
    This is now the perfect test setup. Mouse cursor can be over one, two, three, or four elements.

    The elements are stacked as follows:
    Rectangle
    Image
    Viewport3D
    (and DropTarget as Child of Viewport3D)

    [F9]

    Result (Order of precedence):
    a) Rectangle
    b) Image
    c) DropTarget
    d) Viewport3D

    When dragging an image file from Explorer I see the blue highlight on all four elements. The DropTarget will accept the drop only when mouse is over the DropTarget, but not at the same time over the Rectangle or Image.

    The element which sits closest to the form surface takes precedence!

    It can be tested.

    procedure TForm1.Button1Click(Sender: TObject);
    begin
      Image1.SendToBack;
    end;

    procedure TForm1.Button2Click(Sender: TObject);
    begin
      Rectangle1.SendToBack;
    end;

    2) DropTarget.Parent := self; //Form1

    procedure TForm1.Button3Click(Sender: TObject);
    begin
      DropTarget1.SendToBack;
      //It will now accept the drop. :)
      //but it may be covered. :(
    end;

    Of course, DropTarget1.BringToFront will make it visible, but it does no longer have precedence, nothing can be dropped any more if Image or Viewport or Rectangle overlap the DropTarget.

    ReplyDelete
  6. Not really understanding what your trying to accomplish but...something to control who will accept drag drops...

    1)Change your events to BringToFront.
    2)Chage the DropTarget parent to who ever is in front...

    example

    procedure ChangeTarget(aControl: TControl);//not sure of TControl coding from memory here
    begin
      aControl: BringToFront;
      //might also try aControl.Align := alClient;
      DropTarget1.Parent := aControl; 
    end;


    procedure TForm1.Button1Click(Sender: TObject);
    begin
      ChangeTarget(Image1);
    end;

    procedure TForm1.Button2Click(Sender: TObject);
    begin
      ChangeTarget(Rectangle1);

    end;

    2) DropTarget.Parent := self; //Form1

    procedure TForm1.Button3Click(Sender: TObject);
    begin
      ChangeTarget(Viewport3D);
    end;

    ReplyDelete
  7. The problem is that the Image and/or Rectangle is behind the transparent Viewport. I did not want to bring the Image to the front. And while the backmost control (Image) is the Parent of the DropTarget - (only then it works) - the DropTarget can be obscured by the content in the Viewport, for example.

    https://plus.google.com/u/0/+GustavSchubert/posts/9ymSyfubLDn

    Thanks +Rick, I think I have finished my investigation, learned something, found a workaround, and the basic issue remains.

    1) The DropTarget should be on top of everything.
    2) The Parent of the DropTarget must be the backmost control.

    Not a problem in a simple demo, but an issue in my real app. Easy to test. Not easy to fix.

    ReplyDelete
  8. Good to know when testing in XE4:

    FMX.Platform.Win has its own TDropTarget class.
      TDropTarget = class(TComponent, IDropTarget)
      private
        Form: TCommonCustomForm;

    It is created and registered in function TPlatformWin.CreateWindow.

    When dragging, the Form gets notified, e.g. in DragEnter or DragOver.

    DragOver will try to find a Target:

    FMX.Forms
    procedure TCommonCustomForm.DragOver

    It looks for the backmost control on the form first:

    FMX.Forms
    function TCommonCustomForm.FindTarget
      for i := 0 to ChildrenCount - 1 do

    And then looks for the topmost Child:

    FMX.Controls
    function TControl.FindTarget
      for I := ChildrenCount - 1 downto 0 do

    The order of components in the list of Children is important.

    FMX.Types
    procedure TFmxObject.BringToFront;
    procedure TFmxObject.SendToBack;

    ReplyDelete
  9. Solution found.

    Override protected method FindTarget of the Form,
    Copy implementation from TCommonCustomForm,
    and change the direction of the for loop.

    must be
    for i := ChildrenCount - 1 downto 0 do
    instead of
    for i := 0 to ChildrenCount - 1 do

    Done.

    ReplyDelete

Post a Comment