ListView Final Column Autosize creates scrollbar

2020-03-24 05:11发布

I am implementing a custom control which derives from ListView.

I would like for the final column to fill the remaining space (Quite a common task), I have gone about this via overriding the OnResize method:

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

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

or via another method:

        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;
    }

Edit:

This works fine until I dock the ListView into a parent container and resize via that control. Every second time the control size shrinks (IE, drag the the border one pixel), I get a scroll bar on the bottom which can't move at all (not even one pixel).

The result of which is when I drag the size of the parent I am left with a flickering scroll bar in the ListView, and a 50% chance it will be there when the dragging stops.

8条回答
倾城 Initia
2楼-- · 2020-03-24 05:38

You need to override the protected method SetBoundsCore() and perform the column resizing either before or after delegating to base.SetBoundsCore(), depending on whether the ListView width is narrowing or widening.

The following example is hardcoded for a single column 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;
}

Note his code doesn't deal with the specified parameter, i'll leave that up to you!

查看更多
萌系小妹纸
3楼-- · 2020-03-24 05:42

This may be pretty late, but i believe the easiest solution is to add a function to handle the window resize event. I have my listview anchored to the right, so when i resize my window, the listview also resizes. in the function that handles the window resize, i set the myListView.Columns[lastColumnIndex].Width = -2;

This apparently redraws the listview, extending the last column to the edge of the control. Hope this helps.

查看更多
爷的心禁止访问
4楼-- · 2020-03-24 05:44

I know this is old question, but after reading this and then finding my own answer, I thought I would share for others reading this as well.

The fix for me (so far holding up under basic testing) was to set Docked to None and just Anchor my ListView to the top-left of the parent. Then, in the OnClientSize changed of the parent I set the size of my ListView to fill it, and then set my column width. For whatever reason, probably timing, this fixed the scrollbar issue mentioned by the original question.

    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 is my ListView and ContentSplit.Panel1 is a SplitContainer control it is inside. When I had it set to Dock=Fill, I had the scrollbar issue, with the above changes, no problems.

查看更多
迷人小祖宗
5楼-- · 2020-03-24 05:46

Try calling base.OnResize() after you made your change.

查看更多
叼着烟拽天下
6楼-- · 2020-03-24 05:47

ObjectListView (an open source wrapper around .NET WinForms ListView) allows this "expand-last-column-to-fill-available-space". It's actually more difficult to get right than it first appears -- do not believe anyone who tells you otherwise :)

If you use ObjectListView, you get the solution to that problem -- and several others -- for free. But if you want to do all the work yourself, you'll need to:

  • use ClientSize instead of DisplayRectangle (as @zxpro has already said)
  • use Layout event rather than Resize event.
  • listen for ColumnResized event and do the same work
  • not do the auto resizing in design mode -- it gets very confusing.

The trickiest problem only appears when the window shrinks -- the horizontal scroll bar flickers annoyingly and sometimes remains after the shrinking is finished. Which appears to be exactly what is happening in your case.

The solution is to intercept the WM_WINDOWPOSCHANGING message and resize the ListView to what the new size is going to be.

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));
    }
}
查看更多
【Aperson】
7楼-- · 2020-03-24 05:49

This code works very well for me:


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;
}

Regards Lary

查看更多
登录 后发表回答