Solved

D & D in listbox with objects

Posted on 1997-10-09
8
412 Views
Last Modified: 2010-04-04
The below code is an example of dragging and dropping in a listbox. There are two problems:

1. As soon as I add objects to this list box rather than strings, the beloe code does not work for D & D. Why?

2. I need multiselect to be true (extendedSelect also selected) but Ctrl-select does not work with all in this example. Why?

Thanks a lot.

Tom.

unit Unit2;

interface

uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    ListBox1: TListBox;
    procedure ListBox1DragDrop(Sender, Source: TObject; X, Y: Integer);
    procedure ListBox1DragOver(Sender, Source: TObject; X, Y: Integer;
      State: TDragState; var Accept: Boolean);
    procedure ListBox1MouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
  private
    { Private declarations }
    moveSelectedItem: integer;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.ListBox1DragDrop(Sender, Source: TObject; X, Y: Integer);
var
   moveToItm : integer; { the item that the SELECTED item is being dropped on to
                          in the ListBox }
begin
   if source = sender then
      with Sender as TListBox do begin
         if MultiSelect then
            if SelCount > 1 then begin
               ShowMessage('You can only drag one item at a time');
               Exit;
            end;

         { instructions for moving an item WITHIN lst }
         { moveToItm = the item UNDER the moving, selected item }
         moveToItm := ItemAtPos(Point(X, Y), True);
         if moveToItm <> - 1 then
            Items.Move(moveSelectedItem, moveToItm); { puts the moved item into place }

         { select (highlight) the item you moved }
         if MultiSelect then
            Selected[moveToItm] := True
         else
            ItemIndex := moveToItm;

         if moveToItm = -1 then
            ItemIndex := Items.Count - 1;
      end;
end;

procedure TForm1.ListBox1DragOver(Sender, Source: TObject; X, Y: Integer;
  State: TDragState; var Accept: Boolean);
begin
   if (Sender = Source) then
      Accept := (TListBox(Sender).ItemAtPos(Point(X,Y), False) >= 0);
end;

procedure TForm1.ListBox1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
   if Button = mbLeft then
      with Sender as TListBox do
         if ItemAtPos(Point(X, Y), True) >= 0 then begin
            BeginDrag(False);
            moveSelectedItem := ItemIndex;
         end;
end;

end.

object Form1: TForm1
  Left = 200
  Top = 99
  Width = 435
  Height = 300
  Caption = 'Form1'
  Font.Color = clWindowText
  Font.Height = -13
  Font.Name = 'System'
  Font.Style = []
  PixelsPerInch = 96
  TextHeight = 16
  object ListBox1: TListBox
    Left = 56
    Top = 24
    Width = 121
    Height = 161
    ItemHeight = 16
    Items.Strings = (
      '1'
      '2'
      '3'
      '4'
      '5'
      '6'
      '7')
    MultiSelect = True
    TabOrder = 0
    OnDragDrop = ListBox1DragDrop
    OnDragOver = ListBox1DragOver
    OnMouseDown = ListBox1MouseDown
  end
end
0
Comment
Question by:tomcorcoran
  • 5
  • 3
8 Comments
 
LVL 1

Expert Comment

by:millerw
ID: 1347115
Ok, first of all, you can't add OBJECTS to a TListBox.  If you want to do this, use a TListView (Data property of each node).  Therefore, I can't help you with the first of your answers because it doesn't apply to a TListBox (unless I'm misunderstanding the point).  But here is the solution to your second one:

procedure TForm1.ListBox1DragDrop(Sender, Source: TObject; X, Y:
Integer);
var
   moveToItm : integer;
    Index : Integer;
begin
   if source = sender then
   begin
        With Sender as TListbox do
        begin
             moveToItm := ItemAtPos(Point(X, Y), True);
             Index := Items.Count;
             While Index <> 0 do
             begin
                  Dec(Index);
                  If Selected[Index] then
                  begin
                       Items.Move(Index, movetoitm);
                       If movetoItm > Index then
                          Dec(movetoitm)
                       else
                           Inc(Index);
                  end;
             end;
        end;
   end;
end;

Just replace your code in that procedure with this and it will work.  

With a ListBox you must remember this is not a TListView.  You must hold down the Crtl or Shift keys as you click and drag or when you click to drag the selection will reset.  Try it that way and see if it will do what you want.  I have it working here so just ask and I'll help you our more.

Scott
0
 
LVL 1

Expert Comment

by:millerw
ID: 1347116
BTW, when you cut and paste that code into yours, it will be all messed up.  Try the conbimation Ctrl+K+U to move a block of code all back one space and Crtl+K+I to do the opposite.  

To get this to work, just select the code after you have pasted it and continue to press Ctrl+K+U to unindent the entire selected block one space at a time till you have it where you want it.

Scott
0
 

Author Comment

by:tomcorcoran
ID: 1347117
Millerw,

I'm afraid I must not have explained myself very well.

First of all, of course you can add objects in a list box. Eg.

TListBox1.Items.AddObject('hi', MyPointer);

it is one of the most useful functionalities in Delphi.

I was not trying to implement multi item drag and drop. As the code shows I do not allow that. However, for other functionality I want to enable ctrl and shift selection, however, I can't get it to work once the above drag and drop routines are implemented.

Tom.

0
 
LVL 1

Accepted Solution

by:
millerw earned 100 total points
ID: 1347118
You are right about the objects.  I don't use ListBoxes anymore (I prefer the TListView objects) so that is why I missed that one.  Sorry for my ignorance there.  I either use a TList or TListView mattering on my needs.

As for the second part, yes, we do have a misunderstanding.  Let me see if I have this right.  You want mutiselect, but not for dragging n dropping?  I don't really know why you would not want your users to be able to drag and drop multiple items especially if it didn't hurt anything and improves the programs usability.  But hey, it is your program after all.

Anyway, the solution to your second problem is that you are BeginDragging in the wrong place.  You should be doing it in the MouseMove event of the listbox and not in the MouseDown.  The BeginDrag is causing the Ctrl not to work.  Put this code in the MouseMove event of the Listbox and erase the MouseDown event code:

procedure TForm1.ListBox1MouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
begin
   if ssLeft in Shift then
    with Sender as TListBox do
    if ItemAtPos(Point(X, Y), True) >= 0 then begin
       BeginDrag(False);
       moveSelectedItem := ItemIndex;
    end;
end;

One thing you may want to change here is make sure that SelCount is greater than 0, as well, before you allow the BeginDrag line to execute.  I actually used the CTRL key to select an item and then unselect it (so that it only had the focus but not a select) and the procedure moved it anyway.  Just a thought. :-)

About the first problem.  I have not been able to reproduce your problem.  In other words, the dragging and dropping still work for me.  I added 6 items that were all objects that pointed to ListBox1 and it worked just fine.  You will have to explain exactly what is going wrong with this one to me.  The help states that the Move procedure moves the object as well, so I don't see where your problem could be.  

BTW, here are some problems with your code that I found.
1.  You need to EndDrag.  This makes sure all information is deallocated and everything is cleaned up.  It also makes sure you have exited dragging.  This is most likely happening automatically, but I recommend doing it as a safeguard.  Put it in the ListBox1DropDrag and replace the if statement with:

    if moveToItm <> - 1 then
    begin
         Items.Move(moveSelectedItem, moveToItm); { puts the moved
item into place }
         EndDrag(False);
    end;

2.  Moving the drag after the last item and releasing selects all the items in the list.  This is caused by the:

    if MultiSelect then
    Selected[moveToItm] := True
    else
    ItemIndex := moveToItm;

If MoveToItm is -1 all items will select when MultiSelect is true.

Hope that answers your questions,
Scott
0
How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

 

Author Comment

by:tomcorcoran
ID: 1347119
Scott,

Thanks a million for your very detailed answer. Moving the BeginDrag solved the selection problem. Why does it make a difference to move the BeginDrag to the OnMouseMove?

In some listboxes where I implement drag I also show a hint in the OnMouseMove with code like:

   { itemAtPos returns -1 if no items at that position }
   overItem := (Sender as TListBox).ItemAtPos(Point(X,Y), True);
   if overItem <> - 1 then
      ShowHint(overItem) { my routime }
   else
      (Sender as TListBox).Hint := '';

That shouldn't present a problem with the new code I guess.

I had never noticed the EndDrag ommission or that dragging the last item selected everything, excellent.

You are right of course about multi drag and drop, I had tried impelenting it before but didn't come up with the nice solution of yours. I can't think of any reason not to implement it everywhere, that will certainly help make things less complicated not having to check for MultiSelect.

In youe sample code for OnMouseMove you use itemindex though multiselect is true, is that not a problem?

I have noticed that sometimes, when selecting an item in the listbox and dragging it selects the item underneath it also. I haven't been able to establish a pattern with this, any ideas why this occurs.

With your code after the dragging and dropping is finished, one item has focus but is not selected, Would you recommend removing focus from this item (it looks strange to be focused and not selected) or selecting another?

Thanks a million for your help.

Tom.
0
 
LVL 1

Expert Comment

by:millerw
ID: 1347120
The reason for the BeginDrag having to be moved is that in the actual Borland code, the BeginDrag performs the following line of code:

Perform(WM_LBUTTONUP, 0, Longint(PointToSmallPoint(P)));

Guess what that does?  Yup, it simulates a MouseUP event.  Thus, it acts as though you have double clicked--you just selected the item, and then unselected it.  That is why you get the "blink" of selection.  When it is in the MouseMove event, this doesn't occur because only one up event is performed meaning it is selected or unselected, not both, one right after the other.  Always do a BeginDrag in a MouseMove event.  That keeps it all straight.

Your code for the Hint looks OK to me, but if it ends up having problems write me.  It doesn't cause a mouseup or other event so it shouldn't effect the BeginDrag or selection.

About the ItemIndex.  Technically, you don't even need that line.  I just copied it from your MouseDown event.  Here is the "technical" way of doing a drag.

1.  Start the drag with code like this:
procedure TFMForm.FileListBox1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
 if Button = mbLeft then { drag only if left button pressed }
   with Sender as TFileListBox do { treat Sender as TFileListBox }
    begin
     if ItemAtPos(Point(X, Y), True) >= 0 then      { is there an item here? }
        BeginDrag(False);      { if so, drag it }
   end;
end;

2.  Have the control that will accept a drag return Accept=True like this:
procedure TFMForm.DirectoryOutline1DragOver(Sender, Source: TObject; X,  Y: Integer; State: TDragState; var Accept: Boolean);
begin
  if Source is TFileListBox then
    Accept := True;
  else
     Accept := False;
end;

3.  Drop the items like this:
procedure TFMForm.DirectoryOutline1DragDrop(Sender, Source: TObject; X,  Y: Integer);
begin
  if Source is TFileListBox then
   with DirectoryOutline1 do
      ConfirmChange('Move', FileList.FileName, Items[GetItem(X, Y)].FullPath);
end;

4.  End the drag operation with any "end drag" specific routines:
procedure TFMForm.FileList1EndDrag(Sender, Target: TObject; X, Y: Integer);
begin
  if Target <> nil then FileList1.Update;
end;

Normally you don't even use a variable like MoveSelectedItem.  You use the Selected array when doing Multiselect and the ItemIndex when doing single item drag and dropping.  Let the control keep track of what your moving.  That way you don't have to keep up with it and you don't have another variable.

I must correct myself on one point.  Delphi automatically sends an EndDrag when a control says it can accept the item you are dragging and you actually drop it.  The EndDrag can be used to cancel a drag by specifying False (don't drop).  So "technically" you don't need to specify it--you may still want to keep it as a safeguard if you want to.

BTW, the above information is all available in the help under "drag-and-drop operations."  In Delphi 3 just choose the "Dragging and Dropping" from the list that pops up.  You should be able to get straight to "Dragging and Dropping" from D2 help.

Most likely the "item underneath" the drop point will select if you position the mouse just on the border between two items and drop.  Your mouse will move slightly and may select another item as well.  That is all I can think of that would cause that.

To select the focused item after a drop, you can use this code:
 (Sender as TListbox).Selected[(Sender as TListBox).ItemIndex] := True;

Good Luck,
Scott
0
 

Author Comment

by:tomcorcoran
ID: 1347121
Scott,

Thanks for the prompt reply. I program in D1, hence no access to TListView, etc. Interesting info on BeginDrag and EndDrag.

I assume putting the BeginDrag in the OnMouseDown above was a Freudian slip :-).

In your sample code for OnMouseMove you use itemindex though multiselect is true, is that not a problem?

There's no problem selecting the focused item I was jusy wondering what your recommendation on that was as currently it leaves an item focuse in the listbox, probably it's better to unfocus it and select the first item in the box.

Thanks a lot.

Tom.
0
 
LVL 1

Expert Comment

by:millerw
ID: 1347122
>In your sample code for OnMouseMove you use itemindex though
>multiselect is true, is that not a problem?

I answered that above.  You don't even need that line of code if you do it right.  You shouldn't even need the variable MoveSelectedItem and thus don't need to assign ItemIndex to it.

As for if it is a problem, "no" is the answer.  ItemIndex actually points to the Item that has the FOCUS and one item in the list must have the focus. But it can end up where no item has the focus (eg.  first tabbing into the list will have no item focused and you have to press an arrow key to focus/select one).  Thus, if you use ItemIndex, always make sure you check for it being -1 for that first tab into the listbox.  Other than this, it should never be -1 (although strange things have happened to me and it ended up being -1 after a drag so just watch for it).  But, since there is this exception, you have to prepare for it.  

To sum that all up in a sentence:
Even with multiselect, ItemIndex points to an item--the focused one.

Good Luck,
Scott
0

Featured Post

Do You Know the 4 Main Threat Actor Types?

Do you know the main threat actor types? Most attackers fall into one of four categories, each with their own favored tactics, techniques, and procedures.

Join & Write a Comment

Objective: - This article will help user in how to convert their numeric value become words. How to use 1. You can copy this code in your Unit as function 2. than you can perform your function by type this code The Code   (CODE) The Im…
In my programming career I have only very rarely run into situations where operator overloading would be of any use in my work.  Normally those situations involved math with either overly large numbers (hundreds of thousands of digits or accuracy re…
Illustrator's Shape Builder tool will let you combine shapes visually and interactively. This video shows the Mac version, but the tool works the same way in Windows. To follow along with this video, you can draw your own shapes or download the file…
This demo shows you how to set up the containerized NetScaler CPX with NetScaler Management and Analytics System in a non-routable Mesos/Marathon environment for use with Micro-Services applications.

706 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

Need Help in Real-Time?

Connect with top rated Experts

22 Experts available now in Live!

Get 1:1 Help Now