• Welcome to PlanetSquires Forums.
 

Scrollbar with visual designer

Started by jermy, November 23, 2020, 03:04:23 AM

Previous topic - Next topic

jermy

hello dear people

I need scrollbar control for a project, scrollbar is missing from the toolbox.
how can I still use scrollbar with the visual designer?


Thanks in advance for your help

philbar

Hi Jermy,

If you're talking about making windows that can be scrolled so that you can see the stuff that didn't fit, that's pretty easy, because WinFBX does all the work for you; all you have to do is adapt the Scrollable Window template in WinFBE to fit the style of the Designer windows. I can show you a simple example down below.

On the other hand, it you want to use a scrollbar control on its own, like a volume control or a thermostat, that's a slightly different kettle of fish. There isn't any template that does exactly that. I can show you how I did it in the next post, but someone in a higher pay grade may have a more elegant solution.

Anyway, here is an easy example of scrolling windows. The working parts were copied almost word-for-word from the Scrollable Windows template. The designed window consists of a big text box with a button below it. It's about 800x800. This is the frmMain code (the comments should give you all the instructions to use it):


' frmMain form code file
''
''
/'       Example: A scrollable form

1. Design the form big enough to contain all your controls.
2. In the Load event, create a CScrollWindow class based on the design size of your form.
   Store the pointer in the member of the window class that was created for just this purpose.
   Now you can reduce the size of the window to something smaller, if you want.
3. Give your form an AllEvents event.
   Let it respond as shown to these messages:
      WM_VSCROLL
      WM_HSCROLL
      WM_SIZE
4. That's it. When you shrink the form smaller than the original design size, it
   creates vertical and/or horizontal scrollbars so you can see the whole window.
   When you increase the window at least as large as the original, the scrollbars
   disappear.
'/

Function frmMain_Load( ByRef sender As wfxForm, ByRef e As EventArgs ) As LRESULT
   ' // Create an instance of the CScrollWindow class based on your form
   DIM pScroll AS CScrollWindow PTR = NEW CScrollWindow(frmmain.hWindow)

   ' // Store the pointer in the window class for later deletion
   frmmain.pwindow->ScrollWindowPtr = pScroll
   
   ' // Now you can shrink the window, if you want
   frmmain.height = 400
   
   Function = 0
End Function

''
''
Function frmMain_AllEvents( ByRef sender As wfxForm, ByRef e As EventArgs ) As LRESULT
   select case e.Message
      case WM_VSCROLL
         dim pscroll as CscrollWindow ptr = afxscrollwindowptr(frmmain.hWindow)
         if pscroll then pscroll->onVscroll(e.wParam, e.lParam)
      case WM_HSCROLL
         dim pscroll as CscrollWindow ptr = afxscrollwindowptr(frmmain.hWindow)
         if pscroll then pscroll->onHscroll(e.wParam, e.lParam)       
      case WM_SIZE
         ' // In the WM_SIZE message we need to check the validity of the pointers
         ' // because this message is sent before we have had time to store them.
         ' // i.e., sometimes there is a SIZE event before Form_Load happens.
         dim pscroll as CscrollWindow ptr
         if frmmain.hWindow then pscroll = frmmain.pwindow->scrollwindowptr
         if pscroll then pscroll->onSize(e.wParam, e.lParam)
   end select
   Function = 0
End Function

''
''
Function frmMain_Button1_Click( ByRef sender As wfxButton, ByRef e As EventArgs ) As LRESULT
   frmmain.Close
   Function = 0
End Function




philbar

Hi again Jermy,

As promised, here is my example of adding free-standing scrollbar controls to a designer form. I wanted it to be general enough so I could see in the designer where the scrollbars would be, and I could add as many as I want without duplicating effort, horizontal or vertical. There is an attached picture of the designed form. The two frames, HFrame and VFrame, mark the locations where the scrollbars will be. The form needs to declare the Load and AllEvents events.

First, you include the following file in your project. It defines a class called a Scroller that handles the mechanics of the scrollbars. There should be enough comments to explain it.


/'                            ScrollBar control

The scrollbar control is an adaptation of the scrollbars used to scroll windows that are too
small for their contents. In the case of the control, it is simply a source of numbers that
looks like a volume control. It consists of a vertical or horizontal rectangle with arrows at
either end and a "thumb", which is a small rectangle between the arrows that can be dragged with
the mouse. When the arrows are clicked, the value of the control goes up or down by one "line",
which I have defaulted to 1 unit. When the space between the thumb and an arrow is clicked, the
value of the control is changed by one "page", which I default to one-tenth of the range of the
control. When the thumb is dragged with the mouse, the value of the control is set to the
proportional spot in the range conrresponding to the location of the thumb.

Since WinFBE does not (currently) provide a scroller, this class will create one on
a WinFBE form and give properties and methods to use it.

Usage example:
   1. main .BAS file
         #include once "scroller.inc"         'That's this file. Insert any necessary path information.

   2. Design time:
      Put a frame on the form to mark the location and size of the scroller.
     
      If you make the frame higher than wide, the scroller will operate vertically.
      Because of a Windows quirk, vertical scrollers are a little bit different
      from horizontal ones.
     
   3. Form file, module level:
         Dim Shared as scroller scroll1

   4. Form Load event:
         scroll1.Initialize(frmmain, frmmain.frame1)   'Creates the scroller as a child of frmmain
                                                      'in the location and size of frame1
         scroll1.SetRange(lowval, highval)             'default values 0 to 100
         scroll1.SetVal(startval)                      'starting value of the scroller

   5. When you need the value of the scroller:
         number = scroll1.GetVal()
      To set the value of the scroller from the program
         scroll1.SetVal(newvalue)
      Change the range of the scroller (the page value will be set to one-tenth of the range)
         scroll1.SetRange(newlow, newhigh)       'if you enter them in the wrong order, they will be swapped
      Retrieve the current range limits:
         scroll1.GetRange(curlow, curhigh)

   6. The scroller only works if it responds to event messages WM_VSCROLL or WM_HSCROLL, as
      appropriate. You need to give the form an ALLEVENTS event and test for scroll events
      directed to the hscroll member of the scroller class. When you detect such an event,
      call the UpDate method of the scroller class.

The following structure defined by Windows:

TYPE SCROLLINFO
   cbSize    AS UINT
   fMask     AS UINT
   nMin      AS LONG
   nMax      AS LONG
   nPage     AS UINT
   nPos      AS LONG
   nTrackPos AS LONG
END TYPE

'/
 
type Scroller         
   as wfxForm form                  'the form that is the home of this scroller
   as hwnd hscroll                  'the handle of the the new scroller
   as boolean vert                  'oriented vertically
   as ScrollInfo info               'a structure that holds info including position and range
   declare sub Initialize(form as wfxform, frame as wfxframe)
   declare function GetVal() as long
   declare sub SetVal(newval as long)
   declare sub SetRange(RangeLow as long, RangeHigh as long)
   declare sub GetRange(byref RangeLow as long, byref RangeHigh as long)
   declare sub UpDate(e as EventArgs)
end type

sub scroller.Initialize(form as wfxform, frame as wfxframe)
   dim as long idc = form.getnextctrlid()
   dim as long dstyle
   
   this.info.cbsize = sizeof(this.info)
   if frame.height < frame.width then
      dstyle = WS_VISIBLE OR sbs_horz          'or ws_border
      this.vert = false
   else
      dstyle = WS_VISIBLE OR sbs_vert          'or ws_border
      this.vert = true
   end if   
   
   this.hscroll = form.pwindow->AddControl(   "ScrollBar", , idc, "", _
                                             frame.left, frame.top, _
                                             frame.width, frame.height, _
                                             dstyle)
   this.info.nmin = 0
   this.info.nmax = 100
   this.info.npage = 10
   this.info.npos = 50
   this.info.fmask = sif_page or sif_pos or sif_range
   setscrollinfo(this.hscroll, sb_ctl, @this.info, true)
   enablescrollbar(this.hscroll, sb_ctl, esb_enable_both)
   frame.Visible = false   
end sub

/'   From WinFBX:
      DECLARE FUNCTION AddControl(  BYREF wszClassName AS WSTRING, _
                                    BYVAL hParent AS HWND = NULL, _
                                    BYVAL cID AS LONG_PTR = 0, _
                                    BYREF wszTitle AS WSTRING = "", _
                                    BYVAL x AS LONG = 0, BYVAL y AS LONG = 0, _
                                    BYVAL nWidth AS LONG = 0, BYVAL nHeight AS LONG = 0, _
                                    BYVAL dwStyle AS LONG = -1, BYVAL dwExStyle AS LONG = -1, _
                                    BYVAL lpParam AS LONG_PTR = 0, _
                                    BYVAL pWndProc AS SUBCLASSPROC = NULL, _
                                    BYVAL uIdSubclass AS UINT_PTR = &HFFFFFFFF, _
                                    BYVAL dwRefData AS DWORD_PTR = NULL) _
      AS HWND
'/

function scroller.GetVal() as long

   this.info.fmask = sif_pos or sif_trackpos or sif_page
   if getscrollinfo(this.hscroll, sb_ctl, @this.info) = 0 then
      'afxmsg("Scroller failed to retrieve value", "Error")
   end if
   
   if this.vert then
      return this.info.nmax + this.info.nmin - this.info.npos
   else
      return this.info.npos
   end if
end function

sub scroller.SetVal( newval as long )

   this.info.fmask = sif_pos
   if this.vert then
      this.info.npos = this.info.nmax + this.info.nmin - newval
   else
      this.info.npos = newval
   end if
   setscrollinfo(this.hscroll, sb_ctl, @this.info, true)
end sub

sub scroller.SetRange(RangeLow as long, RangeHigh as long)
   if (rangehigh < rangelow) then
      swap rangelow, rangehigh
   end if
   this.info.fmask = sif_range or sif_page
   this.info.nmin = rangelow
   this.info.nmax = rangehigh
   this.info.npage = abs(rangehigh-rangelow)/10
   setscrollinfo(this.hscroll, sb_ctl, @this.info, true)
end sub

sub scroller.GetRange(byref RangeLow as long, byref RangeHigh as long)
   this.info.fmask = sif_range
   getscrollinfo(this.hscroll, sb_ctl, @this.info)
   rangelow = this.info.nmin
   rangehigh = this.info.nmax
end sub

sub scroller.UpDate(e as EventArgs)
   select case loword(e.wParam)
      case sb_lineup, sb_lineleft
         this.info.fmask = sif_pos
         this.info.npos -= 1
         setscrollinfo(this.hscroll, sb_ctl, @this.info, true)
      case sb_linedown, sb_lineright
         this.info.fmask = sif_pos
         this.info.npos += 1
         setscrollinfo(this.hscroll, sb_ctl, @this.info, true)
      case sb_pageup, sb_pageleft
         this.info.fmask = sif_pos
         this.info.npos -= this.info.npage
         setscrollinfo(this.hscroll, sb_ctl, @this.info, true)
      case sb_pagedown, sb_pageright
         this.info.fmask = sif_pos
         this.info.npos += this.info.npage
         setscrollinfo(this.hscroll, sb_ctl, @this.info, true)
      case sb_thumbposition
         this.info.fmask = sif_trackpos
         getscrollinfo(this.hscroll, sb_ctl, @this.info)
         this.info.fmask = sif_pos
         this.info.npos = this.info.ntrackpos
         setscrollinfo(this.hscroll, sb_ctl, @this.info, true)
   end select
end sub


This is the frmMain file. It allocates a couple of scrollers, initializes them in the Load event, and responds to the WM_HSCROLL and WM_VSCROLL messages in the AllEvents event. One of the buttons sets the value of the scrollers to the middle of their ranges, and the other button prints the current value of the scrollers in the labels above.


' frmMain form code file
''
''
' Include the scroller class
#include once "scroller.inc"

'Allocate a couple of scrollers
dim shared as scroller hscroll          'This one is horizontal because it will overlap a horizontal frame
dim shared as scroller vscroll          'This one is vertical because it overlaps a vertical frame

Function frmMain_Load( ByRef sender As wfxForm, ByRef e As EventArgs ) As LRESULT
   'Create horizontal scroller
   hscroll.Initialize(frmmain, frmmain.frame1)  'Initialize it into a (horizontal) frame.
   hscroll.SetRange(-500, 500)                  'Set the range
   hscroll.SetVal(200)                          'Initial value
   
   'Create vertical scroller
   vscroll.Initialize(frmmain, frmmain.Frame2)  'This time the frame is vertical
   vscroll.SetRange(0, 50000)                   'A bigger range than the other one
   vscroll.SetVal(1000)
   Function = 0
End Function
 
''
''
Function frmMain_AllEvents( ByRef sender As wfxForm, ByRef e As EventArgs ) As LRESULT
   'Test for scroll messages directed to the handle of a Scroller
   select case e.Message
      case wm_vscroll
         if e.lParam = vscroll.hscroll then
            vscroll.UpDate(e)
         end if
      case wm_hscroll
         if e.lParam = hscroll.hscroll then
            hscroll.UpDate(e)
         end if
   end select
   Function = 0
End Function

''
''
Function frmMain_Button1_Click( ByRef sender As wfxButton, ByRef e As EventArgs ) As LRESULT
   'Center the values of the controls in their ranges.
   dim as long n, m
   
   hscroll.GetRange(n, m)
   hscroll.SetVal((n+m)\2)
   vscroll.GetRange(n, m)
   vscroll.SetVal((n+m)\2)
   Function = 0
End Function

''
''
Function frmMain_Button2_Click( ByRef sender As wfxButton, ByRef e As EventArgs ) As LRESULT
   'print the current values of the Scrollers.
   frmmain.hlabel.Text = str(hscroll.GetVal())
   frmmain.vlabel.Text = str(vscroll.GetVal())
   
   Function = 0
End Function



Feel free to use any of this if it has value to you.


jermy

thanks philbar,

tonight I'll see how it all works.
scrollbar in this way is new to me.

Bumblebee

#4
Is the scrollbar control a customized frame?
Could this template be used to create a trackbar control?
https://docs.microsoft.com/en-us/windows/win32/controls/create-a-trackbar
Failed pollinator.

José Roca

I don't use the visual designer, but one way to create a TrackBar control could be


CONST IDC_TRACKBAR = 1001   ' >>> change me if needed

' // Retieve a pointer to the underlying CWindow class
DIM pWindow AS CWindow PTR = AfxCWindowPtr(<handle of the form window>)
IF pWindow THEN
   ' // Add a Trackbar control
   DIM hTrackBar AS HWND = pWindow->AddControl("Trackbar", , IDC_TRACKBAR, "", 75, 60, 200, 32)
   ' // Set the range values
   Trackbar_SetRangeMin hTrackBar, 0, TRUE
   Trackbar_SetRangeMax hTrackBar, 20, TRUE
   ' // Set the page size
   Trackbar_SetPageSize hTrackBar, 1
END IF


philbar

@Bumblebee -

The frame is not actually a part of the scrollbar. I just use it in the designer to mark the place where the scrollbar will be, so I can visualize it. When the scrollbar is initialized, it makes the frame invisible. You can do the same sort of thing with trackbars, too. As you can see from José's example, adding a trackbar or a scrollbar to a form really isn't hard, thanks to WinFBX. You could drop José's code right into the form's Load event (that's when the form handle becomes valid). In order to actually use the trackbar, you need to use the WinFBX wrappers for the control. Mostly you'll just need to get and set the value. I can't find those wrappers in the WinFBE help system, but if you download the whole WinFBX project from Github, you'll find a CHM help file that has a section on Windows Controls Procedures.

The way I see it, the main trick to adding gadgets to designer windows is that as soon as the Application.Run starts in the main program, you never get back to the main program again. You're always jumping from one event function to another. So, if you define something in the Load event, the only way to refer to in another event is by way of some Shared variable. A simple way is to put [ DIM SHARED htrackbar as HWND ] at the global level and then just do the assignment [ htrackbar = pwindow-> ...] in the Load event. All the later events can then reference htrackbar, which they'll need to get its value.

Enough talk from me. I'm a bit over my head.

Bumblebee

#7
Thank-you philbar and José for your explanations.
Paul used a similar approach when he helped me with the image control a few months back.

I was under the impression that 'windows' only refer to forms. A 'child window' would be a form within a form.
When I programmed in Visual Basic... forms, picture boxes and frames were containers. Controls that can contain other controls.

But in this context, a control is a child window, whose Class name/IDC references the list of controls available in the Comctl32.dll library.

The CWindow overview help file bookmarked earlier:
https://github.com/JoseRoca/WinFBX/blob/master/docs/Windows%20GUI/CWindow%20Class.md
Failed pollinator.

José Roca

In fact, "Form" is only used by ex VBer's. In Windows SDK programming, it is called Main Window, and the controls are in reality Child Windows.

VB "Form" was in fact an OLE Container, and the controls were ActiveX controls (OCXs). Everything was tailored for ease of use with the Visual Designer. The problem was that VBer's didn't learn neither Windows Programming nor COM programming. This is why I prefer to code by hand, without compromises, and process messages in the window callback procedure instead of using event functions. But to each his own. I always have been old fashioned.

For example, an small template coding by hand:


' ########################################################################################
' Microsoft Windows
' File: CW_COMMCTRL_Trackbar.fbtpl - Template
' Contents: Demonstrates the use of the Trackbar control.
' Compiler: FreeBasic 32 & 64 bit
' Copyright (c) 2016 José Roca. Freeware. Use at your own risk.
' THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
' EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF
' MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
' ########################################################################################

#define UNICODE
#INCLUDE ONCE "Afx/CWindow.inc"
USING Afx

CONST IDC_TRACKBAR = 1001

DECLARE FUNCTION WinMain (BYVAL hInstance AS HINSTANCE, _
                          BYVAL hPrevInstance AS HINSTANCE, _
                          BYVAL szCmdLine AS ZSTRING PTR, _
                          BYVAL nCmdShow AS LONG) AS LONG

   END WinMain(GetModuleHandleW(NULL), NULL, COMMAND(), SW_NORMAL)

' // Forward declaration
DECLARE FUNCTION WndProc (BYVAL hwnd AS HWND, BYVAL uMsg AS UINT, BYVAL wParam AS WPARAM, BYVAL lParam AS LPARAM) AS LRESULT

' ========================================================================================
' Main
' ========================================================================================
FUNCTION WinMain (BYVAL hInstance AS HINSTANCE, _
                  BYVAL hPrevInstance AS HINSTANCE, _
                  BYVAL szCmdLine AS ZSTRING PTR, _
                  BYVAL nCmdShow AS LONG) AS LONG

   ' // Set process DPI aware
   AfxSetProcessDPIAware

   ' // Create the main window
   DIM pWindow AS CWindow
   pWindow.Create(NULL, "Trackbar control", @WndProc)
   ' // Sizes it by setting the wanted width and height of its client area
   pWindow.SetClientSize(500, 320)
   ' // Centers the window
   pWindow.Center

   ' // Add an Trackbar control
   DIM hTrackBar AS HWND = pWindow.AddControl("Trackbar", , IDC_TRACKBAR, "", 75, 60, 200, 32)
   ' // Set the range values
   Trackbar_SetRangeMin hTrackBar, 0, TRUE
   Trackbar_SetRangeMax hTrackBar, 20, TRUE
   ' // Set the page size
   Trackbar_SetPageSize hTrackBar, 1

   

   ' // Dispatch messages
   FUNCTION = pWindow.DoEvents(nCmdShow)

END FUNCTION
' ========================================================================================

' ========================================================================================
' Main window callback procedure
' ========================================================================================
FUNCTION WndProc (BYVAL hwnd AS HWND, BYVAL uMsg AS UINT, BYVAL wParam AS WPARAM, BYVAL lParam AS LPARAM) AS LRESULT

   SELECT CASE uMsg

      CASE WM_COMMAND
         SELECT CASE GET_WM_COMMAND_ID(wParam, lParam)
            CASE IDCANCEL
               ' // If ESC key pressed, close the application by sending an WM_CLOSE message
               IF GET_WM_COMMAND_CMD(wParam, lParam) = BN_CLICKED THEN
                  SendMessageW hwnd, WM_CLOSE, 0, 0
                  EXIT FUNCTION
               END IF
         END SELECT

      CASE WM_HSCROLL
         IF GetWindowID(cast(HWND, lParam)) = IDC_TRACKBAR THEN
            ' Put your code here
            ' The LOWORD of wParam specifies a scroll bar value that indicates the user's scrolling request.
            ' The HIWORD specifies the current position of the scroll box if the LOWORD is SB_THUMBPOSITION
            ' or SB_THUMBTRACK; otherwise, this word is not used.
         END IF
         EXIT FUNCTION

    CASE WM_DESTROY
         ' // End the application by sending a WM_QUIT message
         PostQuitMessage(0)
         EXIT FUNCTION

   END SELECT

   ' // Default processing of Windows messages
   FUNCTION = DefWindowProcW(hWnd, uMsg, wParam, lParam)

END FUNCTION
' ========================================================================================




Bumblebee

To understand this code I have to be familiar with WinMain, WndProc, and the CWindow framework.
Time will tell how much progress I make.
Failed pollinator.

jermy

#10
I searched for;


' frmMain form code file
''
''

Const IDC_SCROLLBAR = 101

Function frmMain_Load( ByRef sender As wfxForm, ByRef e As EventArgs ) As LRESULT

' // Retieve a pointer to the underlying CWindow class
DIM pWindow AS CWindow PTR = AfxCWindowPtr(sender.hWindow)
IF pWindow THEN
   ' // Add a Scrollbar control
   DIM hScrollBar AS HWND = pWindow->AddControl("SCROLLBAR", , IDC_SCROLLBAR, "", 35, 60, 200, 16, WS_VISIBLE or WS_BORDER)

END IF

    Function = 0
End Function


But I go for the Trackbar, it looks more modern