Introduction
This control is an extension of the code in the article entitled "Manual reordering of items inside a ListView" by mav.northwind. Mav gets the credit. I simply needed a few more features, so I put them in, and am posting this for those who may benefit. Additionally, I adapted the auto-scrolling features discussed by Les Smith in his article ListView Drag & Drop With Auto Scrolling in .NET (Upgraded).
The following features were added to the control developed by mav.northwind:
- Multi-select item reordering functionality
- Auto-scroll the
ListView
while reordering
- Enable/Disable reordering functionality with the
DisableItemReorder
property
- Ability to lock the first (n) items in the list with the
ReorderStartIndex
property
- Change the color of the insertion bar with the
InsertionLineColor
property
Additionally, I moved the ListView
event handlers for reordering into the ListView
control itself so there is no need to handle anything within the form. Finally, since my project needed to be done in VB, I changed the code from C# for all you VB programmers.
Background
For background on the ideas in this article, you should consult the article by mav.northwind, Manual reordering of items inside a ListView. There are other solutions on how to do this, typically using the drag/drop events. I liked this method as it is clean and leaves the drag/drop free for handling drag/drop from external items. I also recommend Les Smith's article, ListView Drag & Drop With Auto Scrolling in .NET (Upgraded) dealing with the same topic, but addressing the auto-scroll functionality as well.
Using the Code
To use this code, simply add the custom control class ReorderListView
to your project. Build the control class, then use the ReorderListView
control on a windows Form. The sample code and project are done in VS2008; however, the code should work fine in earlier versions.
Points of Interest
Support for handling multiple selected items is done simply by first storing the selected items into a list collection, then removing them from the ListView
, and finally, inserting them all back into the ListView
at the proper index. As for auto-scrolling, it is simply a matter of using the SendMessageA
function to scroll the control. A timer was added and the onTick
event handled to initiate the SendMessage
routine, thus the timer Interval
sets the frequency with which the SendMessage
routine is called. This results in more frequent SendMessage
calls when the timer Interval
value is small, hence faster scrolling, and fewer calls when the timer Interval
is greater for slower scrolling.
Public Sub New()
InitializeComponent()
SetStyle(Windows.Forms.ControlStyles.OptimizedDoubleBuffer, True)
tmrLVScroll.Enabled = False
tmrLVScroll.Interval = 100
End Sub
#Region "Scrolling Support"
Private Declare Function SendMessage Lib "user32" _
Alias "SendMessageA" (ByVal hwnd As Integer, _
ByVal wMsg As Integer, ByVal wParam As Integer, _
ByRef lParam As Object) As Integer
Private Sub tmrLVScroll_Tick(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles tmrLVScroll.Tick
ScrollControl(Me, intScrollDirection)
End Sub
Private Sub ScrollControl(ByRef objControl As Control, ByRef intDirection As Integer)
Const WM_SCROLL As Integer = &H115S
SendMessage(objControl.Handle.ToInt32, WM_SCROLL, intDirection, VariantType.Null)
End Sub
#End Region
Now, it's just a matter of enabling the timer to scroll and ascertaining the direction of scroll, and disabling it when not scrolling. To enable/disable the timer at the right time, I added a small bit of code in the MouseMove
event shown below. I used the top item in the list rather than the top of the control itself to determine when the control should scroll up so that the scrolling works whether the column headers are displayed or not.
Dim HeaderOffset As Integer
If Me.View = Windows.Forms.View.Details Then
HeaderOffset = Me.TopItem.GetBounds(ItemBoundsPortion.Entire).Top
If e.Y <= HeaderOffset + Me.Font.Height / 1.5 Then
intScrollDirection = 0
tmrLVScroll.Enabled = True
ElseIf e.Y >= Me.ClientSize.Height - Me.Font.Height / 1.5 Then
intScrollDirection = 1
tmrLVScroll.Enabled = True
Else
tmrLVScroll.Enabled = False
End If
This was quite a simple implementation, so there's no real mystery here. No fancy coding, everything is pretty straightforward, but that's what I prefer as there's fewer "undocumented features". I should also point out that items do get reordered as far as their index values are concerned in any view (Large Icon, List, Details, etc.); however, the items are not redrawn correctly in the ListView
for views other than list and details, so they don't appear to have changed until the view is changed, then changed back, (or maybe if all the items are re-loaded). Since I only needed the details view anyway, I didn't bother to pursue that, but if you want to reorder items in the Large Icon view, this is a good place to start. I did correct the way the insertion bar displays for those other views from the original article. Thanks to mav.northwind for the original post. I hope this can help someone out.
Due to time constraints, I don't plan to support this, so I don't expect to post new updates. Use it as is - but if you do have a suggestion for fixing the display update after reordering in Large Icon and other views, I'm sure someone would like to hear about it. Thanks to all!
History
- March 3, 2009 - Initial post.