C# Change ListView Item's/Row's height

2019-01-23 04:17发布

问题:

I want to change the Item's/Row's height in listview.

I searched every where and I figured that in order to change the height I need to use LBS_OWNERDRAWFIXED or MeasureItem or something like that.

The problem is that I dont know exactly what to do and how to use it..
Can anyone help me with it?

Edit:
I cant use the ImageList hack because I am using the SmallImageList for real and I need different line height from the ImageList images size.

Thanks!

回答1:

It can be done using the SmallImageList trick -- you just have to be careful. ObjectListView -- an open source wrapper around a standard .NET ListView -- uses that trick to successfully implement a RowHeight property.

If you want 32 pixels for each row, allocate an ImageList that is 16x32 (width x height), and then position each of your images in the vertical middle of the 32-pixel height.

This screen shot shows 32-pixel rows and the word wrapping that is possible because of the extra space:

ObjectListView does all this work for you. In fact, if you are trying to do anything with a ListView, you should seriously looked at using an ObjectListView instead. It makes many difficult things (e.g. sorting by column type, custom tooltips) trivial, and several impossible things (e.g. overlays, groups on virtual lists) possible.



回答2:

For the people that are still struggling with this, here is the code I use:

private void SetHeight(ListView listView, int height)
{
    ImageList imgList = new ImageList();
    imgList.ImageSize = new Size(1, height);
    listView.SmallImageList = imgList;
}

To use this, just do:

SetHeight(lvConnections, 25);


回答3:

You have to use a bit of a hack. The trick is to use an image list in the StateImageList property. The ListView will adjust its item height, based on the height of the ImageList's ImageSize property. You do not have to specify an image for your items, but just using the StateImageList will force the ListView to adjust. In the example below, I had set the image list size to 32x32, thus resulting in a 32px height ListViewItem(s).



回答4:

Sadly nobody answered your original question how to use LBS_OWNERDRAWFIXED in all these years.

The answer that you have accepted is integrating a huge project (with demos and documentation 3,3MB). But just for setting the line height of a ListView this is overbloated.

The other workaround suggested here (adding an ImageList) works only to increase the row height. But it does not allow to really set the RowHeight independent of the image height. Additionally the default row height depends on the operating system. For example on Windows 7 the rows are much higher than on XP. You cannot chose to make them tighter, only higher.

But with very few lines you can do what you want. Just copy and paste the following class:

using System;
using System.Drawing;
using System.Diagnostics;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace ExtendedControls
{

public class ListViewEx : ListView
{
    #region Windows API

    /*
    struct MEASUREITEMSTRUCT 
    {
        public int    CtlType;     // Offset = 0
        public int    CtlID;       // Offset = 1
        public int    itemID;      // Offset = 2
        public int    itemWidth;   // Offset = 3
        public int    itemHeight;  // Offset = 4
        public IntPtr itemData;
    }
    */

    [StructLayout(LayoutKind.Sequential)]
    struct DRAWITEMSTRUCT
    {
        public int    ctlType;
        public int    ctlID;
        public int    itemID;
        public int    itemAction;
        public int    itemState;
        public IntPtr hWndItem;
        public IntPtr hDC;
        public int    rcLeft;
        public int    rcTop;
        public int    rcRight;
        public int    rcBottom;
        public IntPtr itemData;
    }

    // LVS_OWNERDRAWFIXED: The owner window can paint ListView items in report view. 
    // The ListView control sends a WM_DRAWITEM message to paint each item. It does not send separate messages for each subitem. 
    const int LVS_OWNERDRAWFIXED = 0x0400;
    const int WM_SHOWWINDOW      = 0x0018;
    const int WM_DRAWITEM        = 0x002B;
    const int WM_MEASUREITEM     = 0x002C;
    const int WM_REFLECT         = 0x2000;

    #endregion

    bool mb_Measured = false;
    int  ms32_RowHeight = 14;

    /// <summary>
    /// Constructor
    /// </summary>
    public ListViewEx()
    {
        SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.AllPaintingInWmPaint, true);
    }

    /// <summary>
    /// Sets the row height in Details view
    /// This property appears in the Visual Studio Form Designer
    /// </summary>
    [Category("Appearance")]  
    [Description("Sets the height of the ListView rows in Details view in pixels.")] 
    public int RowHeight
    {
        get { return ms32_RowHeight; }
        set 
        { 
            if (!DesignMode) Debug.Assert(mb_Measured == false, "RowHeight must be set before ListViewEx is created.");
            ms32_RowHeight = value; 
        }
    }

    protected override CreateParams CreateParams
    {
        get
        {
            CreateParams k_Params = base.CreateParams;
            k_Params.Style |= LVS_OWNERDRAWFIXED;
            return k_Params;
        }
    }

    /// <summary>
    /// The messages WM_MEASUREITEM and WM_DRAWITEM are sent to the parent control rather than to the ListView itself.
    /// They come here as WM_REFLECT + WM_MEASUREITEM and WM_REFLECT + WM_DRAWITEM
    /// They are sent from Control.WmOwnerDraw() --> Control.ReflectMessageInternal()
    /// </summary>
    protected override void WndProc(ref Message k_Msg)
    {
        base.WndProc(ref k_Msg); // FIRST

        switch (k_Msg.Msg)
        {
            case WM_SHOWWINDOW: // called when the ListView becomes visible
            {
                Debug.Assert(View == View.Details, "ListViewEx supports only Details view");
                Debug.Assert(OwnerDraw == false,   "In ListViewEx do not set OwnerDraw = true");
                break;
            }
            case WM_REFLECT + WM_MEASUREITEM: // called once when the ListView is created, but only in Details view
            {
                mb_Measured = true;

                // Overwrite itemHeight, which is the fifth integer in MEASUREITEMSTRUCT 
                Marshal.WriteInt32(k_Msg.LParam + 4 * sizeof(int), ms32_RowHeight);
                k_Msg.Result = (IntPtr)1;
                break;
            }
            case WM_REFLECT + WM_DRAWITEM: // called for each ListViewItem to be drawn
            {
                DRAWITEMSTRUCT k_Draw = (DRAWITEMSTRUCT) k_Msg.GetLParam(typeof(DRAWITEMSTRUCT));
                using (Graphics i_Graph = Graphics.FromHdc(k_Draw.hDC))
                {
                    ListViewItem i_Item = Items[k_Draw.itemID];

                    Color c_BackColor = i_Item.BackColor;
                    if (i_Item.Selected) c_BackColor = SystemColors.Highlight;
                    if (!Enabled)        c_BackColor = SystemColors.Control;

                    using (SolidBrush i_BackBrush = new SolidBrush(c_BackColor))
                    {
                        // Erase the background of the entire row
                        i_Graph.FillRectangle(i_BackBrush, i_Item.Bounds);
                    }

                    for (int S=0; S<i_Item.SubItems.Count; S++)
                    {
                        ListViewItem.ListViewSubItem i_SubItem = i_Item.SubItems[S];

                        // i_Item.SubItems[0].Bounds contains the entire row, rather than the first column only.
                        Rectangle k_Bounds = (S>0) ? i_SubItem.Bounds : i_Item.GetBounds(ItemBoundsPortion.Label);

                        // You can use i_Item.ForeColor instead of i_SubItem.ForeColor to get the same behaviour as without OwnerDraw
                        Color c_ForeColor = i_SubItem.ForeColor;
                        if (i_Item.Selected) c_ForeColor = SystemColors.HighlightText;
                        if (!Enabled)        c_ForeColor = SystemColors.ControlText;

                        TextFormatFlags e_Flags = TextFormatFlags.NoPrefix | TextFormatFlags.EndEllipsis | TextFormatFlags.VerticalCenter | TextFormatFlags.SingleLine;
                        switch (Columns[S].TextAlign)
                        {
                            case HorizontalAlignment.Center: e_Flags |= TextFormatFlags.HorizontalCenter; break;
                            case HorizontalAlignment.Right:  e_Flags |= TextFormatFlags.Right; break;
                        }

                        TextRenderer.DrawText(i_Graph, i_SubItem.Text, i_SubItem.Font, k_Bounds, c_ForeColor, e_Flags);
                    }
                }
                break;
            }
        }
    }
} // class
} // namespace

After adding a ListViewEx to your Form you will see a new property in the Visual Studio Forms Designer which allows to set the row height in pixels:

The value you enter there will be the row height in pixels and it will be respected exatctly on all operating systems. I tested it on Windows XP, 7 and 10:

Additionally my class has two more advantages over the original ListView: It draws flicker-free and it respects the ForeColor and Font set in ListViewSubItem which is ignored by the original Microsoft ListView. So you can draw each cell with a different color and font.

IMPORTANT: As the MSDN says LBS_OWNERDRAWFIXED has been designed only for Details view (Report view). My code works only for this mode and this is because Microsoft has designed it like that.

Additionally please note that setting ListView.OwnerDraw = true is a completely different thing than using LVS_OWNERDRAWFIXED.

I did not implement drawing icons, because I don't need that. But you can easily add this.



回答5:

The default line height of a ListView (in report view mode) is computed based on the control's font size.

So to select the line height, choose a font with the right height in the ListView properties. For example, select MS Sans Serif 18.

Then you can change the font used by all items: when you insert a new item, set its font property.

To optimize font assignment you should declare the item font as a private member of the form:

Private Font stdfont = new Font( "Consolas", 9.0f, FontStyle.Regular );

Then when adding items :

ListViewItem i = new ListViewItem( "some text" );
i.Font = stdfont;
MyListView.Items.Add( i );

This trick is the only easy one allowing to have SMALLER line height ;) i.E. set control's font size to 7 and set items' font size to 10. (Tested with VS 2008 )



回答6:

Plasmabubble has the right idea. This expands on that and is what I use to use a narrow line-width for the items.

The linespacing in a ListView is dependent on the ListView's font and can't be changed. However, you can set the font for the items in the ListView to something larger than the ListView's font.

If you want it to be proportional, create a font based on the item's font. I want the item height to be 90% of normal, whatever the font chosen.

When I populate the list I used a font stored in settings but you could also use a literal font like "Consolas".

lvResults.Font = 
   new Font(Properties.Settings.Default.usrHookFont.FontFamily, 
      (float)(Properties.Settings.Default.usrHookFont.Size * .9));

foreach (HookSet item in resultSet)
   {
      ListViewItem lvi = new ListViewItem();
      lvi.Font = Properties.Settings.Default.usrHookFont;
      <dot><dot><dot>
}