Получение и установка позиции прокрутки WinForms ListView - PullRequest
0 голосов
/ 06 мая 2020

Как я могу:

  • Получить текущую позицию прокрутки ListView в виде координат на основе пикселей? (Пример: если ListView находится полностью вниз, то эта функция должна возвращать эквивалент listHeight - viewPortLength.
  • Установить текущую позицию прокрутки ListView на указанную координату на основе пикселей, заставляя ListView прыгать в это место?
  • Сделать это в vb. net или C#. net управляемый код?
  • Обработка ListViews с помощью 'групп'?

Здесь можно предположить, что listView представляет собой простой текстовый список (не версии с большими / маленькими изображениями).

Хотя есть много ответов, указывающих на использование EnsureVisible, все, что это делает, - это случайным образом прыгать, чтобы убедиться, что указанный элемент виден. Если требуется определенная «позиция прокрутки», и никто заранее не знает, какими должны быть видимые элементы (поскольку базовые данные могут измениться ), то этот метод малопригоден.

Я также нашел предложения по использованию win32-api, в частности, с использованием setScrollInfo и setScrollWindowEx. К сожалению, setScrollInfo только временно изменяет scr oll bar сама по себе и оставляет содержимое окна неизменным, и действительно полезна только для тех, кто реализует свое собственное особое поведение прокрутки. setScrollWindowEx должен теоретически работать через настройку окон просмотра и повторение в новом контенте, но при использовании с этим объектом указанная функция весело сообщит, что она была успешной, ничего не делая и сообщая, что она перерисовала прямоугольник размера [0, 0] в позиции [0, 0] в [w,h],[x,y] обозначениях. Другими словами, внутреннее состояние ListView не очень доступно.

1 Ответ

0 голосов
/ 06 мая 2020

Я нашел частичное решение этой проблемы. Предположим, что ListView расширяется / подклассифицируется. Сначала импортируйте функцию из user32.dll; GetScrollInfo, как показано ниже:

Declare Function GetScrollInfo Lib "user32" Alias "GetScrollInfo" (ByVal hWnd As IntPtr, _
    ByVal n As Integer, ByRef lpScrollInfo As SCROLLINFO) As Integer
Structure SCROLLINFO
    Dim cbSize As Integer
    Dim fMask As Integer
    Dim nMin As Integer
    Dim nMax As Integer
    Dim nPage As Integer
    Dim nPos As Integer
    Dim nTrackPos As Integer
End Structure
Private Const SB_HORZ As Integer = 0
Private Const SB_VERT As Integer = 1

Это можно использовать для получения позиции прокрутки окна как смещения по оси Y. Позиция прокрутки определяется в fantasy-scroll-units, которые можно определить, но вызываемый хочет, чтобы они были, если они 32-битные целые числа. «Действие прокрутки» также не обязательно должно быть евклидовым, но единицы измерения используются для определения размера полосы прокрутки; например, размер перетаскиваемого элемента или «размер страницы» определяется как размер окна в «единицах прокрутки», деленный на размер общей полосы прокрутки, с предварительно установленным жестко заданным минимальным размером, но сколько каждый щелчок прокрутки или перетаскивание или прокрутка колесика мыши кодируются элементом с помощью полосы прокрутки.

Это приводит к определению этой структуры SCROLLINFO. Он состоит из семи членов;

  1. nMin и nMax. nMin <= nPos <= nMax автоматически зажимается scrollBar. Когда полоса прокрутки полностью вверху, то nPos = nMin. Полностью внизу, nPos = nMax.
  2. cbSize - это размер этой конструкции. Обычно cbSize = 28 при использовании win32 api.
  3. fMask можно использовать для получения только части значений. Чтобы получить все, fMask = 7. Значения:
    1. 0x01: возвращает nMin, nMax
    2. 0x02: возвращает nPage
    3. 0x04: возвращает nPos и nTrackPos
  4. nPage обозначает (относительный) размер ручки прокрутки: nPage / (nMax - nMin) будет высотой относительно размера полосы для y-полосы прокрутки.
  5. nPos - текущая позиция полосы прокрутки.
  6. nTrackPos - то же самое, но обновляется во время операции щелчка и перетаскивания, что позволяет вам иметь элемент формы, который перемещается, пока вы перетаскиваете полосу прокрутки (вместо стандартной; когда вы ее отпускаете), как и большинство текста редакторы делают.

Первый трюк состоит в том, чтобы точно выяснить, как мнимые единицы отображаются в пикселях. К счастью, для ListView это кажется довольно простым: следующее определение становится «достаточно близким»;

 Public Function getScrollPos() As Integer
     Dim si As SCROLLINFO = Me.getScrollInfo()
     Dim x As Integer = GetScrollInfo(Me.Handle, SB_VERT, si)
     ' The Windows Scrollbar magicunit is the same as pixels, off by exactly one window height. 
     ' Adding nPage gets you a pixel based location. 
     Return si.nPos + si.nPage
 End Function

Затем мы должны использовать эту информацию и установить значение Y для listView. Это сложная часть; в базовых формах c windows нет готового метода для этой задачи. Однако у каждого элемента есть функция для получения позиции Y. Но наивный метод простого использования Me.items(Y/17) (примечание; 17 - высота по умолчанию) вообще не работает, потому что переменная-член Me.Items не сортируется с отображением.

Public Sub setScrollPos(ByVal y As Integer)
    Dim si As SCROLLINFO = Me.getScrollInfo()
    Dim x As Integer = GetScrollInfo(Me.Handle, SB_VERT, si)
    If (y > si.nMax) Then
        y = Math.Max(si.nMax, 0)
    End If
    Dim itemHeight As Integer = Me.Items(0).GetBounds(ItemBoundsPortion.Entire).Height
    Dim iClosest = 0
    Dim iTmp = 0
    Dim delta = Integer.MaxValue
    ' "Stupidly clever" solution by simply trying all items out and finding out which one is closest to the Y value. 
    ' Implemented as there is no "sorted index" exposed by ListViewItemCollection. (No idea why...)
    If Me.Items.Count > 0 Then
        For i As Integer = 0 To Me.Items.Count - 1 Step 1
            iTmp = Math.Abs(Me.Items.Item(i).Position.Y - y)
            If iTmp < delta Then
                delta = iTmp
                iClosest = i
            End If
        Next 'i
    End If
    Me.TopItem = Me.Items.Item(iClosest)
    Me.Invalidate()
End Sub

Здесь , l oop по каждому элементу, выясните, какой из них имеет наиболее подходящую координату Y для нашего измерения, а затем установите его как верхний. Решение является «глупым» в том смысле, что оно выполняет вычисление, которое форма должна делать в любом случае, чтобы правильно отображать себя: ему необходимо знать порядок отображения элементов списка. Это также не совсем правильно: если предполагаемая верхняя видимая строка является (частью) «разделителем групп», то наша функция setScrollPos вместо этого найдет ближайший ListViewItem и отобразит его вверху. Он также не может обрабатывать любое перемещение субстроки и, таким образом, может быть отключен на (до) половины высоты строки путем округления до целой строки.

...