ListView最后一列自动调整大小会创建滚动条。

4

我正在实现一个从ListView派生的自定义控件。

我希望最后一列填充剩余空间(这是一个非常常见的任务),我通过重写OnResize方法来实现:

     protected override void OnResize(EventArgs e)
    {
        base.OnResize(e);

        if (Columns.Count == 0)
            return;
        Columns[Columns.Count - 1].Width = -2; // -2 = Fill remaining space
    }

或通过其他方式:
        protected override void OnResize(EventArgs e)
    {

        base.OnResize(e);

        if (!_autoFillLastColumn)
            return;

        if (Columns.Count == 0)
            return;

        int TotalWidth = 0;
        int i = 0;
        for (; i < Columns.Count - 1; i++)
        {
            TotalWidth += Columns[i].Width;
        }

        Columns[i].Width = this.DisplayRectangle.Width - TotalWidth;
    }

编辑:

这个方法在将ListView嵌入父容器并通过该控件调整大小时,会出现问题。每隔一秒钟,控件尺寸就会缩小(例如拖动边框一个像素),底部会出现一个无法移动的滚动条。

结果是当我拖动父窗口的大小时,在ListView中会出现闪烁的滚动条,并且有50%的概率在停止拖动时仍然存在。

8个回答

5

ObjectListView(一个.NET WinForms ListView的开源封装)允许“将最后一列展开以填充可用空间”。实际上,这比它看起来要困难得多--不要相信任何告诉你相反的人 :)

如果您使用ObjectListView,则可以免费获得该问题的解决方案 - 以及其他几个问题。但是,如果您想自己完成所有工作,则需要:

  • 使用ClientSize而不是DisplayRectangle(如@zxpro已经提到的)
  • 使用Layout事件而不是Resize事件。
  • 监听ColumnResized事件并执行相同的操作
  • 不要在设计模式下进行自动调整大小--这会非常令人困惑。

最棘手的问题只出现在窗口缩小时--水平滚动条会闪烁并且有时在缩小完成后仍然存在。这似乎正是您的情况。

解决方案是拦截WM_WINDOWPOSCHANGING消息并将ListView调整为新大小。

protected override void WndProc(ref Message m) {
    switch (m.Msg) {
        case 0x46: // WM_WINDOWPOSCHANGING
            this.HandleWindowPosChanging(ref m);
            base.WndProc(ref m);
            break;
        default:
            base.WndProc(ref m);
            break;
    }
}

protected virtual void HandleWindowPosChanging(ref Message m) {
    const int SWP_NOSIZE = 1;

    NativeMethods.WINDOWPOS pos = (NativeMethods.WINDOWPOS)m.GetLParam(typeof(NativeMethods.WINDOWPOS));
    if ((pos.flags & SWP_NOSIZE) == 0) {
        if (pos.cx < this.Bounds.Width) // only when shrinking
            // pos.cx is the window width, not the client area width, so we have to subtract the border widths
            this.ResizeFreeSpaceFillingColumns(pos.cx - (this.Bounds.Width - this.ClientSize.Width));
    }
}

1

尝试使用ClientSize属性。正如MSDN所说:

控件的客户区域是控件的边界,减去非客户元素,例如滚动条、边框、标题栏和菜单。

编辑:我已经添加了一个代码示例,演示如何实现不同的填充行为。

public class MyListView : ListView
{
    public MyListView()
    {
        View = View.Details;
        Columns.Add(Column1);
        Columns.Add(Column2);
        Columns.Add(Column3);
    }

    private readonly ColumnHeader Column1 = new ColumnHeader();
    private readonly ColumnHeader Column2 = new ColumnHeader();
    private readonly ColumnHeader Column3 = new ColumnHeader();

    protected override void OnResize(EventArgs e)
    {
        base.OnResize(e);

        // Constant width
        Column1.Width = 200;
        // 50% of the ListView's width
        Column2.Width = (ClientSize.Width/2);
        // Fill the remaining width, but no less than 100 px
        Column3.Width = Math.Max(100, ClientSize.Width - (Column1.Width + Column2.Width));
    }
}

据我所知,可以使用ClientSize与OnResize结合使用来实现列的正确自适应大小。 - Bryan Menard
我已经添加了一个示例来进行演示。 - Bryan Menard
谢谢 :) 不幸的是,这并没有解决我的问题。在尝试了一段时间后,我发现当手动调整大小时(至少在设计器中),一切都正常。但是当ListView被停靠/锚定在多个侧面,并且我调整父控件的大小时,就会出现问题。 - user164771

1

这段代码对我来说非常有效:


const int SWP_NOSIZE = 1;
[StructLayout(LayoutKind.Sequential)]
public struct WINDOWPOS
{
    public IntPtr hwnd;
    public IntPtr hwndInsertAfter;
    public int x;
    public int y;
    public int cx;
    public int cy;
    public int flags;
}
protected override void WndProc(ref Message m)
{
    switch (m.Msg)
    {
        case 0x46: // WM_WINDOWPOSCHANGING
            this.HandleWindowPosChanging(ref m);
            base.WndProc(ref m);
            break;
        default:
            base.WndProc(ref m);
            break;
    }
}
protected virtual void HandleWindowPosChanging(ref Message m)
{
    try
    {
        WINDOWPOS pos = new WINDOWPOS();
        pos = (WINDOWPOS)System.Runtime.InteropServices.Marshal.PtrToStructure(m.LParam, pos.GetType());
        if (m_EnableAutoColumnResize && this.Columns.Count > 0 && (pos.flags & SWP_NOSIZE) == 0)
        {
            this.ResizeFreeSpaceFillingColumns(pos.cx - (this.Bounds.Width - this.ClientSize.Width));
        }
    }
    catch (ArgumentException) { }
}

private void ResizeFreeSpaceFillingColumns(int listViewWitdh)
{
    int lastColumn = this.Columns.Count - 1;
    int sumWidth = 0;

    for (int i = 0; i < lastColumn; i++)
    {
        sumWidth += this.Columns[i].Width;
    }
    this.Columns[lastColumn].Width = listViewWitdh - sumWidth;
}

敬礼
Lary


1

您需要重写受保护的方法SetBoundsCore(),并在委托给base.SetBoundsCore()之前或之后执行列调整大小,具体取决于ListView宽度是缩小还是扩大。

以下示例针对单列ListView进行了硬编码:

protected override void SetBoundsCore( int x, int y, int width, int height, BoundsSpecified specified )
{
    ResizeColumns( width, true );
    base.SetBoundsCore( x, y, width, height, specified );
    ResizeColumns( width, false );
}

private void ResizeColumns( int controlWidth, bool ifShrinking )
{
    if( Columns.Count < 1 || Parent == null )
        return;

    int borderGap = Width - ClientSize.Width;
    int desiredWidth = controlWidth - borderGap;

    if( (desiredWidth < Columns[0].Width) == ifShrinking )
        Columns[0].Width = desiredWidth;
}

请注意他的代码没有处理 specified 参数,这个我就交给您了!


1

可能已经有点晚了,但我认为最简单的解决方案是添加一个函数来处理窗口调整大小事件。 我将我的listview锚定在右侧,所以当我调整窗口大小时,listview也会调整大小。 在处理窗口调整大小的函数中,我设置myListView.Columns[lastColumnIndex].Width = -2;

这显然重新绘制了listview,将最后一列扩展到控件的边缘。 希望这可以帮助到您。


0

我知道这是一个老问题,但在阅读了这篇文章并找到了自己的答案后,我想与其他人分享。

对我来说(在基本测试下保持稳定),解决方法是将 Docked 设置为 None,然后将 ListView 锚定到父级的左上角。然后,在父级的 OnClientSizeChanged 中,我设置了我的 ListView 的大小以填充它,然后设置了我的列宽。由于某种原因,可能是时间问题,这解决了原始问题中提到的滚动条问题。

    private void ContentSplit_Panel1_ClientSizeChanged(object sender, EventArgs e)
    {
        CaseFilter.Bounds = ContentSplit.Panel1.ClientRectangle;
        if(CaseFilter.Columns.Count == 1)
            CaseFilter.Columns[0].Width = CaseFilter.ClientRectangle.Width;
    }

CaseFilter是我的ListView,ContentSplit.Panel1是一个SplitContainer控件,它在里面。当我将其设置为Dock=Fill时,我遇到了滚动条问题,但通过以上更改,问题得以解决。

0
当一个包含ListView的窗体从最大化状态恢复到FormWindowState.Normal时,即使列宽已经正确调整,水平滚动条也会显示出来。当调整后的列宽缩小时会发生这种情况,但是当列宽增加时不会发生。下面是一个可以解决这个问题而不需要使用win32调用的技巧。(魔术数字[-2]只是一个简单的快捷方式)
        // Member variable.
        private ListView myListView;

        // Method to resize last column.
        private void ResizeColumn(int width) {
              myListView.Columns[myListView.Columns.Count - 1].Width = width;
        }                  

        // ListView ClientSizeChanged event handler.
        private void Process_ListViewClientSizeChanged(object sender, EventArgs e)
        {
              // Get the width to resize the last column.
              int width = myListView.ClientSize.Width;
              for (int i = 0; i < myListView.Columns.Count - 1; i++)
                    width -= myListView.Columns[i].Width;

              // Last column width is growing. Use magic number to resize.
              if (width >= myListView.Columns[myListView.Columns.Count - 1].Width)
                    myListView.Columns[myListView.Columns.Count - 1].Width = -2;

              // Last column width is shrinking. 
              // Asynchronously invoke a method to resize the last column.
              else
                    myListView.BeginInvoke
                          ((MethodInvoker)delegate { ResizeColumn(width); });
        }

0

在你做出修改后,尝试调用base.OnResize()


那是我尝试过的第一件事情之一,可惜没有任何区别。 - user164771

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