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

Managed ListView

, 1 Feb 2013 GPL3
Rate this:
Please Sign up or sign in to vote.
Managed ListView (an advanced .NET C# UserControl ListView).

Snap 1: Shows the demo project first page, it's about adding columns and items in Details view mode.

Snap 2: Shows the demo project second page, it's about advanced functions of Details view mode.

Snap 3: Shows the demo project third page, it's about Thumbnails view mode and drag and drop function.

Introduction

Most Windows applications need a listview control for different purposes. For files browsing, for items listing ...etc., and so you may need a ListView control in your .NET project. Using the Visual Studio Forms Designer, you can drag and drop the ListView from the ToolBox bar into your Windows Form and you're ready to go. This control is good for normal use like adding items, subitems, draw in details with columns ...etc., but when you need to customize this ListView, it gets worse. OK, I'll cut it directly to the point. Why have I written this control?

I started a project called Emulators Organizer http://sourceforge.net/projects/emusorganizer/, this project is written in .NET C#. However that project is about organizing emulators and ROMs (or games) so it basically depends on a listview control. In version 4.6 and older it uses the normal ListView that is provided with Windows. It worked well in old versions but when the program got advanced functions I needed to add more customizations to that ListView. And that was a disaster because of:

  • I had to use columns with reordering ability, the user can sort items when he/she clicks on a column and saves all column settings in the project. Well, this was done after a lot of hard work. The result was a very complex code which wrapped around subitems and indexes (you should remember each index of the subitem and what it represents).
  • The items can be viewed (in details view) as images and texts. Well, you can do that with an item in normal ListView (let's call the the ListView control provided in .NET like that) with the first item using ImagesList. But the problems are:
    • When you reorder the columns (specially the first one), the item image will still be drawn first at the left, showing glitches.
    • You can make only items have an image and text in normal way (details view), but if you want to make subitems have images and texts or have images only (like rating), you have to use the DrawSubItem event in normal ListView. Doing so results in a very, very slow control.
    • If you want to use rating support (or capture mouse clicks over the subitems and make some calculations to do important things like change ration for game or song) you use the DrawSubItem event in the normal ListView but doing so will not solve the problem of 'clicking to change rating' or 'show view rating when the cursor get over the subitem' and you have to write too much complex calculation code and the result is a very slow control.
    • The normal ListView control keeps flashing when scrolling.
  • The thumbnails view (or large icons) is required in Emulators Organizer project to view games as snapshots at the control. Using the ImagesList will cause memory issues for a large number of images, also you'll have to mess around with item image indexes. So I had to use the DrawItem event but this results in a very slow view especially when you zoom in/out. The control hangs for seconds before responding...
  • I can pick more reasons... but I think it's enough to make you hate the normal ListView as much as I do Wink | ;)

Background

So in version 5 of Emulators Organizer, I decided to write a ListView control that provides these features:

  • Very fast draw without flashing.
  • Get rid of column indexes and use another way (column ids).
  • In Details view: each subitem can have image and text.
  • In Details view: can add customized subitems like rating (5 starts rating like you see in Windows Media Player for songs).
  • In Thumbnails view: draw thumbnails fast without hanging problems in zooming.
  • In Thumbnails view: The images draw parameters (image size and coordinates) are calculated automatically and images drawn in "ratio stretch" form.
  • Very easy to use and can be added in any project without problems. (no external components needed but .Net Framework)

The control is called EOListView. I renamed it to ManagedListView (or MLV) to make it sharable.

About MLV

First of all, download the 'demo project' from the top of this article. Extract the source; open the solution using Visual Studio.

You'll find two projects:

  1. ManagedListViewDemo: this is the demo project. We'll talk about it later in this article.
  2. MLV (ManagedListView): this is the managed ListView control. It includes the MLV control and its resources.

What is important is the MLV project, building it will generate the MLV.dll which includes the ManagedListView control. The ManagedListView control is a UserControl which inherits the UserControl class from the System.Windows.Forms namespace.

Open ManagedListView from Solution Explorer to take a look at the designer.

The white space is the ManagedListViewPanel custom control. It inherits the Control class from System.Windows.Forms namespace. ManagedListView.cs is what we use in the application; the ManagedListViewPanel can be used too but it requires additional implementations that is all done in ManagedListView.cs. We'll talk about coding later in this article.

How to add MLV to a .NET project.

If we want to add the MLV to a .NET project, all we have to do is:

  1. Open Visual Studio
  2. Create a new .NET project (Windows Forms application project in this article)
  3. Now to add the control using the DLL only: in the ToolBox strip, right click, then choose "Choose items".
  4. In the Choose ToolBox Items window, click the Browse button.
  5. Browse for MLV.dll in your computer then click OK.
  6. Now you'll see ManagedListView and ManagedListViewPanel listed, make sure they are checked, then click OK to close the window.
  7. After that, you'll find both ManagedListView and ManagedListViewPanel listed in the ToolBox strip, drag and drop the ManagedListView control from the ToolBox strip into your form.

You need to do these steps just once, after that, you'll find the MLV control always listed in the ToolBox strip of Visual Studio ready to use in any project. The reference for MLV.dll will be added automatically in the project after dragging the control into that project. You can also use the DLL the normal way (adding reference, using code ....). It will be nice to use the MLV project directly from the source so you will not have to worry about build configurations.

Adding items to ManagedListView control

After we add the control to a Windows Form:

We need to add items. First, let's use the Details view. Click on the ManagedListview control in the Forms Designer, then in Properties window, look for the ViewMode property and change it to Details.

After that, switch into the code mode for Form1. In Form1.cs, add the following code:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using MLV;// We need this to access the MLV control classes.
namespace WindowsFormsApplication1
{
  public partial class Form1 : Form
  {
       public Form1()
       {
        InitializeComponent();
        // Initialize the mlv control
        // Add columns
        ManagedListViewColumn column = new ManagedListViewColumn();
        column.HeaderText = "Name";// This is the text that will be shown in the control.
        column.ID = "name";// We need to provide id for each column we add. Make the id something easy to remember.
        column.Width = 120;
        managedListView1.Columns.Add(column);
        column = new ManagedListViewColumn();
        column.HeaderText = "Size";
        column.ID = "size";// We use different id for this one, Never use same id for 2 or more columns.
        column.Width = 120;
        managedListView1.Columns.Add(column);
       // Add 10 items
       for (int i = 0; i < 10; i++)
       {
        ManagedListViewItem item = new ManagedListViewItem();
        // In details view mode, the item text will not get shown, the subitems do instead
        // so here we need to setup subitems.
        // We need to add subitem for Name column.
        ManagedListViewSubItem subitem = new ManagedListViewSubItem();
        // Use the name id to connect this subitem to the Name column.
        // By this, the subitem will be shown and listed under the 'Name' column
        subitem.ColumnID = "name";
        subitem.Text = "Item # " + (i + 1).ToString();
        subitem.DrawMode = ManagedListViewItemDrawMode.Text;
        item.SubItems.Add(subitem);
        // This subitem is for Size column
        subitem = new ManagedListViewSubItem();
        // by this, the subitem will be shown and listed under the 'Size' column
        subitem.ColumnID = "size";
        subitem.Text = "Item # " + (i + 1).ToString()+ " Size";
        subitem.DrawMode = ManagedListViewItemDrawMode.Text;
        item.SubItems.Add(subitem);
       // Now the item is ready to be added
       managedListView1.Items.Add(item);
      }
     }
  }
}

Now build and run the application, then it should look like this:

Well, in the demo project, you'll find all you need to use all the control features.

The demo project

The demo project explains every feature of the control, consisting of three pages:

  1. The details view mode demo: this is about adding columns and items in details view. The idea is to add columns to the MLV control, then assign an ID for each column, after that when adding items, we add subitems to each item. The subitem index within an item is not required or has no effect; the only matter is the column ID. So when a subitem in any item at any index has column0 id, it will be listed for Column0. See the code of the demo project for more.
  2. The columns get drawn with a simple rule: first column is drawn first and last column drawn last. This means the columns are drawn as they are ordered in the Columns collection. When you drag a column to reorder it, the column position in the collection will get changed as the same as in the control.

  3. The second page is for advanced features of the details view mode:
  4. As you see in this picture, we add images, images with texts, texts with custom fonts and colors ... and more without any problem ! Also you can use the ImagesList property of the MLV control for images or using the UserDraw mode to draw them as you like. You can reorder columns, sort items via clicking on column, use mouse to change rating of any item. Please see the source for more details about using these features, all are explained at Form1.cs.

  5. The third page is for thumbnails view.

    These picture are dragged from "Pictures" folder from user Documents. As you can see, thumbnails sizes are calculated automatically, even you set the thumbnails size with a fixed value like (50x50), the images will drown in ratio stretch form in the rectangle 50x50. No flashing, no control hanging on zooming.

How this thing works

Let's talk about the code. As I said before, the control contains these parts:

  1. The ManagedListViewPanel: which is the heart of the control. Everything goes in this component.
  2. The scroll bars. We use them to scroll the view port.

In ManagedListViewPanel.cs, we have two collections, Columns and Items. Columns (ManagedListViewColumnsCollection.cs) which is a collection of ManagedListViewColumn.cs; the columns draw only in Details view mode. The Items collection is in ManagedListViewItemsCollection.cs which is collection of ManagedListViewItem.cs; the items include SubItems which is a List collection of ManagedListViewSubItem.cs. The items are drawn in Details view but it requires to fill subitems of each item otherwise it gets ignored. In Thumbnails view, items are drawn without subitems; in this case the subitems are ignored (not shown) and the image and text of the item itself are drawn.

But the question is: how does it draw?

To answer that, let's talk about the mechanism of MLV. MLV contains DrawSpace and ViewPort. The DrawSpace (which is invisible) is the space that can draw all the items (and columns in details view) at once. ViewSpace is the rectangle that the user can see the items through (and columns in details view).

The ViewPort position is determined via HscrollOffset and VscrollOffset. The size of DrawSpace is calculated (or can be calculated) depending on view mode, items count, font.. etc. HscrollOffset and VscrollOffset can be of ranged 0 to whatever you like, however we keep the maximum value so the ViewPort stays in the DrawSpace. The ViewPort is the ManagedListViewPanel itself!! The height is the height of that control, same for the width.

In draw operations, the control only think about what it can draw in the ViewPort. Using correct calculations depending on parameters (items count, item height, vertical scroll value 'VscrollOffset', horizontal scroll value 'HscrollOffset', ....etc.) we can figure out what items we should draw and the coordinates of each. This will make things faster, and I think it's better than creating elements that draw itself.

Like every custom control, we override the OnPaint method to draw:

protected override void OnPaint(PaintEventArgs pe)
{
    base.OnPaint(pe);
    if (viewMode == ManagedListViewViewMode.Details)
        DrawDetailsView(pe);
    else
        DrawThumbailsView(pe);

    //select rectangle
    if (DrawSelectRectangle)
        pe.Graphics.DrawRectangle(new Pen(new SolidBrush(Color.Gray)),
            SelectRectanglex, SelectRectangley, 
            SelectRectanglex1 - SelectRectanglex, SelectRectangley1 - SelectRectangley);
}

Now we use the DrawDetailsView method to draw the details view, and the DrawThumbailsView method for thumbnails. Let's take a look at the thumbnails view draw code:

private void DrawThumbnailsView(PaintEventArgs pe)
{
    // we need this to figure out the text size
    Size CharSize = TextRenderer.MeasureText("TEST", this.Font);
    itemTextHeight = CharSize.Height * 2;// 2 lines for text

    // calculate how many vertical lines of items we can draw
    int vLines = this.Height / (spaceBetweenItemsThunmbailsView + ThumbnailsHeight + itemTextHeight);
    // calculate how many horisontal (or rows) lines of items we can draw
    int hLines = this.Width / (spaceBetweenItemsThunmbailsView + ThumbnailsWidth);
    if (hLines == 0) hLines = 1;// if the items count == 0, it's impossible that we have no v line !
    // This will set us directly to the row that the ViewPort at 
    int passedRows = VscrollOffset / (spaceBetweenItemsThunmbailsView + ThumbnailsHeight + itemTextHeight);
    int itemIndex = passedRows * hLines;// This is the index of the first item on the top left corner.
    if (itemIndex >= items.Count)// if we make some mistakes in calculation, just back off.
        return;
    int y = 2;
    for (int i = 0; i < vLines + 2; i++)
    // loop through the vertical lines. We add 2 for scrolling (so a part of the last row items get draw too).
    {
        int x = spaceBetweenItemsThunmbailsView;
        for (int j = 0; j < hLines; j++)
        {
            int offset = VscrollOffset % (spaceBetweenItemsThunmbailsView + ThumbnailsHeight + itemTextHeight);
            if (highlightItemAsOver)
            {
                if (itemIndex == overItemSelectedIndex)
                {
                    pe.Graphics.FillRectangle(Brushes.LightGray,
                        new Rectangle(x - 2, y - offset - 2, ThumbnailsWidth + 4, ThumbnailsHeight + itemTextHeight + 4));
                }
            }
                 if (items[itemIndex].Selected)
                pe.Graphics.FillRectangle(Brushes.LightSkyBlue,
                    new Rectangle(x - 2, y - offset - 2, ThumbnailsWidth + 4, ThumbnailsHeight + itemTextHeight + 4));

            StringFormat format = new StringFormat();
            format.Alignment = StringAlignment.Center;
            //format.LineAlignment = StringAlignment.Center;
            format.Trimming = StringTrimming.EllipsisCharacter;
            string textToDraw = "";
            Image imageToDraw = null;

            switch (items[itemIndex].DrawMode)
            {
                case ManagedListViewItemDrawMode.Text:
                case ManagedListViewItemDrawMode.Image:
                case ManagedListViewItemDrawMode.TextAndImage:
                    if (items[itemIndex].ImageIndex < ImagesList.Images.Count)
                    {
                        imageToDraw = ImagesList.Images[items[itemIndex].ImageIndex];
                    }
                    textToDraw = items[itemIndex].Text;
                    break;

                case ManagedListViewItemDrawMode.UserDraw:
                // in this case, the user choose what to draw, he/she need to give us the text and the image to draw
                    ManagedListViewItemDrawArgs args = new ManagedListViewItemDrawArgs(itemIndex);
                    if (DrawItem != null)
                        DrawItem(this, args);
                    imageToDraw = args.ImageToDraw;
                    textToDraw = args.TextToDraw;
                    break;
            }
            // Draw image
            if (imageToDraw != null)
            {
                // calculate item coordinates and size as ratio stretch
                Size siz = CalculateStretchImageValues(imageToDraw.Width, imageToDraw.Height);
                int imgX = x + (ThumbnailsWidth / 2) - (siz.Width / 2);
                int imgY = (y - offset) + (ThumbnailsHeight / 2) - (siz.Height / 2);
                pe.Graphics.DrawImage(imageToDraw, new Rectangle(imgX, imgY, siz.Width, siz.Height));
            }
            // Draw text
            pe.Graphics.DrawString(textToDraw, this.Font, Brushes.Black,
                 new Rectangle(x, y + ThumbnailsHeight + 1 - offset, ThumbnailsWidth, itemTextHeight), format);
            // advance
            x += ThumbnailsWidth + spaceBetweenItemsThunmbailsView;
            itemIndex++;
            if (itemIndex == items.Count)
                break;
        }
        y += ThumbnailsHeight + itemTextHeight + spaceBetweenItemsThunmbailsView;
        if (itemIndex == items.Count)
            break;
    }
}

With these few lines, we were able to draw thumbnails without problems using correct calculations. Details view works the same but we make different calculations since we have lines this time. The code is not presented here since its long. However you can check it out in the source if interesting.

The functions

Drawing is half of this control, the other half is the 'events'. So when the user moves the mouse, presses a key on the keyboard, or clicks on an item... we should take care of it all.

In any UserControl, you can implement them all using methods with 'On' keyword. Like OnMouseDown, OnMouseMove, ..etc. In MLV control, most things are done in OnMouseMove and OnMouseClick. So when the user moves the mouse over the control and the view mode is Details, we use calculations to figure out where the cursor is, what item it's over, if the left button of the mouse is pressed what should we do ...etc. Also the code will not be presented here because it's too long, you can check it out in the source zip if interested.

ManagedListView.cs members

These are the members that related to MLV in ManagedListView.cs:

Properties

  • ManagedListViewViewMode ViewMode: the view mode, Details or Thumbnails.
  • ManagedListViewColumnsCollection Columns: the columns collection that drown in Details view. Remember that the first column draw first and the last column draw last.
  • ManagedListViewItemsCollection Items: the items collection. You may need to add subitems for each item in order to use in Details view.
  • bool AllowItemsDragAndDrop: You should enable this property to allow the items to be dragged in the control. Drag and drop work only after implementing the ItemsDrag event which not rise until this property is true.
  • bool AllowColumnsReorder: to allow the user to reorder the columns in Details view.
  • bool ChangeColumnSortModeWhenClick: if enabled, the sort mode of the clicked column gets changed automatically.
  • int ThumbnailsHeight: the height of the thumbnail when draw in Thumbnails mode. However this value is the height of rectangle that present the thumbnail, the image will draw in that rectangle but in ratio stretch mode to keep the aspect ratio.
  • int ThumbnailsWidth: the width of the thumbnail when draw in Thumbnails mode. However this value is the width of rectangle that present the thumbnail, the image will draw in that rectangle but in ratio stretch mode to keep the aspect ratio.
  • int WheelScrollSpeed: how many pixels to pass when using the mouse wheel. This help when you have a large count of items so you may want to speed up the wheel scrolling.
  • bool DrawHighlight: if enabled, the item get highlighted with gray color each time the mouse get over it.
  • ImageList ImagesList: use it to draw item images in both view modes. Just like normal ListView, you assign an ImagesList object to it then each item provides the index of the image for it.
  • List<ManagedListViewItem> SelectedItems: get a list of currently selected items.
  • bool AllowDrop: overrides the AllowDrop property of UserControl. Get or set if this control can accept dropped data.
  • Font Font: overrides the Font property of the UserControl. the font of this control. This value is important because of the draw calculations depend on font size.

Events

  • DrawColumn: Raised when the control need to draw a column. The column information will be sent along with this event args.
  • DrawItem: Raised when the control need to draw an item. The item information will be sent along with this event args. Please note that this event raised only with Thumbnails View Mode.
  • DrawSubItem: Raised when the control need to draw a sub item. The sub item information will be sent along with this event args. Note: raised only if the sub item draw mode property equal UserDraw.
  • MouseOverSubItem: Raised when the mouse get over a sub item. Also useful information will be sent with this event args.
  • SelectedIndexChanged: raised when the user select/unselect items.
  • ColumnClicked: raised when the user click on column.
  • EnterPressed: raised when the user pressed the return key after selecting item. This event will relieve from implementing KeyDown event and checking what is the pressed key and if one item is selected.
  • ItemDoubleClick: raised when the user double click on item.
  • SwitchToColumnsContextMenu: raised when the control needs to switch to the columns context menu. So you can use 2 context menus for MLV control, one for columns and another for items. Using this event you can choose what one to use.
  • SwitchToNormalContextMenu: raised when the control needs to switch to the normal context menu.
  • AfterColumnResize: raised when the user finished resizing a column.
  • ItemsDrag: raised when the user dragged item(s). Note that this will not do drag and drop. This just give you information in the args to do drag and drop.
  • ViewModeChanged: raised when the user changed the view mode.

Methods

  • ManagedListViewItem GetItemAtCursorPoint(): Retrieve item at current cursor point.
  • ManagedListViewItem GetItemAtPoint(Point point): Retrieve item at point.
  • int GetItemIndexAtCursorPoint(): Retrieve item index at current cursor point.
  • int GetItemIndexAtPoint(Point point): Retrieve item index at point.
  • void ScrollToItem(int itemIndex): Scroll view port into item. This is very useful to jump directly to item.
  • void ScrollToItem(ManagedListViewItem item): Scroll view port into item.

Points of Interest

This control is a customizable list view control. You can draw columns as you like, items as you like without 'slow' problems. However that doesn't mean you get the fastest thing but let's say it's better than normal ListView. No need to install any external components in the target pc to get it working (except the .net framework of course). If you don't like the look of the columns for example go ahead and change the way it get drown, it's your choice Wink | ;)

Remember this is a .net library uses System.Windows.Forms namespace so it targets Windows platform only.

TODO:

  • Adding support for more view modes.
  • Find a way to add columns and item directly using Visual Studio forms designer.
  • Fixing bugs if found.

FAQ:

  • Q: I want to use this control in my project, can I?
  • A: Sure, since you do this under GNU. Also it will be appreciated to email me with a link to the project you use MLV with.
  • Q: I added columns and items but the items never show, why?
  • A: Remember to give columns ids (values you can easily remember), each item should include subitems. Each subitem must have id of the column that it should listed with.
  • Q: I want to view thumbnails without using ImagesList, how?
  • A: You should the DrawItem event. That event when raised it give us the argument we need like item index, what image you like to draw and the text you want to draw. Just set the text and image and you're ready to go. All calculation are done automatically.
  • Q: The scroll bars are invisible, why?
  • A: The scroll bars get hidden/shown automatically depending on items count and view mode.
  • Q: I want to use the panel directly, can I? How?
  • A: To do that you'll have to implement some events in the ManagedListViewPanel.cs, for example, you'll have to implement the 'RefreshScrollBars' event to calculate scroll values. All these done at ManagedListView.cs so it's recommended to use it instead.
  • Q: I added the rating subitem but can't find events for it in the MLV control. How I can update rating of clicked subitem?
  • A: Each ManagedListViewRatingSubItem.cs subitem has two events: RatingChanged and UpdateRatingRequest. You should implement them for each subitem.
  • Q: I want to create a customized subitem just like the rating one, can I and how?
  • A: Create a class then inherit it with ManagedListViewSubItem. Then you can override events like OnMouseClick, OnMouseOver .. and you can add additional code lines in the DrawDetailsView method in the MLV panel just like what done for rating subitem.
  • Q: When I click at column I want to sort items depending on that column. How?
  • A: You have to implement the ColumnClicked event, This event will provide you the clicked column id. Use the id to determine the subitems that need to use to sort the items for, Use the sort mode of that column to know what kind of sort it is (A to Z or Z to A). Also you'll need to create a Comparer for that. See the Demo project for a complete example about sorting items via columns.
  • Q: You were talking about the bad of ImagesList in normal ListView in this article then we see MLV supports the ImagesList. How's that?
  • A: That's a good question. The ImagesList in MLV should be used only when you have limited count of images (5 for example) with fixed size. So in this case, using ImagesList will be benefit to use because of:
    1. If you assign image for each item and items count is huge, a memory issue may occur.
    2. We don't have to assign the same image for more than one item. Just the image index.
    3. Using DrawItem event for limited count of images is not necessary. Using indexes is faster.

    So that's why I used ImagesList.

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)

Share

About the Author

Ala Hadid
Software Developer
Syrian Arab Republic Syrian Arab Republic
Start programming at 2008, my first successful project was AHD Subtitles Maker "ahd-subtitles-maker.webnode.com".
I'm studying Physics in Hims university.
Programming is my favorite hobby that fill my empty times.

Comments and Discussions

 
GeneralMy vote of 5 PinmemberAmmar J H2-Feb-13 5:44 
GeneralRe: My vote of 5 PinmemberAla Hadid2-Feb-13 10:14 

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

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.141216.1 | Last Updated 1 Feb 2013
Article Copyright 2013 by Ala Hadid
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid