Watch, Follow, &
Connect with Us

For forums, blogs and more please visit our
Developer Tools Community.


Welcome, Guest
Guest Settings
Help

Thread: virtual TListView drag and drop (with OwnerData set)


This question is not answered. Helpful answers available: 2. Correct answers available: 1.


Permlink Replies: 8 - Last Post: Feb 1, 2016 9:05 PM Last Post By: nilesh shinde
nilesh shinde

Posts: 47
Registered: 10/5/13
virtual TListView drag and drop (with OwnerData set)  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Jan 22, 2016 3:03 AM
Hi,

I am trying to perform simple drag drop operation in TListView (C++) when I am populating list item using OnData event (virtual mode). Following is the sample code that I am trying. When I drag a label over TListView control, I am getting two rows selection (MultiSelect is false for TListView control). Out of two rows, one row is fix selected and gets selected when we enter list view during drag operation. Other row selection changes as per drag over event. Two rows selection creating confusion is user interface.

Please help me understand how I can achieve simple drag drop operation in TListView in virtual mode.

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

#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma link "bsSkinCtrls"
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormCreate(TObject *Sender)
{
bsSkinListView1->Items->BeginUpdate();
bsSkinListView1->Clear();
bsSkinListView1->Items->Count = 5;
bsSkinListView1->Items->EndUpdate();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::bsSkinListView1Data(TObject *Sender, TListItem *Item)
{
Item->Caption = L"Item";
}
//---------------------------------------------------------------------------
void __fastcall TForm1::bsSkinLabel1MouseDown(TObject *Sender, TMouseButton Button,
TShiftState Shift, int X, int Y)
{
bsSkinLabel1->BeginDrag(false,-1);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::bsSkinListView1DragOver(TObject *Sender, TObject *Source,
int X, int Y, TDragState State, bool &Accept)
{
Accept = true;

TListItem *listItem;
listItem = bsSkinListView1->GetItemAt(X, Y);
bsSkinListView1->Selected = listItem;
bsSkinListView1->ItemFocused = listItem;
}
//---------------------------------------------------------------------------

Thanks,
Nilesh
Remy Lebeau (Te...


Posts: 9,447
Registered: 12/23/01
Re: virtual TListView drag and drop (with OwnerData set)  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Jan 22, 2016 1:48 PM   in response to: nilesh shinde in response to: nilesh shinde
nilesh wrote:

Following is the sample code that I am trying. When I drag a label
over TListView control, I am getting two rows selection (MultiSelect
is false for TListView control). Out of two rows, one row is fix
selected and gets selected when we enter list view during drag
operation. Other row selection changes as per drag over event. Two
rows selection creating confusion is user interface.

There is a difference between a truely selected list item and a hot-tracked
list item. Hot tracking follows the cursor to show the user which item a
drop will occur on. That can be different than the list item that is actually
selected. TListItem has separate Selected and DropTarget properties to represent
these two states, and TListView has a DropTarget property to indicate the
current hot-track item.

void __fastcall TForm1::FormCreate(TObject *Sender)

DO NOT use the OnCreate event in C++. It is a Delphi idiom that can produce
illegal behavior in C++. Your OnCreate code belongs in the actual constructor
instead.

bsSkinListView1->Items->BeginUpdate();
bsSkinListView1->Clear();
bsSkinListView1->Items->Count = 5;
bsSkinListView1->Items->EndUpdate();

You don't need the Clear() and (Begin|End)Update() calls just to set a new
Count value.

listItem = bsSkinListView1->GetItemAt(X, Y);
bsSkinListView1->Selected = listItem;

You should not be setting the Selected property during dragging. If fact,
you should not be setting anything at all, except for the Accept parameter
to indicate whether dropping is allowed or not. TListView automatically
tracks the current hot track item for you and updates the TListItem.DropTarget
and TListView.DropTarget properties as needed.

What you can do, if anything, is in the OnDragDrop event, set the Selected
property of the final node you want selected.

--
Remy Lebeau (TeamB)
nilesh shinde

Posts: 47
Registered: 10/5/13
Re: virtual TListView drag and drop (with OwnerData set)  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Jan 25, 2016 12:54 AM   in response to: Remy Lebeau (Te... in response to: Remy Lebeau (Te...
I corrected code as per your suggestion (see below). Now after drop operation appropriate row is getting selected. However during drag over no row gets selected to reflect row mouse pointer is pointing. I mean during drag over there is no change in row selection as per the mouse pointer movement. I haven't changed any other properties TListView except OwnerData=true. Do I have set any other property to achieve this?

As per your earlier comment in 'bsSkinListView1DragOver' event handler I just have to set Accept=true, rest other will get handled by TListView control. But seems its not working in my code. So how I can show the row selection during drag over operation?
//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma link "bsSkinCtrls"
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
bsSkinListView1->Items->Count = 15;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::bsSkinListView1Data(TObject *Sender, TListItem *Item)
{
Item->Caption = L"Item";
}
//---------------------------------------------------------------------------
void __fastcall TForm1::bsSkinLabel1MouseDown(TObject *Sender, TMouseButton Button,
TShiftState Shift, int X, int Y)
{
bsSkinLabel1->BeginDrag(false,-1);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::bsSkinListView1DragOver(TObject *Sender, TObject *Source,
int X, int Y, TDragState State, bool &Accept)
{
Accept = true;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::bsSkinListView1DragDrop(TObject *Sender, TObject *Source,
int X, int Y)
{
TListItem *listItem;
listItem = bsSkinListView1->GetItemAt(X, Y);
bsSkinListView1->Selected = listItem;
bsSkinListView1->ItemFocused = listItem;
}
//---------------------------------------------------------------------------

Edited by: nilesh shinde on Jan 25, 2016 12:55 AM
Remy Lebeau (Te...


Posts: 9,447
Registered: 12/23/01
Re: virtual TListView drag and drop (with OwnerData set) [Edit]  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Jan 25, 2016 12:52 PM   in response to: nilesh shinde in response to: nilesh shinde
nilesh wrote:

I corrected code as per your suggestion (see below). Now after drop
operation appropriate row is getting selected. However during drag
over no row gets selected to reflect row mouse pointer is pointing. I
mean during drag over there is no change in row selection as per the
mouse pointer movement. I haven't changed any other properties
TListView except OwnerData=true. Do I have set any other property
to achieve this?

Sorry, my bad. I forgot that DropTarget functionality is not supported at
the Win32 layer when OwnerData=true, only selection and focus are supported.
To simulate the DropTarget effect in a virtual ListView, you have to owner-draw
the items manually to draw your own selection boxes as the mouse moves around
the items.

--
Remy Lebeau (TeamB)
nilesh shinde

Posts: 47
Registered: 10/5/13
Re: virtual TListView drag and drop (with OwnerData set) [Edit]  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Jan 25, 2016 5:59 PM   in response to: Remy Lebeau (Te... in response to: Remy Lebeau (Te...
Let me try that.
Meanwhile could you please provide me sample code for same i.e single row selection during drag over operation. Also tell me what properties you have set of ListView. This will help me to move forward in my project.

Thanks,
Nilesh
Remy Lebeau (Te...


Posts: 9,447
Registered: 12/23/01
Re: virtual TListView drag and drop (with OwnerData set) [Edit]  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Jan 25, 2016 6:51 PM   in response to: Remy Lebeau (Te... in response to: Remy Lebeau (Te...
Remy wrote:

Sorry, my bad. I forgot that DropTarget functionality is not
supported at the Win32 layer when OwnerData=true, only
selection and focus are supported.

Forget what I said, I just got it working, but not by using the DropTarget
properties, but instead by handling the LVN_GETDISPINFO notification directly,
eg:

class TForm1 : public TForm
{
__published:
    TListView *bsSkinListView1;
    TLabel *bsSkinLabel1;
    void __fastcall bsSkinLabel1MouseDown(TObject *Sender, TMouseButton Button, 
TShiftState Shift, int X, int Y);
    void __fastcall bsSkinListView1DragOver(TObject *Sender, TObject *Source, 
int X, int Y, TDragState State, bool &Accept);
    void __fastcall bsSkinListView1DragDrop(TObject *Sender, TObject *Source, 
int X, int Y);
    void __fastcall bsSkinListView1Data(TObject *Sender, TListItem *Item);
private:
    int bsSkinListView1DropTargetIndex;
    TWndMethod bsSkinListView1PrevWndProc;
    void __fastcall bsSkinListView1WndProc(TMessage &Message);
public:
    __fastcall TForm1(TComponent *Owner);
};
 
...
 
#include <commctrl.h>
 
__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
    bsSkinListView1PrevWndProc = bsSkinListView1->WindowProc;
    bsSkinListView1->WindowProc = &bsSkinListView1WndProc;
    bsSkinListView1DropTargetIndex = -1;
    bsSkinListView1->Items->Count = 15;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::bsSkinListView1WndProc(TMessage &TMessage)
{
    bsSkinListView1PrevWndProc(Message);
    if (Message.Msg != CN_NOTIFY) return;
    if (reinterpret_cast<TWMNotify&>(Message).NMHdr.code != LVN_GETDISPINFO) 
return;
    NMLVDISPINFO *pdi = reinterpret_cast<NMLVDISPINFO*>(Message.LParam);
    if ((pdi->item.iItem == bsSkinListView1DropTargetIndex) &&
        (pdi->item.mask & LVIF_STATE))
    {
        pdi->item.state |= LVIS_DROPHILITED;
        pdi->item.stateMask |= LVIS_DROPHILITED;
    }
}
//---------------------------------------------------------------------------
void __fastcall TForm1::bsSkinListView1Data(TObject *Sender, TListItem *Item)
{
    Item->Caption = L"Item";
    //Item->DropTarget = (Item->Index == bsSkinListView1DropTargetIndex);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::bsSkinLabel1MouseDown(TObject *Sender, TMouseButton 
Button,
         TShiftState Shift, int X, int Y)
{
    bsSkinLabel1->BeginDrag(false, -1);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::bsSkinListView1DragOver(TObject *Sender, TObject 
*Source, int X, int Y, TDragState State, bool &Accept)
{
    Accept = true;
    int NewIndex;
    if (State == dsDragLeave)
        NewIndex = -1;
    else
    {
        LVHITTESTINFO info = {0};
        info.pt = Point(X, Y);
        NewIndex = ListView_HitTest(bsSkinListView1->Handle, &info);
    }
    if (bsSkinListView1DropTargetIndex != NewIndex)
    {
        bsSkinListView1DropTargetIndex = NewIndex;
        bsSkinListView1->Invalidate();
    }
}
//---------------------------------------------------------------------------
void __fastcall TForm1::bsSkinListView1DragDrop(TObject *Sender, TObject 
*Source, int X, int Y)
{
    bsSkinListView1DropTargetIndex = -1;
    TListItem *listItem = bsSkinListView1->GetItemAt(X, Y);
    bsSkinListView1->Selected = listItem;
    bsSkinListView1->ItemFocused = listItem;
    bsSkinListView1->Invalidate();
}
//---------------------------------------------------------------------------


--
Remy Lebeau (TeamB)
nilesh shinde

Posts: 47
Registered: 10/5/13
Re: virtual TListView drag and drop (with OwnerData set) [Edit]  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Jan 27, 2016 2:19 AM   in response to: Remy Lebeau (Te... in response to: Remy Lebeau (Te...
Thanks for providing me the sample code. Its working for drag row. But one issue I am facing here is as soon as I drag the label on ListView, last visible item in list view changes his state as selected/focused along with drag highlighted row. How can I rectify this problem of row automatically getting selected. Again please provide me the updated sample code here.

Also sometime I have observed that existing manually selected row does not gets cleared on next manual row selection, causes two row in selected state(next selection is after drag operation). Any idea?

Thanks,
Nilesh

Edited by: nilesh shinde on Jan 27, 2016 3:28 AM
Remy Lebeau (Te...


Posts: 9,447
Registered: 12/23/01
Re: virtual TListView drag and drop (with OwnerData set) [Edit]  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Jan 27, 2016 3:10 AM   in response to: nilesh shinde in response to: nilesh shinde
nilesh wrote:

one issue I am facing here is as soon as I drag the label on ListView,
last item visible in list view changes his state as selected/focused
along with drag highlighted row.

If I had to venture a guess, it is likely related to how TListView uses a
single internal TListItem object to manage all of the virtual items. Any
code that uses TListItem pointers will reuse the same object. And it seems
that the ListView's internal DropTarget handling is probably not complete
OwnerData-aware.

How can I rectify this problem.

I can't answer that. But I can tell you that when working with virtual a
ListView, it is usually best to not use any of the TListItem-based properties
and methods, but instead go straight to the Win32 API, working with list
item indexes and handling ListView notifications. Then you don't have to
worry as much about that internal TListItem getting in the way.

Again please provide me the updated sample code here.

I would rather not write more code for this. If you want to use a virtual
ListView, you need to learn how to work with it. It is not trivial, but
the benefits can be worth it if you have a lot of data to display.

--
Remy Lebeau (TeamB)
nilesh shinde

Posts: 47
Registered: 10/5/13
Re: virtual TListView drag and drop (with OwnerData set) [Edit]  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Feb 1, 2016 9:05 PM   in response to: Remy Lebeau (Te... in response to: Remy Lebeau (Te...
I am able to achieve it using following code. You just need to disable row select property of TListView.

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

#include <vcl.h>
#pragma hdrstop

#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma link "bsSkinCtrls"
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
bsSkinListView1->Items->Count = 7;
dragging = false;
dragtarget = -1;
defaultFont = bsSkinListView1->Font;
}
//---------------------------------------------------------------------------
void __fastcall TForm1::bsSkinListView1Data(TObject *Sender, TListItem *Item)
{
Item->Caption = L"Item";
}
//---------------------------------------------------------------------------
void __fastcall TForm1::bsSkinLabel1MouseDown(TObject *Sender, TMouseButton Button,
TShiftState Shift, int X, int Y)
{
bsSkinLabel1->BeginDrag(false,-1);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::bsSkinListView1DragOver(TObject *Sender, TObject *Source,
int X, int Y, TDragState State, bool &Accept)
{
Accept = true;

dragging = true;
if(State == dsDragLeave)
{
dragging = false;
}

TListItem *listItem;
listItem = bsSkinListView1->GetItemAt(X, Y);
if(listItem != NULL)
{
bsSkinListView1->UpdateItems(dragtarget,dragtarget);
dragtarget = listItem->Index;
}

bsSkinListView1->UpdateItems(dragtarget,dragtarget);
}
//---------------------------------------------------------------------------


void __fastcall TForm1::bsSkinListView1DragDrop(TObject *Sender, TObject *Source,
int X, int Y)
{
dragging = false;
TListItem *listItem;
listItem = bsSkinListView1->GetItemAt(X, Y);
listItem->Selected = true;
listItem->Focused = true;
}
//---------------------------------------------------------------------------

void __fastcall TForm1::bsSkinListView1AdvancedCustomDrawItem(TCustomListView *Sender,
TListItem *Item, TCustomDrawState State, TCustomDrawStage Stage,
bool &DefaultDraw)
{

if(dragging && Item->Index == dragtarget)
Sender->Canvas->Brush->Color = clBlue;
else
Sender->Canvas->Brush->Color = clWhite;

Item->DropTarget = false;

}
//---------------------------------------------------------------------------

Legend
Helpful Answer (5 pts)
Correct Answer (10 pts)

Server Response from: ETNAJIVE02