Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to detect a drag drop outside your application?

I'm trying to emulate the tab dragging functionality of Chrome. I want the user to be able to drag a tab to a new location in the tab strip or drop it outside the application to create a new window. Dragging within the application is easy, but how do I detect when a user drops somewhere not on my app?

In essence I am looking to implement "tear off" tabs.

like image 558
norgepaul Avatar asked Oct 18 '12 17:10

norgepaul


1 Answers

Since the mouse is captured during a drag operation, there's no problem with detecting when a drag operation is finished in an OnEndDrag handler, even if it is outside any form of the application. You can tell if the drop is accepted or not by testing the 'target' object and if the drop is not accepted, you can tell if it is outside the application by testing the mouse position.

However there's still a problem with this approach. You can't tell if the drag is cancelled by pressing the 'Esc' key. There's also the problem of not being able to set the drag cursor to 'accepted' outside the form, since no control's OnDragOver will be called there.

You can overcome these problem by changing the behavior of the drag operation using a drag object of your creation. Below is one example:

unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    PageControl1: TPageControl;
    TabSheet1: TTabSheet;
    TabSheet2: TTabSheet;
    TabSheet3: TTabSheet;
    procedure FormCreate(Sender: TObject);
    procedure PageControl1MouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure PageControl1StartDrag(Sender: TObject;
      var DragObject: TDragObject);
    procedure PageControl1EndDrag(Sender, Target: TObject; X, Y: Integer);
    procedure PageControl1DragOver(Sender, Source: TObject; X, Y: Integer;
      State: TDragState; var Accept: Boolean);
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
  PageControl1.DragMode := dmManual;
end;


type
  TDragFloatSheet = class(TDragControlObjectEx)
  private
    class var
      FDragSheet: TTabSheet;
      FDragPos: TPoint;
      FCancelled: Boolean;
  protected
    procedure WndProc(var Msg: TMessage); override;
  end;

procedure TDragFloatSheet.WndProc(var Msg: TMessage);
begin
  if (Msg.Msg = CN_KEYDOWN) and (Msg.WParam = VK_ESCAPE) then
    FCancelled := True;
  FDragPos := DragPos;
  inherited;
  if (Msg.Msg = WM_MOUSEMOVE) and
      (not Assigned(FindVCLWindow(SmallPointToPoint(TWMMouse(Msg).Pos)))) then
    Winapi.Windows.SetCursor(Screen.Cursors[GetDragCursor(True, 0, 0)]);
end;

//-------------------

procedure TForm1.PageControl1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  TDragFloatSheet.FDragSheet :=
      (Sender as TPageControl).Pages[TPageControl(Sender).IndexOfTabAt(X, Y)];
  PageControl1.BeginDrag(False);
end;

procedure TForm1.PageControl1StartDrag(Sender: TObject;
  var DragObject: TDragObject);
begin
  DragObject := TDragFloatSheet.Create(Sender as TPageControl);
end;

procedure TForm1.PageControl1DragOver(Sender, Source: TObject; X, Y: Integer;
  State: TDragState; var Accept: Boolean);
var
  TargetSheet: TTabSheet;
begin
  TargetSheet :=
      (Sender as TPageControl).Pages[TPageControl(Sender).IndexOfTabAt(X, Y)];
  Accept := Assigned(TargetSheet) and (TargetSheet <> TDragFloatSheet.FDragSheet);
end;

procedure TForm1.PageControl1EndDrag(Sender, Target: TObject; X, Y: Integer);
begin
  if Assigned(Target) then begin

    // normal processing, f.i. find the target tab as in OnDragOver
    // and switch positions with TDragFloatSheet.FDragSheet

  end else begin
    if not TDragFloatSheet.FCancelled then begin
      if not Assigned(FindVCLWindow(TDragFloatSheet.FDragPos)) then begin

        // drop TDragFloatSheet.FDragSheet at TDragFloatSheet.FDragPos

      end;
    end;
  end;
end;

end.
like image 168
Sertac Akyuz Avatar answered Oct 13 '22 15:10

Sertac Akyuz