?
Solved

Dragover, Key Shift states and drag cursors

Posted on 2006-06-06
19
Medium Priority
?
763 Views
Last Modified: 2010-04-05
I am trying to implement drag and drop across controls and forms within an application.

Each form has it's own data module and each form can (theoretically) point to different sets of data.  Individual forms do not know have a specific relationship with any other form so any functionality has to be handled by the receiving (OnDragOver) control.

Assuming the data begin dragged is able to be accepted by the drop control site I need to (among other things but they are application specific more than solely related to the VCL):

a) Determine the current shift key state to determine whether the user wants a drag or a move operation.
b) Set the Drag cursor and Accept parameter based on the above.
c) The drag cursor and Accept parameter also needs to change when the user presses or releases the Ctrl / Shift key.  The problem here is that this does not trigger the OnDragOver event.

(The above functionality is similar to Windows Explorer functionality)

I already have an object derived from a TDragControlObject so I am prefectly prepared to add parameters to this object if required.

Problems faced so far:

1) I do not want to set the cursor in an OnMouseMove event firstly because I will then need to set the OnMouseMove event for EVERY  control within the application to a single event and also because the OnMouseMove evnt does not know that I am in a drag drop operation.  I also cannot set the Accept parameter of a drag operation here. (ie: some controls may allow a drag copy but not a drag move)

2) The OnDragOver event does not pass in the current shift state of the keyboard.

3) I do not want to use the OnKeyDown / OnKeyUp events for the same reason as item 1) above.

Hopefully this covers everything and provides enough information as to the problems I am facing with getting this working.
0
Comment
Question by:Woodster
  • 10
  • 6
  • 3
19 Comments
 
LVL 4

Author Comment

by:Woodster
ID: 16848740
It is also possible that items will be dragged from a form contained a separate instance of the same application (or possibly a DLL) so (as far as I can tell) global variables are not really an workable option within the main application and something I am trying to avoid if at all possible.
0
 
LVL 10

Expert Comment

by:atul_parmar
ID: 16849885
When the dragstart event fires, use GetShiftState function to check whether ctrl/shift is pressed. The same you can use when a control is dragged over.

you can also use the WinAPI GetKeyState function.
0
 
LVL 4

Author Comment

by:Woodster
ID: 16850116
Doing anything in the OnDragStart event is pointless as the user can 'unpress' the Ctrl or Shift keys during the drag operation which then obviously changes the mode that of the drag (ie: move or copy)

The help files for both C++ Builder 5 and Delphi 5 that we have in house make not mention any of these functiosn (or any others I have seen listed when searching for info on this).  What units do I need to include to get access to these functions?  What version of Delphi introduced the GetShiftState and GetKeyState functions as they may not be available inthe verison of C++ Builder that we are using.

PS: Yes I did realise after posting the question that I posted in the Delphi area instead of the C++ Builder area however the VCL functionality should still be equivalent.
0
Independent Software Vendors: We Want Your Opinion

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
LVL 4

Author Comment

by:Woodster
ID: 16850134
Have found GetKeyState in the Help file (doing a search within the Help does not find it but typing it in the code window and pressing F1 does!!!) but no GetShiftState information so that is a function I can code myself to make it a little more user friendly.

However whayt is the preferred method of achieving all of this efficiently in a drag/drop operation as I need to determine the state of these keys constantly doing not on the the mouse movements during the drag but also of the change of key states even when the mouse remains stationery.  If you are not sure what I mean by this, start a drag/drop operation in Windows Explorer and press and release the Ctrl key a few times. This has the effect of changing the drag cursor and also determining whether or not a drop can be accepted over a given control.
0
 
LVL 10

Assisted Solution

by:atul_parmar
atul_parmar earned 200 total points
ID: 16850161
Here is how you can use GetKeyState;
  if ((GetKeyState(VK_LSHIFT) and $8000) > 0) then
    ShowMessage('Left shift pressed');
The GetShiftState will be available on CLX platform only. TDragObject.GetShiftState
0
 
LVL 4

Author Comment

by:Woodster
ID: 16850207
Getting the KeyState is the easy part of the problem.  Applying that to the drag and drop operation is the hard step and what events will allow access to both the TDragObject that is part of the drag operation and also access to live key presses as they occur so that the control under the mouse cursor during the drag can determine whether or not it can accept the drop and to also change the current drag cursor as required.

As stated above the OnStartDrag is not applicable.

The OnDragOver event is not triggered when a CTRL or Shift Key combination is pressed or released.
0
 
LVL 10

Expert Comment

by:atul_parmar
ID: 16850468
The OnDragOver fires when you hold ctrl or shift. I don't understand why it doesn't happens for you.

procedure TForm1.FormDragOver(Sender, Source: TObject; X, Y: Integer;
  State: TDragState; var Accept: Boolean);
begin
  Accept := True;
  if ((GetKeyState(VK_LSHIFT) and $8000) > 0)
  or ((GetKeyState(VK_RSHIFT) and $8000) > 0) then
  begin
    Memo1.Lines.Add('Shift Holded');
  end;
  if ((GetKeyState(VK_LCONTROL) and $8000) > 0)
  or ((GetKeyState(VK_RCONTROL) and $8000) > 0) then
  begin
    Memo1.Lines.Add('Control Holded');
  end;
end;
0
 
LVL 4

Author Comment

by:Woodster
ID: 16850569
Sorry - my point was that the OnDragOver event is not triggered when by the Shift key being pressed and released.  I inserted some functionality in the OnDragOver event to let me know each time it was triggered.  When the Ctrl key is held down there is a continuous rapid calling of the OnDragOver event which stops as soon as the Ctrl key is released however the same does not happen for either the Shift or the Alt keys (or any other key on the keyboard for that matter).

Is there a flag that needs to be set in order for this to happen or some other setting that I may be missing?
0
 
LVL 10

Expert Comment

by:atul_parmar
ID: 16850588
Can you show that code which works for Ctrl but not for Shift or Alt?
0
 
LVL 4

Author Comment

by:Woodster
ID: 16850613
Have just added a Beep() command to the controls OnDragOver() event

ie:

void MyControl::OnDragOver(...)
{
  Beep();
}
0
 
LVL 10

Expert Comment

by:atul_parmar
ID: 16850753
Woodster, I understand that you are using Ctrl for Drag and Shift for dock operation. If it is correct then the OnDragOver will not fire when you dock over it.

For Docking you will have to use OnDockOver event and it will fire only if its DockSite property is set to true.
0
 
LVL 4

Author Comment

by:Woodster
ID: 16850785
No.  It is all drag and drop.  There is NO drag and dock functionality.

Ctrl is used to force a Drag-Copy operation
Shift is used to force a Drag-Move operation.

Similar to Windows Explorer Drag and Drop operations where a file copy or move can be forced by using the Ctrl or Shift key respectively
0
 
LVL 10

Expert Comment

by:atul_parmar
ID: 16850899
Woodster, I understand your problem and for me OnDragOver works very well when I hold Shift or Alt key.
Paste the following code in your onDragOver event handler (you will have to convert it to c++) and it will change the cursor for shift and ctrl.

  if ((GetKeyState(VK_LSHIFT) and $8000) > 0)
  or ((GetKeyState(VK_RSHIFT) and $8000) > 0) then
  begin
    (Source As TButton).DragCursor := crDrag;
    // (Source As TButton).Tag := 1; //  tag 1 = move
  end;
  if ((GetKeyState(VK_LCONTROL) and $8000) > 0)
  or ((GetKeyState(VK_RCONTROL) and $8000) > 0) then
  begin
    (Source As TButton).DragCursor := crMultiDrag;
    // (Source As TButton).Tag := 2; // tag 2 = copy
  end;
0
 
LVL 14

Accepted Solution

by:
Pierre Cornelius earned 800 total points
ID: 16850956
I'm using Delphi 7 and it works fine for when you press the CTRL key but not when you press the SHIFT key. I found the problem in the controls.pas unit, more specifically in the TDragObject.WndProc function. I wrote a demo to help you out.

I don't like messing with my delphi source codes, as I have heard of instances where this has interfered with future patches and updates. So what I have done is to copy the controls.pas file to the same directory as my demo app, this way any references in the uses clause of the demo app would then refer to the copied file and leave my source files in tact. I suggest you do the same.

I don't want to paste the delphi source (possible copyright issues) but basically what you have to do is add support for cases where the SHIFT key is pressed. In my control.pas file the WndProc only accommodated for CONTROL key presses.
To correct the problem,

1. find the part where it detects a CN_KEYUP message and add something like this:
   if (Msg.WParam = VK_SHIFT) then DragTo(DragObject.DragPos);

2. find the part where it detects a CN_KEYDOWN message and add something like this:
   if (Msg.WParam = VK_SHIFT) then DragTo(DragObject.DragPos);
   {in my version they used a case statement to
     detect VK_CONTROL so I simply added ", VK_SHIFT" in
     the case statement}

Here's the demo I did:

PAS FILE:
============================================
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    grpCtrl: TGroupBox;
    grpShift: TGroupBox;
    grpCtrlShift: TGroupBox;
    grpNone: TGroupBox;
    Button1: TButton;
    procedure grpCtrlDragOver(Sender, Source: TObject; X, Y: Integer;
      State: TDragState; var Accept: Boolean);
    procedure grpShiftDragOver(Sender, Source: TObject; X, Y: Integer;
      State: TDragState; var Accept: Boolean);
    procedure grpCtrlShiftDragOver(Sender, Source: TObject; X, Y: Integer;
      State: TDragState; var Accept: Boolean);
    procedure grpNoneDragOver(Sender, Source: TObject; X, Y: Integer;
      State: TDragState; var Accept: Boolean);
    procedure grpCtrlDragDrop(Sender, Source: TObject; X, Y: Integer);
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.grpCtrlDragOver(Sender, Source: TObject; X, Y: Integer;
  State: TDragState; var Accept: Boolean);
var ks: TKeyboardState;
begin
  GetKeyboardState(ks);
  accept:= ((ks[VK_LCONTROL] AND 128 = 128) or (ks[VK_RCONTROL] AND 128 = 128))
           AND
           ((ks[VK_LSHIFT] AND 128 = 0) AND (ks[VK_RSHIFT] AND 128 = 0));
end;

procedure TForm1.grpShiftDragOver(Sender, Source: TObject; X, Y: Integer;
  State: TDragState; var Accept: Boolean);
var ks: TKeyboardState;
begin
  GetKeyboardState(ks);
  accept:= ((ks[VK_LSHIFT] AND 128 = 128) or (ks[VK_RSHIFT] AND 128 = 128))
           AND
           ((ks[VK_LCONTROL] AND 128 = 0) AND (ks[VK_RCONTROL] AND 128 = 0));
end;

procedure TForm1.grpCtrlShiftDragOver(Sender, Source: TObject; X, Y: Integer;
  State: TDragState; var Accept: Boolean);
var ks: TKeyboardState;
begin
  GetKeyboardState(ks);
  accept:= ((ks[VK_LCONTROL] AND 128 = 128) or (ks[VK_RCONTROL] AND 128 = 128))
           AND
           ((ks[VK_LSHIFT] AND 128 = 128) or (ks[VK_RSHIFT] AND 128 = 128));
end;

procedure TForm1.grpNoneDragOver(Sender, Source: TObject; X, Y: Integer;
  State: TDragState; var Accept: Boolean);
var ks: TKeyboardState;
begin
  GetKeyboardState(ks);
  accept:= ((ks[VK_LCONTROL] AND 128 = 0) AND (ks[VK_RCONTROL] AND 128 = 0))
           AND
           ((ks[VK_LSHIFT] AND 128 = 0) AND (ks[VK_RSHIFT] AND 128 = 0));
end;

procedure TForm1.grpCtrlDragDrop(Sender, Source: TObject; X, Y: Integer);
begin
  if (Source is TButton) AND (Sender is TGroupBox) then
  begin
    (Source as TButton).Parent:= (Sender AS TGroupBox);
    (Source as TButton).Left:= x;
    (Source as TButton).Top:= y;
  end;
end;

end.


DFM File:
======================================================
object Form1: TForm1
  Left = 192
  Top = 114
  Width = 458
  Height = 331
  Caption = 'Form1'
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'MS Sans Serif'
  Font.Style = []
  OldCreateOrder = False
  PixelsPerInch = 96
  TextHeight = 13
  object grpCtrl: TGroupBox
    Left = 16
    Top = 64
    Width = 205
    Height = 105
    Caption = 'Accept if CTRL pressed'
    TabOrder = 0
    OnDragDrop = grpCtrlDragDrop
    OnDragOver = grpCtrlDragOver
  end
  object grpShift: TGroupBox
    Left = 228
    Top = 64
    Width = 205
    Height = 105
    Caption = 'Accept if SHIFT pressed'
    TabOrder = 1
    OnDragDrop = grpCtrlDragDrop
    OnDragOver = grpShiftDragOver
  end
  object grpCtrlShift: TGroupBox
    Left = 16
    Top = 176
    Width = 205
    Height = 105
    Caption = 'Accept if CTRL and SHIFT pressed'
    TabOrder = 2
    OnDragDrop = grpCtrlDragDrop
    OnDragOver = grpCtrlShiftDragOver
  end
  object grpNone: TGroupBox
    Left = 228
    Top = 176
    Width = 205
    Height = 105
    Caption = 'Accept if CTRL and SHIFT not pressed'
    TabOrder = 3
    OnDragDrop = grpCtrlDragDrop
    OnDragOver = grpNoneDragOver
  end
  object Button1: TButton
    Left = 176
    Top = 16
    Width = 75
    Height = 25
    Caption = 'Drag me'
    DragMode = dmAutomatic
    TabOrder = 4
  end
end

Kind Regards
Pierre
0
 
LVL 14

Expert Comment

by:Pierre Cornelius
ID: 16850971
in reflection of this "I already have an object derived from a TDragControlObject so I am prefectly prepared to add parameters to this object if required" simply override it's WndProc to add drag functionality for VK_SHIFT keypresses as stated in my previous comment.
i.e.  if (Msg.WParam = VK_SHIFT) then DragTo(DragObject.DragPos); //for both the keydown and the keyup messages.
0
 
LVL 4

Author Comment

by:Woodster
ID: 16857827
Pierre - You have pretty much stated the exact problem I am having.  Have had a quick look at the TDragControl class and it's ancestors however none of them have the virtual function WndProc for me to derive from.  This virtual function is only introduced in certain controls such as TCustomTreeView and TForm so I will need to look at the controls.pas (or C++ Builder equivalent) solution.

Will keep this thread updated on my progress on this.
0
 
LVL 4

Author Comment

by:Woodster
ID: 16858524
Pierre,

I had to hack the controls.pas file and alter the function TDragObject.MouseMsg.  Unfortunately this function is not declared as a virtual function so I was unable to simply override the function in my derived class to add this functionality.

I had all sorts of problems when adding a check for value VK_MENU such as C++ Builder crashing with an AV during the Build process or when I altered the order of if statements and/or case clauses, the event would still not fire on the pressing of the ALT key so I have simply left the checing to Control and Shift keys only.
0
 
LVL 14

Expert Comment

by:Pierre Cornelius
ID: 16864795
Strange that your control.pas does not have the wndProc method?! What version of delphi are you using?
0
 
LVL 4

Author Comment

by:Woodster
ID: 16866514
TDragControl does not have a member function for WndProc.

I saw the code you were referring to and it was located in TDragControl.MouseMsg

WndProc is introduced at the TControl level however TDragObject is a direct descendent of the TObject class.
0

Featured Post

Hire Technology Freelancers with Gigs

Work with freelancers specializing in everything from database administration to programming, who have proven themselves as experts in their field. Hire the best, collaborate easily, pay securely, and get projects done right.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Introduction The parallel port is a very commonly known port, it was widely used to connect a printer to the PC, if you look at the back of your computer, for those who don't have newer computers, there will be a port with 25 pins and a small print…
Introduction I have seen many questions in this Delphi topic area where queries in threads are needed or suggested. I know bumped into a similar need. This article will address some of the concepts when dealing with a multithreaded delphi database…
Are you ready to place your question in front of subject-matter experts for more timely responses? With the release of Priority Question, Premium Members, Team Accounts and Qualified Experts can now identify the emergent level of their issue, signal…
Is your data getting by on basic protection measures? In today’s climate of debilitating malware and ransomware—like WannaCry—that may not be enough. You need to establish more than basics, like a recovery plan that protects both data and endpoints.…
Suggested Courses
Course of the Month14 days, 16 hours left to enroll

840 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question