Anda di halaman 1dari 3

Introduction (Ver ListViewEmbeddedControls.

jpg imagen de un ejemplo)

Recently, I stumbled across several requests in various news groups on how to embed
controls within a ListView.

There are several owner-drawn ListView controls here on CP, but I wanted to try and
bend the standard ListView to my will... Wink | ;)
Approach
When you're intending to embed a control in a ListView, you'll have to make sure
it's positioned correctly all the time. This can become difficult in several ways:

The position and size of a ListViewSubItem can be modified in various ways (for
example, resizing the ListView, scrolling, resizing ColumnHeaders, and so on).
The default implementation of ListView doesn't have any way to tell you the
size and location of ListViewSubItems.
Columns can be reordered.
ListViewItems can be sorted.

The easiest way to ensure the correct position would be right where the painting
occurs, so I decided to override the ListView's WndProc and listen for WM_PAINT as
a trigger to calculate the controls' positions.

There may be other, more efficient ways, but then it's really hard to get all the
cases in which a control has to be re-positioned. Besides, I didn't find
performance problems with a reasonable number of embedded controls.
Obtaining a cell's position and size

That's a little tricky, as the standard ListView won't help you here. It does have
a GetItemRect() method, but it only gives you information on the entire
ListViewItem. No way to retrieve the bounds of a certain ListViewSubItem here.

Luckily, I've been confronted with the same problem in a previous article of mine
(In-place editing of ListView subitems), so the necessary functions were available
already.

Basically, I get the height and vertical position of the cell from GetItemRect()
and calculate its horizontal position and width from the current ColumnHeaders.

To calculate the left margin of a cell, you just have to sum up the widths of all
ColumnHeaders left of your cell, i.e., with indices smaller than your
ListViewSubItem's index, right? Unfortunately, not. Columns can be reordered by the
users and the ListView's Columns collection doesn't reflect these changes :(

So, I had to resort to interop to get the current display order for the columns.
There's a message LVM_GETCOLUMNORDERARRAY the ListView understands to give you the
current column order in the form of an int array:

'----------------------------------------------->>

''' Retrieve the order in which columns appear


''' <returns>Current display order of column indices</returns>
Protected Function GetColumnOrder() As Integer
Dim lPar As IntPtr =
Marshal.AllocHGlobal((Marshal.SizeOf(GetType(System.Int32)) * Columns.Count))
Dim res As IntPtr = SendMessage(Handle, LVM_GETCOLUMNORDERARRAY, New
IntPtr(Columns.Count), lPar)
If (res.ToInt32 = 0) Then
Marshal.FreeHGlobal(lPar)
Return Nothing
End If

Dim order() As Integer = New Integer((Columns.Count) - 1)


Marshal.Copy(lPar, order, 0, Columns.Count)
Marshal.FreeHGlobal(lPar)
Return order
End Function
'----------------------------------------------->>

Once I had this array, I could simply sum up the widths of the columns displayed
left of the cell in question.
Positioning the embedded control

That's the easiest part. Once I had the correct position and size of a
ListViewSubItem, I only had to assign this information to the embedded control's
Bounds property in the ListView's Paint event.
What about sorting?

My first tests didn't include sorting the ListView. My first tests also just held
the row and column number of the embedded control as a reference to where to put
the control.

The problem arose when I allowed the user to sort the ListView. All ListViewItems
changed their position but none of the embedded controls did. What had happened?

When a ListView is sorted, the ListViewItems change their position in the Items
collection. That's OK, but after sorting, they also have their Index property
changed to reflect the current position in the collection and not the position at
which they were added originally.

Luckily, this behavior could be fixed easily by adding a reference to the


ListViewItem in question to my management structure. Now, I could retrieve the
right display position of the ListViewItem as well.
Using the new ListView

To embed a given control in the new, extended ListView, you have two new methods:

'----------------------------------------------------------------------------------
-------------------------'

Public Overloads Sub AddEmbeddedControl(ByVal c As Control, ByVal col As Integer,


ByVal row As Integer)
End Sub

Public Overloads Sub AddEmbeddedControl(ByVal c As Control, ByVal col As Integer,


ByVal row As Integer, ByVal dock As DockStyle)
End Sub

'----------------------------------------------------------------------------------
-------------------------'

The second function allows you to specify how the control is positioned and sized
in its target cell. Usually, you'd use DockStyle.Fill to let the control use the
entire SubItem rectangle (default value if you don't give the dock parameter). If
you don't want your control to be resized in both directions, you can specify one
of the other DockStyles. If you specify DockStyle.None, your control will not be
resized at all and thus might overlap other parts of the ListView.

There are also methods to remove a given control or query the ListView for the
control embedded at a certain position.

Anda mungkin juga menyukai