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

Embedding Controls in a ListView

By , 30 Dec 2004
 

Sample Image - ListViewEmbeddedControls.jpg

Introduction

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... ;)

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:

/// <summary>
/// Retrieve the order in which columns appear
/// </summary>
/// <returns>Current display order of column indices</returns>
protected int[] GetColumnOrder()
{
  IntPtr lPar = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(int)) * Columns.Count);
  IntPtr res = SendMessage(Handle, LVM_GETCOLUMNORDERARRAY, 
               new IntPtr(Columns.Count), lPar);
  if (res.ToInt32() == 0) // Something went wrong
  {
    Marshal.FreeHGlobal(lPar);
    return null;
  }
  int [] order = new int[Columns.Count];
  Marshal.Copy(lPar, order, 0, Columns.Count);
  Marshal.FreeHGlobal(lPar);
  return order;
}

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 void AddEmbeddedControl(Control c, int col, int row);
 
public void AddEmbeddedControl(Control c, int col, int row, DockStyle dock);

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.

About the demo

I've added a little demo project so that you can try out the new ListView and its features.

While building the demo, I tried embedding a RichTextBox in the ListView and it worked quite nicely, but I was annoyed to find out that I couldn't keep the RichTextBox from being selected, so I've also included a ReadOnlyRichTextBox class to the demo.

I don't think this qualifies for a separate article, but you can use this ReadOnlyRichTextBox as well with your projects, when you want a label with pretty formatting.

Please feel free to comment on the article and don't forget to vote!

Release History

  • 31.12.2004 V1.0

    Initial release.

License

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

About the Author

mav.northwind
Software Developer (Senior) 4voice AG
Germany Germany
No Biography provided

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   
GeneralAusgezeichnet!membertoneware16-Oct-12 3:52 
Vielen Dank! Well written and informative.
QuestionJust awesome.memberpdoxtader21-Jun-12 4:57 
Really excellent. Thanks for this!
GeneralMy vote of 4memberfaley21-Mar-12 0:26 
great job mav Smile | :)
QuestionButton for each row?memberMarius Iulian Mihailescu7-Feb-12 10:22 
Hi,
 
You have done great job.
 
I have used this control to add a button for each row, but it seems when I parse the items in listview:
for (int i = 0; i < lvAllWorkpapers.Items.Count; i++)
{
lvAllWorkpapers.AddEmbeddedControl(but, 6, i);
}
 
the result will be only one button for a single row, not for each row.
 
Could you advice me what to do in this case? I have tried many methods but it seems that they don't work.
 
Thanks
Marius Iulian Mihailescu
Computer Security

AnswerRe: Button for each row?memberfaley21-Mar-12 0:35 
maybe you can do like this:
 
Button but;
for (int i = 0; i < lvAllWorkpapers.Items.Count; i++)
{
   but = new Button();
   but.Name = "but" + i.toString();
   but.Text = "Iam Button " + i.toString();
   but.Click += someButtonEvent_Click;
   lvAllWorkpapers.AddEmbeddedControl(but, 6, i);
}

QuestionBug in Progress bar displaymemberYKK Reddy27-Dec-11 23:15 
Iam facing a problem in displaying the Progress bar on a list.
Modify the size of the list such that the added Embedded control (Progress bar) row is not visible. When the progress bar is added, instead of showing at the respective column, it is showing at the top corner of the list box.
Is there any fix ?
AnswerRe: Bug in Progress bar displaymemberStuartbaker428-Jan-13 13:41 
refresh the listview, or do application.doevents() either works
QuestionGet ListViewItem from Click eventmembercrazyfool21000029-Sep-11 21:57 
Perhaps there is another way to do this, but when I add a custom event handler to a button control that I add to the listView, there doesn't seem to be a way to get the original ListViewItem that the control is attached to. This is because the ListViewItem is stored in the EmbeddedControl struct, which is not visible outside the control class.
 
I added this to the listview class:
 
        public ListViewItem GetListViewItem(Control control)
        {
            foreach (EmbeddedControl ec in _embeddedControls)
            {
                if (ec.Control == control)
                {
                    return ec.Item;
                }
            }
 
            return null;
        }
 
So now in a Form class, for example, we can do something like this to retrieve the ListViewItem that the control is attached to:
 
Populate the listView (with 2 columns):
            foreach (string email in emails) // a list of emails, for example
            {
                ListViewItem item = new ListViewItem();
                item.Text = email; // Column 1
                listView.Items.Add(item);
                
                Button b = new Button();
                b.Text = "Remove";
                b.BackColor = SystemColors.Control;
                b.Font = this.Font;
                b.Click += new EventHandler(Remove_Click); // Add event handler for each button

                listView.AddEmbeddedControl(b, 1, item.Index); // Add button to Column 2
            }
 
Event Handler:
        private void Remove_Click(object sender, EventArgs e)
        {
            ListViewItem item = listView.GetListViewItem((Control)sender);
            MessageBox.Show("Removing: " + item.Text);
            listView.Items.Remove(item);
        }

QuestionHow do I change the height of the rows?memberpeter_m_22-Sep-11 7:16 
Great post, but I have one question. I added buttons and a textbox to your extended list view but my textbox ends up a different height than the buttons, and if I have multiple rows in the list view they don't align properly. The first row in my listviewex is also taller than the remaining rows, the tops of which are rendered over each other. I tried changing the ListViewEx font and the form font, but the problem was still there.
Generalexcellent postmemberYael018-Aug-10 13:08 
Thanks for the post its quite usefull i was trying to embed a calendar in a listview and also a label in separate columns this should work well if i can make it work in a edited listview from another developer like (devcomponents)
GeneralSorting the progressbarsmemberTimotei_Younge21-Jun-10 1:05 
Hi,
 
This is awesome, but could you please explain how the sorting of the embedded controls works? I am not well versed in C# and I am trying to get embedded controls (progressbars) in a ListView to sort but cannot figure it out (using VB .NET) I had a look through the code you made available but am finding it hard to figure out how the sorting of the controls are accomplished.
 
Thanks!
Questionadding controls to datagridviewsmemberymaod23-Mar-10 5:17 
Hi,
 
This is an excellent piece of work!
Do you by any chance have this for a datagridview too?
GeneralMy vote of 1memberBerry1418-Mar-10 4:12 
-
GeneralHelp... embedded control is not highlighting the selected rowmemberTien Pham22-Jul-09 5:55 
I've read the other comment regarding this issue but none of them is a solution for me. I have a user control that is a chart-like display panel. I've embed the control into the listviewex and everything is looking good. However, clicking on this embedded control does not invoke the _embeddedControl_Click event so that it can highlight the selected row. Please help!!!
 
thanks!
Questionvb.netmembermrcouthy11-Jun-09 1:19 
hi looks great but can we do it in vb.net?
AnswerRe: vb.netmemberThomas ST4-Nov-09 1:22 
Did you convert it to vb.net? I'm interested Wink | ;)
QuestionHow can I add multiple button control in a field?memberMd. Ali Naser Khan3-Jun-09 23:22 
It is a very nice article. I want to add 2 or 3 buttons in the field (Row 3,Col1)
how it is possible?
AnswerRe: How can I add multiple button control in a field?memberAndrew Courtice7-Jun-11 13:59 
Create a usercontrol with all the controls you want in it. Then embed an instance of the usercontrol to the listview. eg.
 
VB.net Implementation
'Method to embed several controls into the listview in VB.Net
sub Add_Control()
 
'Create new Button Instances
dim button1 as new button
dim button2 as new button
dim button3 as new button
 
'Set the buttons postions/Anchor/Dockstyles etc.
button1.dock = dockstyle.left
button2.dock = dockstyle.fill
button3.dock = dockstyle.right
 
'Add the buttons to the Usercontrol
Usercontrol1.controls.add(button1)
Usercontrol1.controls.add(button3)
Usercontrol1.controls.add(button2)
 
'Embed the Usercontrol into the Listview
ExListView1.AddEmbededControl(Usercontrol1)
end sub
 

And the C# Implementation
 
//Method to embed several controls into the listview in C#
public void Add_Control()
{
	//Create new Button Instances
	button button1 = new button();
	button button2 = new button();
	button button3 = new button();
 
	//Set the buttons postions/Anchor/Dockstyles etc.
	button1.dock = dockstyle.left;
	button2.dock = dockstyle.fill;
	button3.dock = dockstyle.right;
 
	//Add the buttons to the Usercontrol
	Usercontrol1.controls.@add(button1);
	Usercontrol1.controls.@add(button3);
	Usercontrol1.controls.@add(button2);
 
	//Embed the Usercontrol into the Listview
	ExListView1.AddEmbededControl(Usercontrol1);
}
Hope that helps Smile | :)
Questionwhy set Bounds property in the ListView's Paint event?memberflyingxu9-Mar-09 16:28 
I think you can set the bounds of controls in some other event like listView_ColumnWidthChanged(), any reasons?
GeneralChanging BackColor/ForeColormembermikesinfo8-Dec-08 20:15 
As everyone else has been say, great example.
 
My question is this: Is there a easy way, using your code to change the backcolor and forcolor?
 
If so, a little snippet of code would help, thank you in advance.
 
I can't be this stupid

QuestionDockStylemembertdsuper23-Jun-08 3:41 
When I want to embed ComboBox to the ListView ,the ComboBox.Fill doesn't work.
Can you help me? Thanks for any answer.
 
tdsuper
AnswerRe: DockStylemembermav.northwind23-Jun-08 5:27 
ComboBoxes don't usually resize vertically, that's got nothing to do with embedding them inside the LV. You'll have to set the DrawMode to on of the OwnerDraw values and set the ItemHeight property accordingly.
Oh, and of course perform painting yourself.
 
Regards,
mav
 
--
Black holes are the places where God divided by 0...

GeneralRe: DockStylemembertdsuper23-Jun-08 17:09 
Thanks for your reply.I have read others messages.I know that the height of ComboBox depends on the size of its font.So I think if I set the font size of ComboBox smaller than the font size of ListView,the problem will be solved.Is't right?
Expect your reply.
 

tdsuper
Questionhow to bind data with listview controlmemberjayandrath23-May-08 2:02 
please give me a code for databiding process with listview control in C#.net
GeneralThanks Mavmemberjchalfant30-Oct-07 3:49 
This article and your previous article have pointed me in the right direction.

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

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130617.1 | Last Updated 31 Dec 2004
Article Copyright 2004 by mav.northwind
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid