Jonny
Jonny

Reputation: 83

Auto-Stretch ListView rows By Column Width

I have a listview with some rows, and all have a progressBar.

Adjusting column width works fine (as by design), however as it easily moves text within rows, it does NOT move the progressbar, resize it or even move it around.. without writing this:

private void fileList_ColumnWidthChanged(object sender, ColumnWidthChangedEventArgs e)
        {
            foreach (CustomProgressBar p in fileList.Controls.OfType<CustomProgressBar>().ToList())
            {
                if (e.ColumnIndex == 0)
                {
                    p.Width = fileList.Columns[3].Width;
                    p.Left = fileList.Columns[e.ColumnIndex].Width + 400;
                }
                else if (e.ColumnIndex == 1)
                {
                    p.Width = fileList.Columns[3].Width;
                    p.Left = fileList.Columns[e.ColumnIndex].Width + 227;
                }
            }
        }

I couldn't figure it out, so that was my only solution.. which "partially works".

Initial - no resize yet enter image description here

Column 1 resized enter image description here

General column resizing (no particular order) enter image description here

Is there something I am missing about automatically aligning items correctly according to the column position or width?

The code I wrote has zero effect after the 2nd column, as I can not confirm if it is working correctly for column1/column2 to begin with. So I dont see a point implementing the rest of it, if its set to fail anyway.

Any help is appreciated.

.NET Framework 4.8 / WinForms

--

NOTE: ProgressBar responds to all typical properties as would a regular progressBar. The difference is just the overlay shade and color. Nothing more.

Upvotes: 2

Views: 416

Answers (2)

Reza Aghaei
Reza Aghaei

Reputation: 125197

Relying on an owner-drawn ListView which I shared in my other answer is a better idea; however, just for the users who for any reason cannot change their solution, here is a solution based on adding ProgressBar controls to the Listview and adjusting control position based on sub-item position and size.

enter image description here

Example

Highlights of the code:

Here is the code:

using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
public class ListViewProgressBarExample : Form
{
    private MyListView listView1;
    private void InitializeComponent()
    {
        listView1 = new MyListView();
        listView1.Columns.Add("#", 50);
        listView1.Columns.Add("File name", 150);
        listView1.Columns.Add("Progress", 200);
        listView1.View = View.Details;
        listView1.Dock = DockStyle.Fill;
        listView1.FullRowSelect = true;
        Controls.Add(listView1);
        Text = "ListView ProgressBar Example";
        Size = new Size(500, 300);
    }
    public ListViewProgressBarExample()
    {
        InitializeComponent();
        listView1.Items.Add(new ListViewItem(
            new[] { "1", "lorem ipsum.txt", "10" }));
        listView1.Items.Add(new ListViewItem(
            new[] { "2", "dolor sit.txt", "0" }));
        listView1.Items.Add(new ListViewItem(
            new[] { "3", "amet.txt", "30" }));
        listView1.Items.Add(new ListViewItem(
            new[] { "4", "consectetur adipiscing.txt", "70" }));
        listView1.Items.Add(new ListViewItem(
            new[] { "5", "celit.txt", "50" }));
        listView1.Items.Add(new ListViewItem(
            new[] { "6", "adipiscing elit.txt", "100" }));
        foreach (ListViewItem item in listView1.Items)
        {
            var p = new ProgressBar();
            p.Value = int.Parse(item.SubItems[2].Text);
            item.SubItems[2].Tag = p;
            if (p != null)
            {
                p.Bounds = item.SubItems[2].Bounds;
                listView1.Controls.Add(p);
                p.SendToBack();
            }
        }
        listView1.ColumnWidthChanged += (sender, args) => AdjustControls();
        listView1.ColumnReordered += (sender, args) => AdjustControls();
        listView1.ColumnWidthChanging += (sender, args) => AdjustControls();
        listView1.Scrolled += (sender, args) => AdjustControls();
        listView1.SizeChanged += (sender, args) => AdjustControls();
    }
    private void AdjustControls()
    {
        foreach (ListViewItem item in listView1.Items)
        {
            foreach (ListViewItem.ListViewSubItem subItem in item.SubItems)
            {
                if (subItem.Tag is Control)
                {
                    var bound = subItem.Bounds;
                    bound.Offset(listView1.AutoScrollOffset);
                    var c = (Control)subItem.Tag;
                    c.Bounds = subItem.Bounds;
                    c.Visible = bound.Top >= listView1.GetHeaderHeight();
                }
            }
        }
    }
}
public class MyListView : ListView
{
    private const int WM_HSCROLL = 0x114;
    private const int WM_VSCROLL = 0x115;
    private const int WM_MOUSEWHEEL = 0x020A;
    public event EventHandler Scrolled;
    private const int LVM_GETHEADER = 0x1000 + 31;
    [StructLayout(LayoutKind.Sequential)]
    private struct RECT
    {
        public int Left;
        public int Top;
        public int Right;
        public int Bottom;
    }
    [DllImport("user32.dll")]
    private static extern IntPtr SendMessage(
        IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);

    [DllImport("user32.dll")]
    private static extern bool GetWindowRect(
        IntPtr hwnd, out RECT lpRect);
    public int GetHeaderHeight()
    {
        var r = new RECT();
        var hwnd = SendMessage(Handle, LVM_GETHEADER, IntPtr.Zero, IntPtr.Zero);
        if (GetWindowRect(hwnd, out r))
            return r.Bottom - r.Top;
        return -1;
    }
    protected override void WndProc(ref Message m)
    {
        base.WndProc(ref m);
        if (m.Msg == WM_HSCROLL || m.Msg == WM_VSCROLL || m.Msg == WM_MOUSEWHEEL)
            Scrolled?.Invoke(this, EventArgs.Empty);
    }
}

Upvotes: 1

Reza Aghaei
Reza Aghaei

Reputation: 125197

You can set the OwnerDraw property of the ListView to true. Then customize drawing of the sub items and for your specific column, draw a ProgressBar. To draw a ProgressBar, you can use ProgressBarRenderer or your own custom paint logic.

enter image description here

Example

The highlights of the code:

  • Set the OwnerDraw = true.
  • Handle DrawColumnHeaders and set e.DrawDefault = true.
  • Handle DrawSubItems and for the progress column, draw a ProgressBar.
  • Enable DoubleBuffered for the listview (in a derived class or using reflection).

Here is the code:

using System;
using System.Drawing;
using System.Windows.Forms;
public class ListViewProgressBarExample : Form
{
    private ListView listView1;
    private void InitializeComponent()
    {
        listView1 = new ListView();
        listView1.Columns.Add("#", 70);
        listView1.Columns.Add("File name", 200);
        listView1.Columns.Add("Progress", 400);
        listView1.View = View.Details;
        listView1.OwnerDraw = true;
        listView1.Dock = DockStyle.Fill;
        listView1.FullRowSelect = true;
        listView1.DrawSubItem += listView1_DrawSubItem;
        this.Controls.Add(listView1);
        this.Text = "ListView ProgressBar Example";
        this.Size = new Size(800, 500);
        this.Load += ListViewProgressBarExample_Load;
    }
    public ListViewProgressBarExample()
    {
        InitializeComponent();
        listView1.OwnerDraw = true;
        listView1.GetType().GetProperty("DoubleBuffered",
            System.Reflection.BindingFlags.NonPublic |
            System.Reflection.BindingFlags.Instance)
            .SetValue(listView1, true);
        listView1.DrawColumnHeader += (sender, e) => e.DrawDefault = true;
    }
    private void ListViewProgressBarExample_Load(object sender,
        EventArgs e)
    {
        listView1.Items.Add(new ListViewItem(
            new[] { "1", "lorem ipsum.txt", "10" }));
        listView1.Items.Add(new ListViewItem(
            new[] { "2", "dolor sit.txt", "0" }));
        listView1.Items.Add(new ListViewItem(
            new[] { "3", "amet.txt", "30" }));
        listView1.Items.Add(new ListViewItem(
            new[] { "4", "consectetur adipiscing.txt", "70" }));
        listView1.Items.Add(new ListViewItem(
            new[] { "5", "celit.txt", "50" }));
        listView1.Items.Add(new ListViewItem(
            new[] { "6", "adipiscing elit.txt", "100" }));
    }
    private void listView1_DrawSubItem(object sender, 
        DrawListViewSubItemEventArgs e)
    {
        if (e.ColumnIndex == 2)
        {
            var r = e.Bounds;
            r.Inflate(-1, -1);
            ProgressBarRenderer.DrawHorizontalBar(e.Graphics, r);
            r.Inflate(-1, -1);
            int w = r.Width * int.Parse(e.SubItem.Text) / 100;
            ProgressBarRenderer.DrawHorizontalChunks(e.Graphics,
                new Rectangle(r.Left, r.Top, w, r.Height));
            e.DrawText(TextFormatFlags.HorizontalCenter |
                TextFormatFlags.VerticalCenter);
        }
        else
            e.DrawDefault = true;
    }
}

Upvotes: 2

Related Questions