Automatically fit Columns to Listview for Vertical Scrollbar

后端 未结 2 1309
日久生厌
日久生厌 2020-12-12 02:03

I\'ve inherited a Listview to perform some minor changes but I would like to improve the design in the usercontrol Class or anywhere in the Form Class \'cause I\'m not happy

相关标签:
2条回答
  • 2020-12-12 02:51

    when the (default) scrollbar appears inside the listview the size of the last column is not automatically fixed/decreased ... "default scrollbar" taken to mean the Veritical Scroll.

    AutoResize is not AutoFit. It is for sizing columns based on the Header text extent or content length, not managing scrollbars and works very well. If it did automatically resize the last column, we would be angry when the last column is a small 'OK?' type column which was rendered unreadable when it was AutoFitted away.

    PInvokes in a NativeMethods class; these eat scrollbars and get whether they are showing or not.

    Public Const WS_VSCROLL As Integer = &H200000
    Public Const WS_HSCROLL As Integer = &H100000
    Friend Enum SBOrientation As Integer
        SB_HORZ = &H0
        SB_VERT = &H1
        SB_CTL = &H2
        SB_BOTH = &H3
    End Enum
    
    <DllImport("user32.dll")> _
    Private Shared Function ShowScrollBar(ByVal hWnd As IntPtr,
                         ByVal wBar As Integer,
                         <MarshalAs(UnmanagedType.Bool)> ByVal bShow As Boolean
                         ) As <MarshalAs(UnmanagedType.Bool)> Boolean
    End Function
    
    <DllImport("user32.dll", SetLastError:=True)> _
    Private Shared Function GetWindowLong(ByVal hWnd As IntPtr, 
                 ByVal nIndex As Integer) As Integer
    End Function
    
    Friend Shared Function IsHScrollVisible(ByVal ctl As Control) As Boolean
        Dim wndStyle As Integer = GetWindowLong(ctl.Handle, GWL_STYLE)
        Return ((wndStyle And WS_HSCROLL) <> 0)
    End Function
    
    Friend Shared Function IsVScrollVisible(ByVal ctl As Control) As Boolean
        Dim wndStyle As Integer = GetWindowLong(ctl.Handle, GWL_STYLE)
        Return ((wndStyle And WS_VSCROLL) <> 0)
    End Function
    
    Friend Shared Sub ShowHideScrollBar(ByVal ctl As Control, 
            ByVal sb As SBOrientation, ByVal bShow As Boolean)
         ShowScrollBar(ctl.Handle, sb, bShow)
    End Sub
    

    Steal xx pixels from the last column; Put the pixels back if the VScroll goes away.

    Basically what happens is that ClientSizeChanged fires twice: 1) when the VScroll arrives then again a few micros later when the HScroll arrives as a result of the VScroll. So you have to process the event twice. This resizes the desired column in the first pass, and eats the HScroll in the second one. Even though the HScroll is no longer needed after Step 1, it shows up briefly if you dont remove it manually.

    If an HScroll is needed due to the way you laid it out or from the user resizing columns, it will resize the last column anyway, but not eat the HScroll. The extra logic to detect when the first step is not needed is missing (you cant see that it does it, normally).

    Caveats: I have no idea how or if this will work with this 3rd party theme. I dont think that the themes can modify internal scrollbars like these.

    Also, this is intended for use in a subclassed LV, which I know you have already done. For others, with some reworking of references, this should also work on a form to 'push' the changes using the related events (e.g. ClientSizeChanged) even if it leaves a lot of ugly code in the form.

    This also isnt wrapped in something like an IF AutoFit property test.

    Private orgClient As Rectangle      ' original client size for comparing
    Private _VScrollWidth As Integer 
    
    Private _resizedCol As Integer = -1
    Private _VScroll As Boolean = False    ' persistent Scroll flags 
    Private _HScroll As Boolean = False
    
    ' 3rd party???
    _VScrollWidth = System.Windows.Forms.SystemInformation.VerticalScrollBarWidth
    

    Put this somewhere like ISupportInitialize.EndInit:

    orgClient = Me.ClientRectangle
    

    Sub New is not a good place for orgClient - the control hasnt been created yet. OnHandleCreated will do, but without ISupportInitialize, I would invoke a new Sub on the control to set it from Form_Load instead. This can actually be handy to have, if you want to 'restart' things after manually resizing columns then back again. For example:

    ' method on subclassed LV:   from form load: thisLV.ResetClientSize
    
    Public Sub ResetClientSize          
       orgClient = Me.ClientRectangle   
    End Sub
    

    ' a helper:
    Private Function AllColumnsWidth() As Integer
        Dim w As Integer = 0
        For Each c As ColumnHeader In Columns
            w += c.Width
        Next
        Return w
    End Function
    
    ' The Meat
    Protected Overrides Sub OnClientSizeChanged(ByVal e As System.EventArgs)
        Dim VScrollVis As Boolean
        Dim HScrollVis As Boolean
    
        ' get who is Now Showing...local vars
        VScrollVis = NativeMethods.IsVScrollVisible(Me)
        HScrollVis = NativeMethods.IsHScrollVisible(Me)
    
         ' width change
        Dim delta As Integer = (orgClient.Width - ClientRectangle.Width)
    
        Dim TotalWidth As Integer = AllColumnsWidth()
        If (TotalWidth < ClientRectangle.Width - _VScrollWidth) And 
               (_resizedCol = -1) Then
            Exit Sub
        End If
    
        Me.SuspendLayout()
    
        ' If VScroll IS showing, but WASNT showing the last time thru here
        '  ... then we are here because VScroll just appeared.
        ' That being the case, trim the desired column
        If VScrollVis And _VScroll = False Then
            ' a smarter version finds the widest column and resizes THAT one
            _resizedCol = Columns.Count - 1
    
            ' we have to wait for the HScroll to show up
            ' to remove it
            Columns(_resizedCol).Width -= (delta + 1)
    
        End If
    
        ' HScroll just appeared
        If HScrollVis And (delta = _VScrollWidth) Then
    
            ' HScroll popped up, see if it is needed
            If AllColumnsWidth() <= orgClient.Width Then
                ' no, go away foul beast !
                NativeMethods.ShowHideScrollBar(Me,
                            NativeMethods.SBOrientation.SB_HORZ, False)
    
                _HScroll = False             ' hopefully
    
                ' allows us to set it back if the VScroll disappears
                orgClient = ClientRectangle
            Else
                ' ToDo: use this to detect when none of this is needed
                _HScroll = HScrollVis
            End If
        End If
    
        ' If VScroll ISNOT showing, but WAS showing the last time thru here
        '   ...then we are here because VScroll disappeared
        If VScrollVis = False And _VScroll = True Then
            ' put back the pixels
    
            If _resizedCol <> -1 Then
                Columns(_resizedCol).Width += (_VScrollWidth + 1)
                ' reset column tracker
                _resizedCol = -1
                ' reset to new compare size
                orgClient = ClientRectangle
            End If
    
        End If
    
        _VScroll = VScrollVis
    
        Me.ResumeLayout()
    
    End Sub
    
    0 讨论(0)
  • 2020-12-12 03:00

    More or less I have it done so I'll post this solution, something still wrong with the code 'cause it is not working as expected in some cases where I've tried to "play" showing and hidding the scrollbars it can hang the listview, and sure the code has unneded checks and can be improved but now I don't have more time to try to fix the related issues, if someone could help to fix this code to improve it then thankyou so much:

    Dim SizeAlreadyDecreased As Boolean = False
    
    Private Sub ElektroListView_Downloads_ClientSizeChanged(sender As Object, e As EventArgs) _
    Handles ElektroListView_Downloads.ClientSizeChanged
    
        ' Retrieve all the column widths
        Dim AllColumnsWidth As Integer =
            (From col As ColumnHeader In CType(sender.Columns, ListView.ColumnHeaderCollection)
             Select col.Width).Sum
    
        ' Fix the last column width to fill the not-used blank space when resizing the Form.
        Me.ColumnDownload.Width =
            Me.ColumnDownload.Width + (sender.ClientSize.Width - AllColumnsWidth) - 2
    
        ' Fix the last column width to increase or decrease the size if a VertivalScrollbar appears.
        If GetScrollbars(sender) AndAlso Not SizeAlreadyDecreased Then
            SizeAlreadyDecreased = True
            ColumnDownload.Width -= SystemInformation.VerticalScrollBarWidth
    
        ElseIf GetScrollbars(sender) AndAlso SizeAlreadyDecreased Then
            SizeAlreadyDecreased = True
    
        ElseIf Not GetScrollbars(sender) AndAlso SizeAlreadyDecreased Then
            SizeAlreadyDecreased = False
            ColumnDownload.Width += SystemInformation.VerticalScrollBarWidth
    
        ElseIf Not GetScrollbars(sender) AndAlso Not SizeAlreadyDecreased Then
            SizeAlreadyDecreased = False
    
        End If
    
    End Sub
    
    Private Const WS_VSCROLL As Integer = &H200000I
    Private Const WS_HSCROLL As Integer = &H100000I
    Private Const GWL_STYLE As Integer = -16I
    
    <DllImport("user32.dll", CharSet:=CharSet.Auto)>
    Private Shared Function GetWindowLong(
            ByVal hWnd As HandleRef,
            ByVal nIndex As Integer
    ) As Integer
    End Function
    
    Public Function GetScrollbars(ByVal ctrl As Control) As ScrollBars
    
        Dim style As Integer = GetWindowLong(New HandleRef(ctrl, ctrl.Handle), GWL_STYLE)
    
        Dim HScroll As Boolean = ((style And WS_HSCROLL) = WS_HSCROLL)
        Dim VScroll As Boolean = ((style And WS_VSCROLL) = WS_VSCROLL)
    
        If (HScroll AndAlso VScroll) Then
            Return ScrollBars.Both
    
        ElseIf HScroll Then
            Return ScrollBars.Horizontal
    
        ElseIf VScroll Then
            Return ScrollBars.Vertical
    
        Else
            Return ScrollBars.None
    
        End If
    
    End Function
    
    0 讨论(0)
提交回复
热议问题