Click here to Skip to main content
Click here to Skip to main content

Reorderable ListView

By , 14 Dec 2009
 
Demo screenshot

Introduction

This work just extends another work I found here at CodeProject, and is inspired mainly with the article "Manual reordering of items inside a ListView". What is new in this project is that it is compact (everything needed is part of the inherited control), and that it supports draging multiple items and automatic scrolling, which I shaped by playlist of Foobar 2000 multimedia player. In this article, I will not describe everything in detail, on the other side I will describe all extended behavior, which I intended to implement.

Drag-and-drop Construction

As you probably found already, some coders, including MSDN writers, prefer to use built in Drag and Drop methods of ListView to implement Reorder by Mouse function. I found this quite limiting, and bringing no advantages at all. It seems also that we can ask, what does reordering have to do with clipboard. I guess the answer is pretty clear, until you need external data to enter ListView. So I used a method quite similar to that in the referred article. The only thing I changed is that the MouseDown event is not used to initiate Draging - both: initialization and progress are done in MouseMove. This is also the place where all values which are derived from mouse position are computed. Override WndProc is used to draw insertion line, when Invalidate is called from MouseMove. Finally this event handler is calling autoscroll initializer, which we are always draging. Of course MouseUp event is used to finalize reordering or eventually to cancel it.

Scrolling Concept

Three images on scrolling principle

Scrolling starts when IsDraging is set to True, and MouseMove occurs in a specified rectangle on top or at the bottom of ListView (Image 1). I defined this area in units of ItemCount, and speed of scrolling is computed from the relative position of mouse to these item sequences. This means that you can set property MaxScrollAreaSize to some count of items, and while your list is high enough to contain this number twice (otherwise Image 2), you can be sure that the control starts to scroll when you place your draging cursor above this number of items on top or at the bottom of the visible area. Scrolling itself is managed by one routine which computes its parameters from the mouse location, and one timer. 

Private Sub StartScroll()
  Dim hp# = Me.GetUserHurryPercentage
  Me.ScrollPerTick = cspt.Invoke(hp, Me.GetMaxItemVisibleCount)
  If Me.ScrollPerTick <> 0 Then
    Me.IsScrolling = True
    Me.ScrollTimer.Interval = csti.Invoke(hp)
    Me.ScrollTimer.Start()
  Else
    Me.ScrollTimer.Stop()
    Me.IsScrolling = False
  End If
End Sub

Note that scrolling parameters are two: count of items per scroll, and scroll interval. This is very important because if we had one of these options static, scrolling would not be very friendly (and fit for reordering process). Both these parameters are computed from HurryPercentage, what is just a position of the cursor above number of top or bottom items, in percent (Image 3). For example if we had this scrollarea to be three items at each side, and we put mouse to the first of them, HurryPercentage should be around 33 percents (= 0.33--). GetHurryPercentage function returns zero, if scrolling should stay turned off (when we are at the start or end already, or when we actually aren't in scroll area), <-1,0) for scrolling up, and (0,1> for scrolling down. Also note that I used delegate functions to convert this value to ScrollTimer interval and to ScrollPerTick<number of items> value. There is no other reason than to give control's user an opportunity to customize scrolling behavior if needed.

Public Delegate Function ScrollPerTickCalculator%_
	(ByVal UserHurryPercentage#, ByVal ItemsPerPage%)
Public Delegate Function ScrollTimerIntervalCalculator%(ByVal UserHurryPercentage#)
Private cspt As ScrollPerTickCalculator = _
	Function(a#, b%) Comparer(Of Double).Default.Compare(a, 0)
Private csti As ScrollTimerIntervalCalculator = _
	Function(a#) CInt(220 - 200 * Math.Abs(a))

These are default functions which in my opinion produce scrolling very similar to the one in Foobar. As you dig into lambdas above, you see that the function computing number of items per tick returns just {-1, 0, 1}, which is only job of every comparer. This means that visual component of scrolling is being smooth always, in default setting here. So only the second component can regulate and regulates scroll speed - you see, ScrollTimerInterval is always somewhere between 220 and 20 milliseconds. This means that above 500 items it gets a little bit boring to scroll from start to end. But still, it is not a very significant delay. If you need more, your functions can speed down TimerInterval for last 30% of hurry, and return ItemsPerPage or its derivate for ScrollPerTick... which will result in option of smooth scrolling and also fast scrolling.

External Drag-and-drop Support

Second version already implements this option. Such implementation brings some new elements to behavior, and of course requires few more properties. You can research, that on DragEnter, EventHandlers managing scrolling and InsertionLine drawing react no more. It is pretty easy to make it up. I simply added new handlers for events like DragOver or DragLeave, and these handlers generally do nothing more than calling MouseMove, or MouseLeave. One remarkable thing is, that when you need to Drag some items from this control, you call DoDragDrop from your MouseLeave when specified mouse button is pressed, so that even if you return with these data back to control, you can't switch somehow from Drag-and-drop mode to MouseMove handled reordering routine. This is solved by data origin recognition in internal DragDrop event handler. BTW, it is possible to provide all functionality based on standard Mouse events with Drag events, with some improvisation (you are not as well informed with DragEventArgs as with MouseEventArgs).

Notice, that every external drop must be confirmed in DragEnter event, by setting Effect to value different from None. All external Drops must come in array of ListViewItem or in Collection to be managed automatically. If this is not possible, CantConvertDrop event is raised, and user can convert them for control, or let event unhandled to cancel Drop operation. DropCancelled event is raised if data couldn't be converted, or if data collection was empty. Notice IsDragingInside and IsDragingOutside properties, they can help you managing DragEnter or MouseLeave events.

Behavior Extensions for other ListView `View` Modes

Second version modifies concept of internal event handling. If you follow naming convence for event handlers (when extending base class) which is described in top-level XML remark, you can just add new behavior to Enum BehaviorExtensionType, and all work with switching eventhandlers for another extension is fully automated for you (see code region Internal handler managing). If you want to extend control by inheritation, you must suffice with protected property AllowedBehaviorExtension by which you can switch between your bases behavior extensions (or disable them).

The reason for pretty though Reflection coding of events is efficiency, but also easy extensibility. With two shadows events already it seems pretty difficult to make some another extension, without some built in helper eventhandler management. You could simply erase all this section and preserve that property. If you mind hardcoding bunch of AddHandler and RemoveHandler statements within Select Case BehaviorExtension. For me it seems much easier to move on with just remembering straight ahead naming convence.

Important Note

  • Remember to set AllowDrop if you need to transfer items between controls.
  • Notice shadows events DragEnter and DragDrop. If you handle DragEnter, you can prevent control from any reaction on crossing Drag-and-drop data by setting Effect to None. And if you do this in DragDrop you can cancel all consequences of Drop.
  • DragDrop accepts only array of ListViewItem or Collection, so ensure, that you allow in and out only this type of data, or that you handle conversion event CantConvertDrop.
  • Control behavior is extended only when its View property is set to Detail.
  • BottomItem & LastVisibleItem property in another View mode may return invalid results (as standard TopItem will).

Public Properties of Control

  • AllowReorder - determines whether reordering and all subsequent behavior is enabled
  • AutoDropFocus - determines whether control focuses itself on successful Drop
  • AutoDropSelect - determines whether control selects Droped data
  • DragMouseButton - gets or sets mouse button used to intitate reorder or drag
  • MaxScrollAreaSize - gets or sets count of items at both ends of list, which maintains scrolling, this number is used when count of visible items >= this number. If there are not enough items visible, a smaller number is used. Basically demanding two items high list to provide reordering and automatic scrolling is nonsense. All routines in this class check if the result number is higher than zero, else do nothing. Result number is computed on every SizeChanged and FontChanged. This property is Designer and DesignerSerialize friendly.
  • CalcScrollPerTick - gets or sets function which computes smoothness of scrolling from user requested speed percentage, you can also use its second parameter, which tells you how long the current page of listview is. This property is not PropertyEditor friendly, nor does it save to designer code.
  • CalcScrollTimerInterval - gets or sets function which computes speed for ScrollTimer. This property is not PropertyEditor friendly, nor does it save to designer code.
  • IsDragingInside - determines whether control is currently in reordering or draging-in state
  • IsDragingOutside - determines whether control was in reordering or draging-in state, but user left its area with stuff. Note, that this property does not reflect origin of current dragdrop data.
  • LastVisibleItem - gets last visible item of ListViewRO, this property is extension of BottomItem function
  • SetReorderAutoScrollParameters - This is just a method which provides setting of all scrolling parameters at once.
  • BottomItem - gets item which is at bottom of DisplayRectangle
  • InsertionLineWidth - gets or sets thickness of insertionline. This property is Designer friendly and is stored in designer code.
  • InsertionLineColor - gets or sets color of insertionline. This property is Designer friendly and is stored in designer code.
  • InsertionLinePen - gets or sets two previous properties at all. This property is not Designer friendly nor it is saved in designer code.

Other Important Exposed Stuff

  • ItemsReordered - public event, bringing few important informations about current reorder or dragdrop operation. On external Drop this event occures before Focus (when AutoDropFocus is on)
  • CantConvertDrop - is raised when Data allowed to come in are Droped in, but are not in recogized format (see important note section).
  • CancelDrop - is raised when Data could not be converted or were empty.
  • Reorder - This routine moves selected items to specified index and raises ItemsReordered event, if necessary.

Limitations

This control in the current state doesn't support external Drop. I can't imagine what is the best logic for extremely subsized ListView, so by default it just loses its Reorder & Autoscroll functionality. Probably you can solve it by setting Drag-and-drop Effect to Scroll+Move, any ideas welcomed.

Discussion

I will be glad if you find some different lambdas for computing scroll parameters. I can then update this code with some Enum, which will switch between them. I am also curious if someone has some idea about moving multiple items when there are spaces in selection - as you may have researched, my code joins all selected items to a single sequence when moved to newIndex.

History

  • 30th August, 2009: Initial post
  • 14th December, 2009: General updates, mostly based on deep bug report and feature request by KPEBEDKO.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

konikula
Software Developer piSoft
Czech Republic Czech Republic
Member
Developing is my hobby and preferred job. Currently looking for good, creative and innovative job.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralMy vote of 1 [modified]memberTL Wallace17 Dec '09 - 11:57 
Very poor and sloppy coding. No conventional code practice was followed. Code is a really bad representation and usage of our beautiful VB.Net language.
 
I have yet to figure out, what, are developers trying to save electronic 'paper' when they force all classes into one file? Dead | X|
 
Keep in mind that there are Visual Basic Coding Conventions[^]
 
The goal of the .NET Framework design guidelines is to encourage consistency and predictability in public APIs. It is strongly recommended that you follow these design guidelines when developing classes and components that extend the .NET Framework and attempt to instruct others in the VB.NET language.
 
Please take some time read/download/print Brad Abrams blog on "Design Guidelines, Managed code and the .NET Framework". Design Guidelines[^] Yes, its in CSharp however, the basic design principals are global. Good luck and happy coding in the future.
 
modified on Thursday, December 17, 2009 6:25 PM

GeneralRe: My vote of 1memberLloyd (Chris) Wilson22 Dec '09 - 5:26 
I downloaded and modified this for Framework 2.0 ChecklistboxRO control, and found the code was documented just fine and was easy to follow. Although this is a small project, I actually think it is a good idea to group similar supporting classes into a single file, despite what the guidelines may say.
 
The author should be commended for making this contribution to the community. Good job and keep up the good work!
 
Chris (another lonely programmer!)
GeneralRe: My vote of 1 [modified]memberkonikula22 Dec '09 - 7:13 
Thank you much for your opinion, It moved these dead waters a bit Smile | :) Actually I ran across Design Guidelines for Developing Class Libraries[^], especially this chapter[^], and I found lot of interesting insights!
 
What I have concluded from article GuideLibs\GuideType\Nesting[^] is, that it's generally not good to nest types. But this "generally" is very pretty general, and may be confusing in this context.
Quotes:
  1. Do not use public nested types as a logical grouping construct; use namespaces for this.
  2. Avoid publicly exposed nested types. The only exception to this is when variables of the nested type need to be declared in rare scenarios such as subclassing or other advanced customization scenarios.
  3. Do not use nested types if the type is likely to be referenced outside of the declaring type. ...For example, an event-handler delegate that handles an event defined on a class should not be nested in the class;...
  4. Do not define a nested type as a member of an interface. Many languages do not support such a construct.


So what? If you check point 3, part in italic, you can reconsider and reconsider if it is right or not to nest EventArgs in their "producer". You can reconsider it infinitely even if you focus on this case.
 
By my opinion, some of EventArgs classes should really rather occupy another level, Support.cControls in this case, because they can be reused. I didn't really thought of such possibility when writing first nor second version of article and code. I will surely make some of stylistic updates arised, but until I'll ship my whole library as a framework, it has no importance at all to. That's also because nested EventArgs classes seems much advanced, nested delegates - they may be more advanced than those EventArgs, and finally, because this article provides also code, and anyone who took some time to read text, comments and feedback threads, will know what to do with code, if it's needed to be absolutely correct (just changing EventArgs, de-nesting subset or all of types, more commenting).
 
I don't know what is to be considered poor and sloppy. Actually this nesting seems, with my fresh new knowledge (thanks MSDN), not big issue in local circumstances. I still have doubts about correctness of that Reflection mechanism, but that's because I didn't try to extend it yet (so it can cause issues when extending). And I know, that with introducing DoDragDrop ('external drag drop') support I probably let some trash in code, and that it can go, at least, under formatting revision. But in this development phase, I don't think it needs to have absolutely correct code-style.
 
Maybe it can be told, that every code, in any phase should be absolutely correct and stylish. But that's not true in any moment. If I start to write function *, I may recomplex subsequent situtations in code in number of times, still modifying used types, buffers. Concept of implementation evolves hand in hand with writing code. And this moment - writing method - can be, in the same manner as presenting and developing this article, decomposed to many garbaged and ungarbaged, correctly-formed and malfunctioning at the moment.
 
If this article was closed for discussion and updates, situation would be bit different. But I tend to extend the code, if there is any feature request.
 
Currently it does its work, maybe it can do more...
 
Thank you for your revision and support again, and I hope I didn't bored you to death (just one joke for such Piece(Of Theory) Big Grin | :-D
 

Best wishes, Matt
 
* its interesting that I do this usually from top of method-body container to its bottom Laugh | :laugh: - but think about it, we all cast as being good programmers, in some way, but noone of us is able to write complex function in direction from return to arguments ... shame on us!!! Dead | X|
 
modified on Thursday, December 24, 2009 11:32 AM

GeneralRe: My vote of 1memberkonikula22 Dec '09 - 16:14 
Lonely loner, on the lonely road ... alone Sniff | :^)
GeneralRe: My vote of 1memberkonikula28 Dec '09 - 13:33 
Wink | ;) I noticed it. You posted me just the current links which I with absurd ignorance to your links found myself, just by an accident. Now I am not sorry again. My position in this thread is absolutely funny and absurd. Bizarre
 
Thumbs Up | :thumbsup:
So thank you without probablies, for posting me interesting and lifesaving links, even if I ignored them absolutely. Confused | :confused:
NewsCancelDrop eventmemberkonikula16 Dec '09 - 16:46 
There's a bug (with no consequences at all). If you mind, you may rewrite CancelDrop `e` type to CancelDropEventArgs. It accidentally stayed EventArgs from initial design. This relates to mistaken info in this article on this event. This event also informs you, that DragDrop you already handled is not external, what is basically thing not described in this version of article, and what is also indicator you can't use until you rewrite event declaration for proper type. Event comes when data was empty,OR was not converted, OR DragDrop was cancelled, OR DragDrop was just internal reordering accidentally leaving control as DragAndDrop for a while. This last option is indicated by e.ReorderCancelled property. I will include this new information with improvements to behavior and look (if any look improvements) Maybe I could make insertion line also horizontally customizable ). Cool | :cool:
 
I am opened to discussion
Matt
GeneralDiscusion Multi Item questionmemberSSDiver211214 Dec '09 - 12:51 
Konikula,
I also have a reorderable listview I developed. Check out gListView - ListView Control with Visual Drag and Drop FeedBack (VB.NET)[^]
 
It does mixed multi item reorder and scrolling.
 
Hope it helps
 
SSDiver2112
GeneralRe: Discusion Multi Item questionmemberkonikula15 Dec '09 - 14:59 
ah, I didn't see caption of your message Smile | :) sorry if you obtained lengthy critic, instead of thanks for discussion Smile | :) I am really sorry
GeneralRe: Discusion Multi Item questionmemberkonikula15 Dec '09 - 15:03 
hm, but you initiate scrolling when almost on border. this maybe requires enumerator behavior selection to be exact. because I start scrolling in specified inside area, and only for no dragdrop I let it scrolling inside when mouse is outside.
GeneralRe: Discusion Multi Item questionmemberSSDiver211215 Dec '09 - 18:05 
I set it up this way so you can change the scroll speed by Dragging the cursor futher and closer to the control.
GeneralRe: Discusion Multi Item questionmemberkonikula16 Dec '09 - 4:28 
Yeah, I see. But what happens when you need reorder listview in fullscreen mode? Smile | :)
GeneralRe: Discusion Multi Item questionmemberSSDiver211216 Dec '09 - 4:38 
The scroll triggers before you are totaly off the control, so it will still scroll. Plus I didn't expect the control to be right up on the edge of a window there is usually some kind of border area.
GeneralRe: Discusion Multi Item questionmemberkonikula16 Dec '09 - 4:59 
And always there is a chance to use wheel, isn't it? You are true, but I am used to count with all possibilities, if they are really possible. But I will have to adapt your idea, it will solve my problems when listview is shrinked unbelievably. I will implement that as behavior change on resize. Also I am sure I will adapt your scrolling cursor, it is one of very usual features which I can adopt from your project with no changes or generalizations to it. Thanks! Wink | ;) You'll be in credits, if you mind.
NewsUpdatememberkonikula4 Dec '09 - 6:30 
A have code updates, but I don't know how to modify this article D'Oh! | :doh: . Any idea?? Thx
GeneralRe: UpdatememberAnt210014 Dec '09 - 7:48 
I think you have to send codeproject the updated code and article. I normally send my updates to submit@codeproject.com. They will then update the article for you.
 
Anthony
 
Check out my desktop conversion software for Windows -
www.universalconverter.net

GeneralRe: Updatememberkonikula14 Dec '09 - 13:15 
Ah, thank you. Wink | ;)
GeneralBottomItem PropertymemberKPEBEDKO2 Dec '09 - 23:57 
I didn't try your code, but I adopted BottomItem property from your class and it returned null when listview had few items or it was scrolled all the way down. So here's a small change:
        public ListViewItem BottomItem
        {
            get
            {
                return GetItemAt(0, DisplayRectangle.Bottom - 1) ?? Items[Items.Count - 1];
            }
        }

GeneralRe: BottomItem Propertymemberkonikula3 Dec '09 - 3:18 
Thank you for your attention. I will proceed necessary updates to code and include you to contributor comment, if you mind. Best regards, Matt. Big Grin | :-D
GeneralRe: BottomItem PropertymemberKPEBEDKO3 Dec '09 - 4:26 
I've run your demo and that thing with BottomItem doesn't seem to affect your code behavior. Instead i've noticed some other issues. Using mousewheel when dragging results in insertion line being drawn without being erased. And it seems strange to me that dragging items over the bottom of the list is way slower than dragging over top.
GeneralRe: BottomItem PropertymemberKPEBEDKO3 Dec '09 - 23:31 
I also noticed that scrolling continues when mouse leaves the list. It doesn't react on mousemove, though it stops on mouseup. I encountered those problems and my first attempt was to hook all events, but then i realized that scrolling should stop on dragleave because user probably wants to drop the items somewhere else and not to change their position in the list.
GeneralRe: BottomItem Propertymemberkonikula4 Dec '09 - 6:36 
Hi. I have no access to this content already (I have no idea why). You can find all your feature requests and bug reports heard here http://quilt.ic.cz/tmp/devfus/ListViewRo.zip[^]. If you have any idea how to edit this page, let me know. Thanks for all your deep analyze Shucks | :->
GeneralRe: BottomItem Propertymemberkonikula14 Dec '09 - 13:00 
You can check out article and files now. It is even more different than file I linked in my last reply. Cheers; PS: sorry for not fullfiling your concept of BottomItem, and going with LastVisibleItem standing for previous version's BottomItem and BottomItem doing still the same stuff. If you have better point in this convence issue, let me know. PSII: I still didn't checked speed difference you issued. PSIII: thank you for moving me forward in this project! Smile | :)
Generalvery good articlememberDonsw12 Sep '09 - 16:35 
Good detail in your article. Learned something from it.
 
cheers,
Donsw
My Recent Article : Backup of Data files - Full and Incremental

GeneralBrill!memberAnt210031 Aug '09 - 0:27 
Thank you very much for this!
 
Anthony Smile | :)
 
Check out my desktop conversion software for Windows -
www.universalconverter.net

GeneralRe: Brill!memberkonikula14 Dec '09 - 13:16 
Anyway, didn't we met at msdn once? Regards, Matt

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130523.1 | Last Updated 14 Dec 2009
Article Copyright 2009 by konikula
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid