C#如何改变ListView中项/行的高度

29

我想要改变ListView中每个选项的行高度。

我已经搜索了很多地方,发现要改变高度,需要使用LBS_OWNERDRAWFIXEDMeasureItem之类的方法。

问题是我不知道具体该怎么做和如何使用它。
有人能帮帮我吗?

编辑:
我不能使用ImageList技巧,因为我实际上正在使用SmallImageList,并且我需要与ImageList图像大小不同的行高。

谢谢!


请参考 https://dev59.com/6HM_5IYBdhLWcg3ww18Q。这是一个简单的解决方案。 - user3704023
1
提到的“简单解决方案”针对的是WPF——确实很简单。而这个问题涉及WinForms,情况并不简单。 - Grammarian
使用TreeView模拟ListView的行为。TreeView具有ItemHeight属性。 - 23W
7个回答

37

对于仍然苦恼的人,这是我使用的代码:

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

要使用它,只需执行以下操作:

SetHeight(lvConnections, 25);

2
我在详细模式下进行了测试。这个技巧只能增加行高度而不能使行比Windows定义的更紧密。请注意,列表视图行的高度取决于操作系统。例如,在Windows 7上,行比XP高得多。不幸的是,这个技巧对我没有用。 - Elmue

16
您需要使用一个小技巧。诀窍是在StateImageList属性中使用一个图像列表。ListView将根据ImageList的ImageSize属性调整其项高度。您不必为项目指定图像,只需使用StateImageList即可强制ListView进行调整。在下面的示例中,我将图像列表大小设置为32x32,因此ListView将生成32像素高的ListViewItem(s)。

输入图像描述


1
我忘了说,我正在使用SmallImageList属性中的ImageList,所以我不能使用那个hack。 - Ron
那么你将不得不编写自己的控件,因为根据经验和研究,使用现有的WinForms ListView 是不可能实现的。这也是微软社区支持开发人员对一些人进行研究后得出的相同答复,并建议编写自定义控件。 - David Anderson
我在详细模式下进行了测试。这个技巧只适用于增加行高度,而不能使行比Windows定义的更紧凑。请注意,列表视图行的高度取决于操作系统。例如,在Windows 7上,行的高度比XP高得多。遗憾的是,这个技巧对我没有用处。 - Elmue

15

可以使用 SmallImageList 技巧来实现 - 你只需要小心一些。 ObjectListView -- 一个基于标准 .NET ListView 的开源封装 -- 使用此技巧成功实现了 RowHeight 属性。

如果你希望每行有32像素,就要分配一个16x32(宽x高)的 ImageList,然后将每个图像放置在32像素高度的垂直中间。

以下屏幕截图显示了32像素行和由于额外的空间而可能的换行:

enter image description here

ObjectListView 为您完成所有这些工作。事实上,如果您正在尝试对ListView进行任何操作,应该严肃考虑使用 ObjectListView。 它使许多困难的事情(例如按列类型排序,自定义工具提示)变得微不足道,并使一些不可能的事情(例如覆盖,虚拟列表上的分组)成为可能。


2
好的,我只是想调整行大小,为此需要300kb的程序太大了。无论如何,总的来说那是一个好的解决方案。 - Ron
2
我认为你提出的解决方案太过复杂。 - John Nguyen

10
很遗憾,多年来没有人回答您如何使用LBS_OWNERDRAWFIXED的问题。
您接受的答案集成了一个庞大的项目(带有演示和文档3.3MB)。但仅仅为了设置ListView的行高,这是过度臃肿的。
这里提出的另一种解决方法(添加ImageList)只能增加行高。但它不能真正地独立于图像高度设置行高。此外,默认行高取决于操作系统。例如,在Windows 7上,行比在XP上高得多。您不能选择使它们更紧凑,只能更高。
但是,只需要极少的代码就可以实现您想要的效果。 只需复制并粘贴以下类:
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

在向窗体添加ListViewEx后,您将在Visual Studio Forms Designer中看到一个新属性,允许以像素设置行高:

Setting RowHeight in a C# ListView

您在此处输入的值将是行高(以像素为单位),并且在所有操作系统上都会完全保留。我已在Windows XP、7和10上进行了测试:

ListViewEx.RowHeight sample

此外,我的类比原始的ListView有两个更大的优势:它可以无闪烁地绘制,并且尊重在ListViewSubItem中设置的ForeColor和Font,而这在原始的Microsoft ListView中被忽略。因此,您可以使用不同的颜色和字体绘制每个单元格。
重要提示:正如MSDN所说,LBS_OWNERDRAWFIXED仅为详细视图(报表视图)设计。我的代码仅适用于此模式,这是因为Microsoft已经这样设计了。
此外,请注意,将ListView.OwnerDraw设置为true与使用LVS_OWNERDRAWFIXED完全不同。
我没有实现绘制图标,因为我不需要它。但您可以轻松添加它。

CreateParams重写导致子项文本无法显示... - user1932634
我完全不知道你在说什么。我在我的应用程序中使用这段代码。它完美地工作!在上面的截图中,您可以看到所有子项都被正确地绘制,甚至是彩色的。 - Elmue
这段代码没有考虑listviewitem.UseItemStyleForSubItems属性,因此不会绘制。如果将该属性设置为false,则需要绘制子项的背景颜色。请建议如何实现相同的效果。 - JDoshi
2
我的代码示例的目的是回答问题,而我的代码确实做到了。如果您需要更多功能,很容易根据您的需求更改代码。 - Elmue
1
@Alexis Martial:只需查看我回答中的2个屏幕截图!第一个显示Visual Studio已经有了一个名为“GridLines”的选项,我已将其设置为True。第二个屏幕截图显示网格线已绘制。原因是Microsoft已经完成了这项工作。我的代码绘制单元格的内容,Microsoft绘制控件的网格线、标题和边框。 - Elmue
显示剩余3条评论

3
ListView(在报表视图模式下)的默认行高是根据控件的字体大小计算的。
因此,要选择行高,请在ListView属性中选择具有正确高度的字体。 例如,选择MS Sans Serif 18。
然后,您可以更改所有项目使用的字体: 在插入新项目时,设置其字体属性。
为了优化字体分配,您应该将项目字体声明为窗体的私有成员。
Private Font stdfont = new Font( "Consolas", 9.0f, FontStyle.Regular );

接着当添加项目时:

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

这个技巧是唯一一个可以让行高更小的简单方法;例如将控件的字体大小设置为7,将项目的字体大小设置为10。(在VS 2008中测试通过)

这样做的一个缺点是工具提示具有较大的字体,看起来可能有些疯狂。 - Drew Noakes
这并没有回答问题。将字体变小并不会改变行的高度。 - Elmue

1

Plasmabubble有正确的想法。这个扩展了它,是我用来使用窄线宽的项目。

ListView中的行间距取决于ListView的字体,无法更改。但是,您可以将ListView中的项目字体设置为大于ListView的字体。

如果您希望其成比例,请基于项目字体创建字体。 我希望项目高度为正常值的90%,无论选择哪种字体。

当我填充列表时,我使用存储在设置中的字体,但也可以使用像“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>
}

这并没有回答问题。将字体变小并不会改变行高。 - Elmue

0

阅读了这么多年的答案后,有两种方法可以使用基于ListView的扩展控件,另一种是使用字体或图标进行扩展。 如果您的项目已经使用了ListView - 就像我的情况一样,您需要在保持原始图标和字体大小的同时扩展列高度 - 我建议 您可以大致计算所需的列高度和正常图标大小的比例,因此使用透明边框来扩展图标大小,例如,如果24x24图标实际上高度为35,则可以使用Windows Paint 3D将图标扩展到35x35,使用画布保持图标的原始比例,我认为这可能是最省时和成本效益的方法。


1
你的回答可以通过提供更多支持信息来改进。请编辑以添加进一步的细节,例如引用或文档,以便他人可以确认你的答案是正确的。您可以在帮助中心中找到有关如何编写良好答案的更多信息。 - Community

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接