【问题标题】:Automatically fit Columns to Listview for Vertical Scrollbar自动将列适合垂直滚动条的 Listview
【发布时间】:2014-09-03 03:50:58
【问题描述】:

我继承了一个 Listview 来执行一些小的更改,但我想改进用户控件类或表单类中任何地方的设计,因为我对 Listview 的默认大小调整机制不满意。

在表单类中,我调整最后一列(“下载”)的大小,如下所示:

ColumnDownload.AutoResize(ColumnHeaderAutoResizeStyle.HeaderSize)

问题在于,当(默认)滚动条出现在列表视图中时,最后一列的大小不会自动固定/减小,因此也会自动出现水平且不需要的滚动条。

那么有人可以帮我解释一下如何在滚动条出现时自动修复最后一列的大小?我对此非常迷茫。

注意:我需要说明的是,我并不是在寻找替代的 LV,例如 ObjectListView

注意 2:我没有显示我的 usercontrol 类,因为我目前没有执行任何自动调整大小改进,我不知道从哪里开始。

这是我正常的列表视图:

这是填充项目的同一个列表视图:

【问题讨论】:

  • 我经常做的是将垂直滚动条的宽度添加到最后一列(或空列),当滚动条出现时它不会干扰布局并且水平滚动条不会出现在全部。
  • 问题是 ColumnHeader.AutoResize 方法有自己的逻辑来调整大小,那么我该如何实现你的建议?此外,滚动条宽度由 windows 主题的值确定(我已经调整了我的 3rd 方主题的滚动条的大小)这意味着“默认”滚动条可以根据主题更大或更小,所以我应该实例化一次虚拟滚动条只是为了获得“默认”宽度?无论如何我应该用宽度做什么?我仍然迷失了这一切,感谢您的评论!
  • 在自动调整大小到最后一个列宽之后(ColumnDownload.AutoResize(...) 之后),您始终可以手动添加(例如)15px。您可以使用VerticalScrollbarWidth property 检索滚动条的宽度。
  • I've resized the scrollbar of my 3rd party theme 看看 System.Windows.Forms.SystemInformation.VerticalScrollBarWidth 在常规 WinApp 中是什么,然后使用这个主题再做一次,看看 NET 是否自动反映了这个主题。我只是注意到......没有那个复杂性,你需要使用OnClientSizeChanged并在它出现时吃掉它(它需要2遍)。如果 VScroll 消失,我有一个东西也会把像素放回去。我不知道主题是否会破坏它。
  • @Plutonix 非常感谢,我不知道 'SystemInformation.VerticalScrollBarWidth' 属性,但要正确执行此操作,我如何确定 HScroll 是否存在于列表视图客户区?如果您分享您讨论过的代码,那就太好了!所以我应该在 ClientSizeChanged 事件中做这样的事情吗?:If ScrollBarVisible AndAlso Not SizeAlreadyDecreased Then ColumnDownload.Width -= SystemInformation.VerticalScrollBarWidth

标签: c# .net vb.net winforms listview


【解决方案1】:

when the (default) scrollbar appears inside the listview the size of the last column is not automatically fixed/decreased ...“默认滚动条”表示垂直滚动。

AutoResize 不是AutoFit。它用于根据标题文本范围或内容长度调整列的大小,而不是管理滚动条并且效果很好。如果它确实自动调整最后一列的大小,当最后一列是一个小的“OK?”时,我们会很生气。类型列在 AutoFitted 时呈现为不可读。

NativeMethods 类中调用;这些吃滚动条并获取它们是否显示。

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

从最后一列窃取 xx 个像素;如果 VScroll 消失,则将像素放回去。

基本上发生的情况是ClientSizeChanged 触发两次:1) 当 VScroll 到达时,然后在几个微秒后 HScroll 由于 VScroll 而到达时再次触发。因此,您必须两次处理该事件。这会在第一遍中调整所需列的大小,并在第二遍中吃掉 HScroll。即使在第 1 步之后不再需要 HScroll,如果您不手动删除它,它也会短暂显示。

如果由于您的布局方式或用户调整列的大小而需要 HScroll,它无论如何都会调整最后一列的大小,但不会吃掉 HScroll。缺少检测何时不需要第一步的额外逻辑(通常,您看不到它执行此操作)。

警告:我不知道这将如何或是否适用于这个第 3 方主题。我不认为主题可以像这样修改内部滚动条。

另外,这是为了在子类 LV 中使用,我知道你已经这样做了。对于其他人,通过对引用进行一些修改,这也应该在表单上使用相关事件(例如ClientSizeChanged)“推送”更改,即使它在表单中留下了很多丑陋的代码。

这也没有包含在 IF AutoFit 属性测试之类的东西中。

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

把它放在像ISupportInitialize.EndInit这样的地方:

orgClient = Me.ClientRectangle

Sub New 不是适合orgClient 的地方 - 控件尚未创建。 OnHandleCreated 可以,但如果没有ISupportInitialize,我会在控件上调用一个新的 Sub 来从 Form_Load 设置它。这实际上很方便,如果您想在手动调整列大小后“重新启动”事物然后再回来。例如:

' 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

【讨论】:

  • 太棒了,从您的 2 个答案中可以阅读和学习很多东西,关于“适合”,是的,我真正想做的是为最后一列集成自动调整,并在以下情况下减小该列的大小VScroll 似乎避免了 HScroll 出现(但仍然自动调整),稍后我会阅读你所有的答案谢谢
  • 如果您将第一个测试更改为 If VScrollVis And _VScroll = False And HScrollVis = False Then,如果用户故意导致 HScroll 调整列大小,它应该跳过截断最后一行
【解决方案2】:

或多或少我已经完成了,所以我会发布这个解决方案,代码仍然有问题,因为在我尝试“播放”显示和隐藏滚动条的某些情况下它没有按预期工作可以挂起列表视图,并确保代码有未完成的检查并且可以改进,但现在我没有更多时间尝试修复相关问题,如果有人可以帮助修复此代码以改进它,那么非常感谢:

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

【讨论】:

  • 您不是已经将 LV 子类化了吗?如果是这样,为什么是表单事件而不是OnClientSizeChanged
  • 只是因为订阅表单事件测试它更快
猜你喜欢
  • 1970-01-01
  • 2012-05-23
  • 2012-07-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-07-27
  • 2020-02-14
  • 2017-02-08
相关资源
最近更新 更多