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