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.)
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;
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;
ReplyDeleteprocedure TForm4.FormCreate(Sender: TObject);
begin
DropTarget1.Filter := '*.jpeg;*.jpg;*.png';
DropTarget1.Parent := Image1;
Image1.Align := TAlignLayout.alClient;
end;
Making the Image the Parent of the DropTarget does the trick. (But the problem is now somewhere else.)
ReplyDeleteIn 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.)
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.
ReplyDeleteOnly Viewport3D is alClient in my real application. Viewport can be transparent. Image will show through from behind, this is a feature.
ReplyDeleteHad 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.)
try DropTarget1.Parent := Viewport3D1;
ReplyDeleteTest results:
ReplyDelete1) 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.
Not really understanding what your trying to accomplish but...something to control who will accept drag drops...
ReplyDelete1)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;
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.
ReplyDeletehttps://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.
Good to know when testing in XE4:
ReplyDeleteFMX.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;
Solution found.
ReplyDeleteOverride 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.