|
|
![]() |
|
||
|
|
|||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||
| Visual Basic
Window/Form Routines SendMessage: Move Controls to Simulate Form Scrolling |
||
| Posted: | Friday July 16, 2004 | |
| Updated: | Monday December 26, 2011 | |
| Applies to: | VB4-32, VB5, VB6 | |
| Developed with: | VB6, Windows XP | |
| OS restrictions: | None | |
| Author: | VBnet - Randy Birch, Dave Scarmozzino, Rui Trigueiros | |
|
Related: |
SendMessage: Creating a Scrollable Viewport to Simulate a Scrollable Form |
|
| Prerequisites | ||||||||||||||||||
| None. | ||||||||||||||||||
|
|
||||||||||||||||||
|
The first technique is to create a scrollable viewport (as shown at SendMessage: Creating a Scrollable Viewport to Simulate a Scrollable Form) and as popularized in the MSKB article on scrollable viewports. In this technique, all controls are placed inside a container (such as a picture box) which in turn is placed into another picture box. The 'inner' picture box is sized as required to accommodate the controls contained within and, in response to scroll bar movement from scroll controls placed on the form, the larger inner picture box is repositioned to reveal different portions of its contained controls through the form-sized 'viewport' created by the 'outer' picture box. With this technique there is only one 'moving' control - the larger inner picture box. The second method - shown here - is to place all controls directly on the form and then determine the total 'area' the controls occupy within the form's client area. On each scroll bar movement each control is moved (repositioned) appropriately within the exposed client area. This method does not require the controls be placed into containers as the viewport example does and is therefore a simpler method to employ, especially on quick data entry-type screens. Plus, you get to work physically with the controls on the form (rather than the pain of selected controls inside a container) which alone can speed up application design. The code presented here is based on a demo originally published by Dave Scarmozzino (thescarms.com). The principles from Dave's code are pretty much carried over here but I have made considerable changes to the original code correctly handle scrollbar sizing, work area calculation and control positioning regardless of form style, form caption size and border widths. And, because users expect to be able to use a grab handle to resize a sizable form, I've added code from ReleaseCapture: Simulating a Working Size-Grip on a VB Form to provide this functionality. Positioning of the controls is achieved by looping through the controls within the client area and manipulating the top and left properties of those controls. However, some controls do not expose these properties (e.g. common dialog, timer, lines etc.), while others do not allow manipulation of these properties (e.g. toolbars and status bars set to align). In order to accommodate the peculiarity of different controls I resorted to a simple On Error Resume Next test which handles attempts to manipulate non-existent or read-only properties of the controls, albeit arguably not necessarily in the nicest way. While certainly a functional and solid solution, your programming practices and standards may dictate the need for an alternative action to circumvent the restrictions of some controls. One other thing to make mention of: this is as simple a demo as possible to deliver the ability to simulate a scrolling form with a resizing handle. It does not take into account nor compensate for portions of the client area that may be occupied by alignable controls such as a status bar, toolbar or aligned picture box. This limitation can be worked around by additional handling in the appropriate events, but such is outside the scope of this hopefully "simple" demonstration. Finally, the gripper handle shown is constructed by using Windows' Marlett font, just as Windows normally does in versions prior to Windows XP, or on a Windows XP box running in "standard" GUI mode. Windows XP in "themed" mode does not use Marlett for the gripper image but rather draws the gripper is a series of dots rather directly through APIs calls to the XP themes.dll. So while it is possible to display the XP-style gripper on XP machines, the code required to do so is involved and is also outside the scope of this particular demo. Specific demos for that and other XP UI enhancements are in development. By the way, if you're using this code on a RTL system that displays the resizing grip handle at the bottom left of the form rather than the bottom right, the Marlett character to use below instead of "o" is "x".
|
||||||||||||||||||
| BAS Module Code | ||||||||||||||||||
| None. | ||||||||||||||||||
|
|
||||||||||||||||||
| Form Code | ||||||||||||||||||
|
|
||||||||||||||||||
|
Add
a horizontal and vertical scrollbar (HScroll1, VScroll1) and a picture
box (Picture1) to a form to act as the scroll bars and resizing gripper
handle. The position, size and initial properties of these three controls is of no
importance, as all are properly set up in the form load and resize events.
With those three mandatory controls, add any number/combination of other controls representing a form to scroll - these controls can have any control names desired. The controls can also either be individual controls or part of a control array. Add the following code: |
||||||||||||||||||
|
|
||||||||||||||||||
Option Explicit '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Copyright ©1996-2011 VBnet/Randy Birch, All Rights Reserved. ' Some pages may also contain other copyrights by the author. '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' ' Distribution: You can freely use this code in your own ' applications, but you may not reproduce ' or publish this code on any web site, ' online service, or distribute as source ' on any media without express permission. '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' 'loosely based on code by Dave Scarmozzino (thescarms.com)
'The height and width of all information on
'the screen if it were all shown at one time
Private maxViewportHeight As Long
Private maxViewportWidth As Long
'flag to enables/disable the ability to
'resize the window using the 'fake' grabber
Private bWindowIsResizable As Boolean
Private Const WM_NCLBUTTONDOWN = &HA1
Private Const HTBOTTOMRIGHT = 17
Private Declare Function SendMessage Lib "user32" _
Alias "SendMessageA" _
(ByVal hwnd As Long, _
ByVal wMsg As Long, _
ByVal wParam As Long, _
lParam As Any) As Long
Private Declare Function ReleaseCapture Lib "user32" () As Long
Private Sub Form_Load()
Dim ctl As Control
Dim maxh As Long
Dim maxw As Long
'these are the default controls widths
'and can be changed to suit your purpose
HScroll1.Height = 255
VScroll1.Width = 255
'set flag indicating a resizable window
bWindowIsResizable = (Me.BorderStyle = vbSizable) Or _
(Me.BorderStyle = vbSizableToolWindow) And _
(Me.WindowState <> vbMaximized)
'set up the picture box used to fill
'the corner between the H and V scroll
'bars, and if the window is sizable
'print a 'gripper' image to the control
With Picture1
.ScaleMode = vbTwips
.AutoRedraw = True
.ForeColor = &H80000015
.BackColor = Me.BackColor
.BorderStyle = 0
'prepare to draw fake gripper image
.Font.Name = "Marlett"
.Font.Size = 11
.Font.Bold = False
End With
'we have to safely handle controls that
'do not expose referenced properties
'(such as timers, common dialog control,
'lines/shapes)
On Error Resume Next
'calc the largest size the form
'has to 'scroll' to accommodate
'all controls, but ignore controls embedded inside
'other containers (e.g. frames or picture boxes)
For Each ctl In Me.Controls
If Not (ctl Is VScroll1) And _
Not (ctl Is HScroll1) And _
Not (ctl Is Picture1) And _
(TypeOf ctl.Container Is Form) Then
If (ctl.Top + ctl.Height) > maxh Then maxh = (ctl.Top + ctl.Height)
If (ctl.Left + ctl.Width) > maxw Then maxw = (ctl.Left + ctl.Width)
End If
Next
On Error Goto 0
'remember to allow for the size
'of the scrollbars to ensure
'everything fits on-screen when
'scrolled
maxViewportHeight = maxh + (HScroll1.Height * 2)
maxViewportWidth = maxw + (VScroll1.Width * 2)
'position and ensure the other controls
'move behind the scrollbars and gripper pix box
With VScroll1
.Move Me.ScaleWidth - .Width, 0
.ZOrder 0
End With
With HScroll1
.Move 0, Me.ScaleHeight
.ZOrder 0
End With
Picture1.ZOrder 0
End Sub
Private Sub Form_Resize()
'don't attempt resizing if minimized!
If Me.WindowState <> vbMinimized Then
VScroll1.Left = Me.ScaleWidth - VScroll1.Width
HScroll1.Top = Me.ScaleHeight - HScroll1.Height
'if the form has been resized to
'display all the controls, disable
'the scrollbars
VScroll1.Enabled = (maxViewportHeight - Me.ScaleHeight) >= 0
HScroll1.Enabled = (maxViewportWidth - Me.ScaleWidth) >= 0
'this prevents an error if
'the form is sized too small
If (Me.ScaleHeight > HScroll1.Height) And _
(Me.ScaleWidth > VScroll1.Width) Then
'because the window size has
'changed we have to modify the
'scrollbar parameters to reflect
'the new form size and resize the
'scrollbar to fill the client area
With VScroll1
.Top = 0
.Height = (Me.ScaleHeight - HScroll1.Height)
If VScroll1.Enabled Or _
Me.WindowState = vbMaximized Then
.Min = 0
.Max = (maxViewportHeight - Me.ScaleHeight)
.SmallChange = (Screen.TwipsPerPixelY * 10)
.LargeChange = (Me.ScaleHeight - HScroll1.Height)
End If 'VScroll1.Enabled
End With
With HScroll1
.Left = 0
.Width = (Me.ScaleWidth - VScroll1.Width)
If HScroll1.Enabled Or _
Me.WindowState = vbMaximized Then
.Min = 0
.Max = (maxViewportWidth - Me.ScaleWidth)
.SmallChange = (Screen.TwipsPerPixelX * 10)
.LargeChange = (Me.ScaleWidth - VScroll1.Width)
End If 'HScroll1.Enabled
End With
End If '(Me.ScaleHeight > HScroll1.Height) And ...
End If 'Me.WindowState
'Windows applications remove the gripper
'if a resizable window is maximized, so
'we'll do the same with our fake gripper.
'This must execute regardless of window state
'to ensure the gripper is displayed when valid
bWindowIsResizable = (Me.BorderStyle = vbSizable Or _
Me.BorderStyle = vbSizableToolWindow) And Not _
(Me.WindowState = vbMaximized)
'if resizable is false then remove
'the gripper handle mark, otherwise
'ensure it is visible
With Picture1
.Move VScroll1.Left, HScroll1.Top, VScroll1.Width, HScroll1.Height
'if sizable windows print the gripper image
If bWindowIsResizable = True Then
'by using the scale- and text- width/height,
'we can ensure the fake grab handle is drawn at the
'bottom right of the picture box regardless of the
'width set for the scrollbars. (Try changing the
'scrollbar size to 500 in the Load event to demonstrate.)
.Cls
Picture1.CurrentX = Picture1.ScaleWidth - Picture1.TextWidth("o")
Picture1.CurrentY = Picture1.ScaleHeight - Picture1.TextHeight("o")
Picture1.Print "o"
Else
.Cls 'clear the pixbox
End If 'bWindowIsResizable
End With 'Picture1
End Sub
Private Sub Picture1_MouseDown(Button As Integer, _
Shift As Integer, _
x As Single, _
y As Single)
'if a sizable window and not maximized then..
If bWindowIsResizable Then
'..fake a resize grabber action
If Button = vbLeftButton Then
ReleaseCapture
SendMessage Me.hwnd, WM_NCLBUTTONDOWN, HTBOTTOMRIGHT, ByVal 0&
End If 'Button
End If 'bWindowIsResizable
End Sub
Private Sub Picture1_MouseMove(Button As Integer, _
Shift As Integer, _
x As Single, _
y As Single)
'users expect a resizable window with a grip handle
'to display a resizing arrow. This code does this,
'and is equivalent to using:
' if bWindowIsResizable = True Then
' Picture1.MousePointer = vbSizeNWSE
' else
' Picture1.MousePointer = vbDefault
' end if
'
'because:
'True (-1) AND vbSizeNWSE (8) = 8 (or vbSizeNWSE)
'False (0) AND vbSizeNWSE (8) = 0 (or vbDefault)
Picture1.MousePointer = (bWindowIsResizable And vbSizeNWSE)
End Sub
Private Sub VScroll1_Change()
Call DoFormScroll
End Sub
Private Sub VScroll1_Scroll()
Call DoFormScroll
End Sub
Private Sub HScroll1_Change()
Call DoFormScroll
End Sub
Private Sub HScroll1_Scroll()
Call DoFormScroll
End Sub
Private Sub DoFormScroll()
Dim ctl As Control
'just like Load, handle errors for controls
'not exposing touched properties
On Local Error Resume Next
'saves scroll state between calls
Static oldVpos As Long
Static oldHpos As Long
'Move each control in response to
'a change in scrollbar positions.
'
'The code below skips over the two scroll bars
'and the picture box used for the fake gripper
'handle by excluding those controls by name.
'If you rename these controls for your final
'application it is recommended that a global
'form Replace be performed to change the
'control names of these controls to ensure
'the references here, in the Load event,
'the Resize event, and the control events
'themselves all reflect your new control names.
For Each ctl In Me.Controls
If Not (ctl Is VScroll1) And _
Not (ctl Is HScroll1) And _
Not (ctl Is Picture1) And _
(TypeOf ctl.Container Is Form) Then
ctl.Top = ctl.Top + oldVpos - VScroll1.Value
ctl.Left = ctl.Left + oldHpos - HScroll1.Value
End If
Next
'we need to save the positions
oldVpos = VScroll1.Value
oldHpos = HScroll1.Value
End Sub
|
||||||||||||||||||
| Comments | ||||||||||||||||||
|
The demo uses On Local Error (or rather just On Error since the 'Local'
part was depreciated early in VB, but I continue to use it to show that I expect all
errors to be handled locally within the routine defining the error handler -
a sort of self-documenting code) in
order to circumvent the need for an excessively large and all-inclusive "If
(Not ctl is <controlname>)" statement in DoFormScroll and the Load
event to handle controls that do not expose a settable Top and Left
property. If On Error isn't your cup-of-tea, feel free to increase
the If..Then block to explicitly name all the controls on your form. In addition, VB's line controls present another issue, namely their positioning via X1.X2/Y1.Y2 properties rather than the usual left/top etc. The error traps in the code will correctly catch references these controls, but because the code does not contain explicit X1.X2/Y1.Y2 positioning code a form's lines will not scroll using the code above. If this type of control is important in your application, you will need to further extend the If..Then test such as: For Each ctl In Me.Controls
If Not (ctl Is VScroll1) And _
Not (ctl Is HScroll1) And _
Not (ctl Is Picture1) And _
(TypeOf ctl.Container Is Form) Then
If Not (TypeOf ctl Is Line) Then
ctl.Top = ctl.Top + oldVpos - VScroll1.Value
ctl.Left = ctl.Left + oldHpos - HScroll1.Value
Else
ctl.X1 = ctl.X1 + oldHpos - HScroll1.Value
ctl.X2 = ctl.X2 + oldHpos - HScroll1.Value
ctl.Y1 = ctl.Y1 + oldVpos - VScroll1.Value
ctl.Y2 = ctl.Y2 + oldVpos - VScroll1.Value
End If
End If
Next
A similar test is also required in the Load event control loop to properly include the area taken by the lines in the calculation of the client's scrollable real estate. |
||||||||||||||||||
|
|
||||||||||||||||||
|
|
|
|||||
|
|||||
|
|
|||||
|
Copyright ©1996-2011 VBnet and Randy Birch. All Rights Reserved. |
![]() |