Автоматическая прокрутка по горизонтали при добавлении столбцов в представление списка

#c# #winforms #listview #scrollbar #autoscroll

Вопрос:

Я хочу автоматически прокручивать представление списка по горизонтали до последнего столбца каждый раз, когда добавляется заголовок столбца.
Мне нужно только прокрутить до последнего столбца, а не до элемента.

  • listView1.Alignment установлен в левое положение
  • listView1.View устанавливается в View.Details

Я пробовал это, но это ничего не дает:

 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, но если это единственный вариант, любая помощь будет признательна.
Если бы это было возможно без пинвокинга, это было бы лучше.

Ответ №1:

Чтобы прокрутить представление списка по горизонтали, вы можете отправить в элемент управления сообщение LVM_SCROLL, установив wParam значение, соответствующее позиции (в пикселях), на которую нужно прокрутить.
Положение относительно текущего смещения.
Установите lParam для прокрутки по вертикали.

Поскольку вы хотите прокрутить до последнего столбца, а представление списка находится в 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);
 

Редактировать:
Измените свой код, добавив интервал между вставкой каждого столбца/элемента списка, чтобы увидеть его в замедленном режиме. Вы можете использовать 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);
 

nPos Элемент структуры SCROLLINFO возвращает текущую позицию прокрутки. nMax максимальное значение прокрутки. Вычтите nPage , соответствующее элементу управления 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
}
 

Комментарии:

1. Моим решением также был user32 SendMessage. private const int WM_HSCROLL = 0x114; private const int WPARAM = 7; SendMessage(listView1.Handle, WM_HSCROLL, WPARAM, 0); Я использовал 0x114 для горизонтальной прокрутки и 7 в качестве Wparam, и это сработало, ниже 7 не прокручивалось до конца.

2. Я попробовал ваше решение, и полоса прокрутки переходит к последнему столбцу, но после перехода всегда возвращается к началу при добавлении столбцов.

3. Я не знаю, как вы это реализовали. См. Пример редактирования.

4. Я добавил SendMessage после добавления столбца и добавления элемента, и мне нравится, что это работает.

Ответ №2:

Вы можете создать CustomListView и Re_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);
 

Ответ №3:

Мое решение заключается в использовании функции 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);
}