向 ListView 添加列时自动水平滚动

Auto scroll horizontally while adding columns to a ListView

我想在每次添加 ColumnHeader 时自动水平滚动 ListView 到最后一列。
我只需要滚动到最后一列,而不是项目。

我试过了,但没有任何效果:

listView1.AutoScrollOffset = new Point(listView1.AutoScrollOffset.X-10, 0);`

这仅在我仅添加项目时有效:

listView1.EnsureVisible(0);

string rowstr = "Test,";
for (var i = 0; i < 10; i++)
{
    Debug.WriteLine(i);
    
    ColumnHeader head = new ColumnHeader();
    head.Text = i.toString();
    listView1.Columns.Add(head);
    listView1.Columns[i].Width = 65;
    
    rowstr += "Test"+",";
    string[] row = rowstr.Split(",");
    var listViewItem = new ListViewItem(row);
    listViewItem.Font = new Font("Consolas", 10f);
    listView1.Items.Insert(0, listViewItem);

    //listView1.EnsureVisible(0);
    listView1.AutoScrollOffset = new Point(listView1.AutoScrollOffset.X-10, 0); 
}

我没有尝试覆盖 WndProc 或调用 User32 函数,但如果这是唯一的选择,我们将不胜感激。
如果不用PInvoking也可以,那就更好了。

要水平滚动 ListView,您可以向控件发送 LVM_SCROLL 消息,将 wParam 设置为与要滚动到的位置(以像素为单位)相对应的值。
位置是相对于当前的偏移量。
设置 lParam 垂直滚动。

因为你想滚动到最后一列并且 ListView 处于 Details 模式,你可以只传递 int.MaxValue 作为偏移量:Win32 控件将进行调整(它会这样做无论如何)。

[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern int SendMessage(IntPtr hWnd, int uMsg, int wParam, int lParam);

private const int LVM_SCROLL = 0x1014;
SendMessage(listView1.Handle, LVM_SCROLL, int.MaxValue, 0);

编辑:
修改您的代码,在每个 Column/ListItem 插入之间添加一个间隔,以慢动作查看它。您可以使用 Button.Click 处理程序。

private async void SomeButton_Click(object sender, EventArgs e)
{
    for (int idx = 0; idx < 10; idx++) {
        var head = new ColumnHeader() {
            Text = idx.ToString(),
            Width = 65
        };
        listView1.Columns.Add(head);
        SendMessage(listView1.Handle, LVM_SCROLL, int.MaxValue, 0);
        var rowArray = new List<string>(Enumerable.Range(0, idx + 1).Select(n => $"Test{n}"));
        var listViewItem = new ListViewItem(rowArray.ToArray());
        listView1.Items.Insert(0, listViewItem);
        SendMessage(listView1.Handle, LVM_SCROLL, int.MaxValue, 0);
        await Task.Delay(500);
    }
}

删除 async 内容以使其免费。


要获取当前滚动位置,如果需要,请使用 GetScrollInfo()。例如,

var scrollInfo = new SCROLLINFO(SBInfoMask.SIF_ALL);
bool result = GetScrollInfo(listView1.Handle, SBParam.SB_HORZ, ref scrollInfo);

SCROLLINFO 结构的 nPos 成员 returns 当前滚动位置。 nMax 最大滚动值。减去nPage,对应Control的ClientSize.Width,得到最大可能的滚动值。
当拇指滚动到末尾时,它应该等于nPos

Win32 声明:

[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool GetScrollInfo(IntPtr hwnd, SBParam nBar, [In, Out] ref Point lpsi);

[StructLayout(LayoutKind.Sequential)]
internal struct SCROLLINFO
{
    public uint cbSize;
    public SBInfoMask fMask;
    public int nMin;
    public int nMax;
    public uint nPage;
    public int nPos;
    public int nTrackPos;

    public SCROLLINFO(SBInfoMask mask)
    {
        cbSize = (uint)Marshal.SizeOf<SCROLLINFO>();
        fMask = mask;
        nMin = 0; nMax = 0; nPage = 0; nPos = 0; nTrackPos = 0;
    }
}

internal enum SBInfoMask : uint
{
    SIF_RANGE = 0x1,
    SIF_PAGE = 0x2,
    SIF_POS = 0x4,
    SIF_DISABLENOSCROLL = 0x8,
    SIF_TRACKPOS = 0x10,
    SIF_ALL = (SIF_RANGE | SIF_PAGE | SIF_POS | SIF_TRACKPOS),
    SIF_POSRANGE = (SIF_RANGE | SIF_POS | SIF_PAGE)
}

public enum SBParam : int
{
    SB_HORZ = 0x0,
    SB_VERT = 0x1,
    SB_CTL = 0x2,
    SB_BOTH = 0x3
}

您可以创建 CustomListViewRe_Define EnsureVisible 方法

class DataFlowFilterListView : ListView
{
    /// <summary>
    /// margin from the selected column to the border of listview.
    /// </summary>
    const int MARGIN = 20;

    /// <summary>
    /// native windows message to scroll the listview.
    /// </summary>
    const Int32 LVM_FIRST = 0x1000;
    const Int32 LVM_SCROLL = LVM_FIRST + 20;

    [DllImport("user32")]
    static extern IntPtr SendMessage(IntPtr Handle, Int32 msg, IntPtr wParam,
    IntPtr lParam);

    private void ScrollHorizontal(int pixelsToScroll)
    {
       SendMessage(this.Handle, LVM_SCROLL, (IntPtr)pixelsToScroll,
       IntPtr.Zero);
    }

     /// <summary>
     /// Ensure visible of a ListViewItem and SubItem Index.
     /// 
     /// 
     /// </summary>
     /// <param name="item"></param>
     /// <param name="subItemIndex"></param>
     public void EnsureVisible(ListViewItem item, int subItemIndex)
     {
         if (item == null || subItemIndex > item.SubItems.Count - 1)
         {
           throw new ArgumentException();
         }

         // scroll to the item row.
         item.EnsureVisible();
         ScrollToRectangle(item.SubItems[subItemIndex].Bounds.Width);
     }

     /// <summary>
     /// Scrolls the listview.
     /// </summary>
     /// <param name="bounds"></param>
     private void ScrollToRectangle(int width)
     {
         this.ScrollHorizontal(width);
     }
}

现在您可以使用 DataFlowFilterListView 代替 ListView


每次向列表中添加一列时执行以下代码

listView1.EnsureVisible(listView1.Items[0], 0);

我的解决方案是使用 user32 函数和 SendMessage()

[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, int wParam, int lParam);

private const int WM_HSCROLL = 0x114; //for horizontal scroll
private const int WPARAM = 7; //to scroll to end

实施:

string rowstr = "Code,"
for (var i = 0; i < 10; i++)
{
    ColumnHeader head = new ColumnHeader();
    head.Text = i.toString();
    listView1.Columns.Add(head);
    listView1.Columns[i].Width = 65;
    
    rowstr += "Test"+",";
    string[] row = rowstr.Split(",");
    var listViewItem = new ListViewItem(row);
    listViewItem.Font = new Font("Consolas", 10f);
    listView1.Items.Insert(0, listViewItem);
    SendMessage(listView1.Handle, WM_HSCROLL, WPARAM, 0);
}