Announcement

Collapse

Forum Guidelines

This forum is for finished source code that is working properly. If you have questions about this or any other source code, please post it in one of the Discussion Forums, not here.
See more
See less

4-Way Window Splitting With Scintilla

Collapse
X
  • Filter
  • Time
  • Show
Clear All
new posts

  • PBWin 4-Way Window Splitting With Scintilla

    The first example demonstrates a 4-way split window using a horizontal bar above
    the client area and 1 or 2 vertical bar(s) to the left of the client area.
    It uses pretty much the same code I implemented in Jose's SCI editor, but is only
    the minimum code needed to get 4-way splitting implemented. Using bars is by far
    the easiest way to provide a splitting mechanism because the Scintilla controls
    take care of much of the housekeeping for you. Hopefully, some industrious programmer
    will take this code and adapt it to Jose's CSED editor - (how about YOU Jose:thinking.

    The second example is a tad more busy. Rather than using bars, it places 1 button
    above the right vertical scrollbar and 1 or 2 buttons to the left of the horizontal
    scroll bar(s). This method seems to be more popular with 'professional' apps.
    It's busy because 3 (of the potential 8) Scintilla scrollbars must be turned off and
    replaced by scrollbar controls, so much additional code is needed for scrollbar management.
    Hopefully, folks find these examples useful for their own Scintilla based projects...
    [EDIT] Fixed V scroll bugs in scrollbar buttons example

    Code:
    ' Scintilla Split Bars.bas
    ' This provides what I beleive to be the basic raw code needed to provide
    ' a decent 4-way split window mechanism for Scintilla.
    ' (Tested with Jose's headers, PB v10.4 and the scilexer.dll v3.5.1)
    #Compile Exe
    #Dim All
    #Include ONCE "Scintilla.inc"
    ' The following cursors can be found in Jose's SED 2.xx source code.
    ' Otherwise, substitute one of the stock windoze cursors (see WINMAIN) & comment out the below:
    '#Resource Icon, AppIcon, "Ho_split.cur"
    '#Resource Icon, AppIcon, "Vt_split.cur"
    
    ' Identifiers
    ENUM Identifiers SINGULAR
       IDC_EDIT1   = 3110   ' Edit control main (bottom Right)
       IDC_EDIT2            ' Edit control BL (bottom Left)
       IDC_EDIT3            ' Edit control TR (Top Right)
       IDC_EDIT4            ' Edit control TL (Top Left)
       IDC_TAB              ' To switch control focus
       SPLITSIZE   = 4      ' Size of the splitter bars
    END ENUM
    
    ' *** For Splitting use ***
    TYPE SplitWinInfoStruct
       hBotR       AS DWORD    ' Handle of bottom right edit control
       hBotL       AS DWORD    ' Handle of bottom left edit control
       hTopR       AS DWORD    ' Handle of top right edit control
       hTopL       AS DWORD    ' Handle of top left edit control
       pSciMsg(3)  AS DWORD    ' Edit control Direct Message pointers
       hFocus      AS DWORD    ' Handle of edit control with focus
       SplitY      AS INTEGER  ' Last Y coordinate of mouse cursor
       SplitXB     AS INTEGER  ' Last bottom X coordinate of mouse cursor
       SplitXT     AS INTEGER  ' Last top X coordinate of mouse cursor
       OldX        AS INTEGER  ' Last Frame window client height
       OldY        AS INTEGER  ' Last Frame window client width
    END TYPE
    
    ' Globals
    GLOBAL g_hCurSplitH, g_hCurSplitV, hLib AS DWORD
    
    FUNCTION WINMAIN(BYVAL hInstance AS LONG, BYVAL hPrevInstance AS LONG, BYVAL lpszCmdLine AS ASCIIZ PTR, BYVAL nCmdShow AS LONG) AS LONG
       LOCAL Msg AS tagMSG, wcl AS WNDCLASSEX, szWinName AS ASCIIZ * 20
    
       hLib = LoadLibrary("SCILEXER.DLL")  ' Generally not necessary, we do it anyway
    
       szWinName         = "MyWinDemo"
       ' Register the Main application window class (note what is NOT being used)
       wcl.cbSize        = sizeof(WNDCLASSEX)
       wcl.hInstance     = hInstance
       wcl.lpszClassName = varptr(szWinName)
       wcl.lpfnWndProc   = codeptr(WndProc)
       wcl.hIcon         = LoadIcon(hInstance, BYVAL %IDI_APPLICATION)
       RegisterClassEx wcl
    
       ' Frame window
       CreateWindow szWinName, "Split Bar Demo", %WS_OVERLAPPEDWINDOW OR %WS_VISIBLE,_
                300, 300, 410, 340, %NULL, %NULL, hInstance, BYVAL %NULL
    
       'g_hCurSplitH = LoadImage(BYVAL 0, "Ho_split.cur", %IMAGE_CURSOR, 32, 32, %LR_LOADFROMFILE)
       'g_hCurSplitV = LoadImage(BYVAL 0, "Vt_split.cur", %IMAGE_CURSOR, 32, 32, %LR_LOADFROMFILE)
       ' Use these instead, if you don't have access to the custom cursors above
       g_hCurSplitH = LoadCursor(BYVAL 0, BYVAL %IDC_SIZENS)
       g_hCurSplitV = LoadCursor(BYVAL 0, BYVAL %IDC_SIZEWE)
    
       WHILE GetMessage(Msg, %NULL, 0, 0)
          IF IsDialogMessage(msg.hwnd, Msg) THEN '...we avoid having to subclass
             IF Msg.Message = %WM_KEYUP THEN     ' Eat <ctrl> + <SPC> keys for our use
                IF Msg.wParam = &H20 AND (GetKeyState(%VK_LCONTROL) OR GetKeyState(%VK_RCONTROL)) < 0 THEN _
                   SendMessage GetParent(msg.hwnd), %WM_USER+600, 0, 0
             END IF
          ELSE
             TranslateMessage Msg    ' Do key conversions
             DispatchMessage Msg     ' Send messages to WndProc
          END IF
       WEND
    
       FUNCTION = Msg.wParam         ' Return app's exit code
    End Function
    '----------------------------------------------------------------------------------
    
    FUNCTION WndProc(BYVAL hWnd AS DWORD, BYVAL Msg AS DWORD, BYVAL wParam AS DWORD, BYVAL lParam AS LONG) AS LONG
       LOCAL wi AS SplitWinInfoStruct PTR, pt AS POINT, ps AS PAINTSTRUCT
       STATIC rc, rc2 AS RECT, Ysplit AS LONG ' THESE MUST BE STATIC, OR WM_MOUSEDOWN WILL FAIL!!!
    
       ' Get saved frame window data pointer
       wi = GetWindowLong(hWnd, %GWL_USERDATA)
    
       Select Case Msg
       Case %WM_CREATE
          ' Attach pointer to splitter information structure to frame window
          wi = HeapAlloc(GetProcessHeap, %HEAP_ZERO_MEMORY, SIZEOF(@wi))
          IF wi THEN
             SetWindowLong hWnd, %GWL_USERDATA, wi  ' Store the pointer for later use
          ELSE
             FUNCTION = %TRUE : EXIT FUNCTION       ' Failed to allocate memory
          END IF
          InitScintillas hWnd, @wi
          
       CASE %WM_PAINT
          BeginPaint hWnd, ps
             UpdFrClient hWnd, %FALSE, @wi ' The split bars are included in any painting
          EndPaint hWnd, ps
          AssignFocus @wi
          
       Case %WM_SIZE
          IF wParam <> %SIZE_MINIMIZED THEN
             IF @wi.SplitY THEN   ' Scale split windows as constant % of frame size
                @wi.SplitY = MulDiv(HI(WORD, lParam), @wi.SplitY, @wi.OldY)
                IF @wi.SplitXT THEN @wi.SplitXT = MulDiv(LO(WORD, lParam), @wi.SplitXT, @wi.OldX)
             END IF
             
             IF @wi.SplitXB THEN @wi.SplitXB = MulDiv(LO(WORD, lParam), @wi.SplitXB, @wi.OldX)
             @wi.OldY = HI(WORD, lParam) : @wi.OldX = LO(WORD, lParam)
             SetRect rc, 0, 0, @wi.OldX, @wi.OldY
             UpdFrClient hWnd, %TRUE, @wi
             ' An MDI app must process DefMDIChildProc or maximized child will not apply menu adjustments
             FUNCTION = %TRUE
          END IF
          
       CASE %WM_USER + 600
          ' Tabs between visible view controls (<ctrl> + <SPC>)
          LOCAL phWnds AS DWORD PTR, i, k AS LONG
          phWnds   = VARPTR(@wi.hBotR)
          
          FOR i = 0 TO 3
             IF @phWnds[i] = @wi.hFocus THEN  '...get index to control w/focus
                FOR k = i + 1 TO i + 3        ' Get index to next visible control
                   i = k mod 4                ' OK to "borrow" (i) here
                   IF IsWindowVisible(@phWnds[i]) THEN SetFocus @phWnds[i] : EXIT FOR
                next
                EXIT FOR
             END IF
          NEXT
          
       CASE %WM_COMMAND
          IF HI(WORD, wParam) = %SCEN_SETFOCUS THEN @wi.hFocus = lParam
          FUNCTION = %TRUE
          
       Case %WM_SETFOCUS
          SetFocus @wi.hFocus
          FUNCTION = %TRUE
          
       Case %WM_SETCURSOR
          ' If mouse is over scrollbars, we show the hand cursor to keep the user entertained :)
          SELECT CASE wParam
          CASE @wi.hFocus
             IF lo(word, lParam) = %HTHSCROLL OR lo(word, lParam) = %HTVSCROLL THEN _
                SetCursor LoadCursor(%NULL, BYVAL %IDC_HAND)
             FUNCTION = %TRUE
          CASE ELSE   ' ...allow DefWindowProc to determine which cursor to use
             FUNCTION = DefWindowProc(hWnd, Msg, wParam, lParam)
          END SELECT 'wParam
          
       Case %WM_MOUSEMOVE   ' Manage the split bars(s) activity
          pt.x = LO(WORD, lParam) : pt.y = HI(WORD, lParam) ' Get cursor X/Y coordinates
          IF GetCapture() = hWnd THEN   ' We're moving a split bar
             ClipCursor rc2
             IF Ysplit THEN             '...it's the top one
                @wi.SplitY = pt.y       ' Split position = current Y coordinate of cursor
                ' Confine all split bar locations. If they encounter the non-client area,
                ' we deem their positions as exessive, and reset their positions back to home.
                IF pt.y < rc.nBottom - %SPLITSIZE AND pt.y => 0 THEN
                ELSE                   ' Disable V splitting - revert to unsplit view
                   @wi.SplitY = 0
                   ClipCursor BYVAL %NULL
                END IF
             ELSE                     ' It's a left split bar
                ' If we're below the horizontal split bar, save current X coordinate of cursor
                IF pt.y > @wi.SplitY THEN @wi.SplitXB = pt.x ELSE @wi.SplitXT = pt.x
                IF pt.x < rc.nRight - %SPLITSIZE AND pt.x => 0 THEN
                ELSE                 ' Disable H splitting - revert to unsplit view
                   IF pt.y > @wi.SplitY + %SPLITSIZE THEN @wi.SplitXB = 0 ELSE @wi.SplitXT = 0
                   ClipCursor BYVAL %NULL
                END IF
             END IF
             UpdFrClient hWnd, %TRUE, @wi
          ELSE
             ' When the mouse is not captured, we only get a WM_MOUSEMOVE message when the
             ' mouse is over the split bars (which is the frame window's client area).
             ' Show proper split cursor at this time. Windows will change it when we release
             ' the split bar.
             GetClientRect hWnd, rc
             SetRect rc2, rc.nLeft, @wi.SplitY, rc.nRight, @wi.SplitY + %SPLITSIZE
             IF PtInRect(rc2, pt) THEN
                SetCursor g_hCurSplitH
                Ysplit = %TRUE
             ELSE
                SetCursor g_hCurSplitV
                Ysplit = %FALSE
             END IF
          END IF
          FUNCTION = %TRUE
          
       Case %WM_LBUTTONDOWN
          IF GetCursor() = g_hCurSplitH OR GetCursor() = g_hCurSplitV THEN
             SetCapture hWnd
             IF Ysplit THEN '...the top split bar is being used
                pt.x = LO(WORD, lParam) : pt.y = @wi.SplitY
                ' "Lock" the x/y position of cursor to the top split bar
                SetRect rc2, pt.x, -1, pt.x + 1, rc.nBottom
             ELSE        '...determine which left split bar is being used
                pt.y = HI(WORD, lParam)
                pt.x = IIF&(pt.y > @wi.SplitY, @wi.SplitXB, @wi.SplitXT)
                ' "Lock" the x/y position of cursor to the selected left split bar
                SetRect rc2, -1, pt.y, rc.nRight, pt.y + 1
             END IF
             MapWindowPoints hWnd, %NULL, rc2, 2 ' Convert coordinate space
             ClientToScreen hWnd, pt             ' Convert pt to screen coordinates
             SetCursorPos pt.x, pt.y             ' Position cursor onto selected split bar
             FUNCTION = %TRUE
          END IF
          
       Case %WM_LBUTTONUP
          IF GetCapture() = hWnd THEN
             ReleaseCapture
             ClipCursor BYVAL %NULL
             FUNCTION = %TRUE
          END IF
          
       Case %WM_DESTROY
          ' Remove Scintilla controls
          IF wi Then FOR i = 3 To 0 STEP -1 : DestroyWindow GetDlgItem(hWnd, %IDC_EDIT1+i) : NEXT
          ' Free memory
          IF wi THEN HeapFree GetProcessHeap, %NULL, GetWindowLong(hWnd, %GWL_USERDATA)
          If hLib Then FreeLibrary hLib ' Free the Scintilla library
          PostQuitMessage 0
          
       CASE ELSE
          FUNCTION = DefWindowProc(hWnd, Msg, wParam, lParam)
       End Select 'Msg
    END FUNCTION
    '----------------------------------------------------------------------------------
    
    SUB UpdFrClient(BYVAL hWnd AS LONG, BYVAL fMove AS LONG, wi AS SplitWinInfoStruct)
       ' Sizes & positions all controls simultaneously
       LOCAL w, h AS LONG, hDwp, hDC AS DWORD, rc AS RECT
       STATIC lStyle AS LONG
    
       IF lStyle = 0 THEN lStyle = %SWP_NOZORDER OR %SWP_NOOWNERZORDER OR %SWP_SHOWWINDOW OR %SWP_NOACTIVATE OR %SWP_NOCOPYBITS
    
       GetClientRect hWnd, rc
       ' Get width & height of frame window client area
       w = rc.nRight : h = rc.nBottom
    
       IF fMove THEN
          ' Allocates memory for a multiple-window position structure and returns the handle to the structure.
          ' Specify the initial number of controls for which to store position information
          hDwp = BeginDeferWindowPos(4)
             IF wi.SplitY THEN       '...the top horizontal split bar is active
                hDwp = DeferWindowPos(hDwp, wi.hTopR, %NULL, wi.SplitXT + %SPLITSIZE, 0, w - (wi.SplitXT + %SPLITSIZE), wi.SplitY, lStyle)
                IF wi.SplitXT THEN  '...the top left vertical split bar is active
                   hDwp = DeferWindowPos(hDwp, wi.hTopL, %NULL, 0, 0, wi.SplitXT, wi.SplitY, lStyle)
                ELSE                '...it's at the home position, so hide the top left control
                   ShowWindow wi.hTopL, %SW_HIDE
                END IF
             ELSE  '...there is no viewable top, so hide top left & right controls
                ShowWindow wi.hTopR, %SW_HIDE
                ShowWindow wi.hTopL, %SW_HIDE
             END IF
             
             IF wi.SplitY < h - %SPLITSIZE THEN  ' Position our main control
                hDwp = DeferWindowPos(hDwp, wi.hBotR, %NULL, wi.SplitXB + %SPLITSIZE, wi.SplitY + %SPLITSIZE, w - (wi.SplitXB + %SPLITSIZE),_
                                  h - wi.SplitY - %SPLITSIZE, lStyle)
                IF wi.SplitXB THEN  '...the bottom left vertical split bar is active
                   hDwp = DeferWindowPos(hDwp, wi.hBotL, %NULL, 0, wi.SplitY + %SPLITSIZE, wi.SplitXB, h - wi.SplitY - %SPLITSIZE, lStyle)
                ELSE                '...it's at the home position, so hide the bottom left control
                   ShowWindow wi.hBotL, %SW_HIDE
                END IF
             END IF
          EndDeferWindowPos hDwp
          UpdateWindow hWnd
       END IF
    
       hDC = GetDC(hWnd)
          ' Draw the top horizontal split bar
          SetRect rc, 0, wi.SplitY, w, wi.SplitY + %SPLITSIZE
          DrawFrameControl hDC, rc, %DFC_BUTTON, %DFCS_BUTTONPUSH
          
          ' Draw the bottom left vertical split bar
          SetRect rc, wi.SplitXB, wi.SplitY + %SPLITSIZE, wi.SplitXB + %SPLITSIZE, h
          DrawFrameControl hDC, rc, %DFC_BUTTON, %DFCS_BUTTONPUSH
          
          IF wi.SplitY THEN '...draw the top left vertical split bar
             SetRect rc, wi.SplitXT, 0, wi.SplitXT + %SPLITSIZE, wi.SplitY
             DrawFrameControl hDC, rc, %DFC_BUTTON, %DFCS_BUTTONPUSH
          END IF
       ReleaseDC hWnd, hDC
    END SUB
    '-----------------------------------------------------------------------------------------
    
    Sub InitScintillas(hWnd AS LONG, wi AS SplitWinInfoStruct)
       Local txt As String, lStyle, pDoc, i As Long, phWnds AS DWORD PTR
    
       ' Using the WS_BORDER style on the controls better defines the split bars when active
       lStyle   = %WS_CHILD OR %WS_VISIBLE OR %WS_VSCROLL OR %WS_HSCROLL OR %ES_MULTILINE OR _
               %ES_AUTOHSCROLL OR %ES_AUTOVSCROLL OR %ES_NOHIDESEL OR %WS_BORDER
       phWnds   = VARPTR(wi.hBotR)
    
       ' Add 4 Scintilla controls to our frame window
       ' wi.hBotR is our MAIN control window (bottom right)
       ' wi.hBotL is our bottom left VIEW control window
       ' wi.hTopR is our top right VIEW control window
       ' wi.hTopL is our top left VIEW control window
       For i = 0 to 3
          ' Create 4 Scintilla controls obtaining the handles & Direct Pointers for them
          @phWnds[i] = CreateWindowEx(0, "Scintilla", "", lStyle, 0, 0, 0, 0, hWnd, %IDC_EDIT1+i, GetModuleHandle("") , BYVAL %NULL)
          ' While we're at it, store a 'direct' pointer for each control to use in direct messaging
          wi.pSciMsg(i) = SendMessage(@phWnds[i], %SCI_GETDIRECTPOINTER, 0, 0)
          ' We take advantage of direct messaging...
          SciMsg wi.pSciMsg(i), %SCI_SetMarginWidthN, 0, 25
          ' If Vista/Win7, use Direct Write for higher quality antialiased drawing
          ' If you don't care for the client area's "sparkling" when splitting, comment below out.
          'IF AfxGetWindowsVersion => 6 THEN
          '  SciMsg wi.pSciMsg(i), %SCI_SETBUFFEREDDRAW, 0, 0
          '  SciMsg wi.pSciMsg(i), %SC_TECHNOLOGY_DIRECTWRITE, 0, 0
          'END IF
       NEXT
    
       ' We add some text to the main control
       txt = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" & $CRLF
       For i = 2 to 100 : txt = txt & "This is line" & Str$(i) & $crlf : Next
       SciMsg wi.pSciMsg(0), %SCI_SetText, 0, StrPTR(txt)
    
    	' Tell Scintilla that the current state of the document is unmodified.
    	SciMsg wi.pSciMsg(0), %SCI_SETSAVEPOINT, 0, 0
    
       ' Get the document pointer from our main control...
       pDoc = SciMsg(wi.pSciMsg(0), %SCI_GetDocPointer, 0, 0)
       ' & assign it to the remaining 3 edit controls - they will now share the SAME text
       IF pDoc THEN For i = 1 to 3 : SciMsg wi.pSciMsg(i), %SCI_SetDocPointer, 0, pDoc : NEXT
    
       wi.hFocus = wi.hBotR ' Main control gets the focus at startup
    End Sub
    '----------------------------------------------------------------------------------
    
    Sub AssignFocus(wi As SplitWinInfoStruct)
       ' Set focus to an appropriate control if control w/focus is no longer shown
       LOCAL phWnds AS DWORD PTR, i AS LONG
       phWnds   = VARPTR(wi.hBotR)
    
       FOR i = 3 TO 0 STEP -1
          IF IsWindowVisible(@phWnds[i]) THEN SetFocus @phWnds[i] : EXIT FOR
       NEXT
    END SUB

    Code:
    ' This provides what I beleive to be the basic raw code needed to provide
    ' an alternate 4-way split window mechanism for Scintilla by using scrollbar buttons.
    ' (Uses Jose's headers and the most current version of the scilexer.dll (v3.5.1) & PB v10.4)
    #Compile Exe
    #Dim All
    #INCLUDE ONCE "Scintilla.inc"
    #RESOURCE MANIFEST, 1, "XPTheme.xml"   ' Located in PB's samples directory
    ' The following cursors can be found in Jose's SED 2.xx source code.
    ' Otherwise, substitute one of the stock windoze cursors (see WINMAIN) & comment out the below:
    '#RESOURCE ICON, APPICON, "Ho_split.cur"
    '#RESOURCE ICON, APPICON, "Vt_split.cur"
    
    ' Identifiers
    ENUM Identifiers SINGULAR
       IDC_EDIT1   = 3110   ' Edit control main (bottom Right)
       IDC_EDIT2            ' Edit control (bottom Left)
       IDC_EDIT3            ' Edit control (Top Right)
       IDC_EDIT4            ' Edit control (Top Left)
       IDC_SIZEGRIP
       IDC_SIZEBOX
       IDC_SCROLLMV         ' Main control vertical scrollbar
       IDC_SCROLLMH         ' Main control horizontal scrollbar
       IDC_SCROLLTH         ' Top right control horizontal scrollbar
       SPLITSIZE   = 5      ' Width/Height of the split buttons
    END ENUM
    
    ' *** For Splitting use ***
       'hBotR=ScihWnds(0)      = Handle of bottom right Sintilla control
       'hBotL=ScihWnds(1)      = Handle of bottom left Sintilla control
       'hTopR=ScihWnds(2)      = Handle of top right Sintilla control
       'hTopL=ScihWnds(3)      = Handle of top left Sintilla control
    TYPE SplitWinInfoStruct
       ScihWnds(3) AS DWORD    ' Scintilla control handles
       pSciMsg(3)  AS DWORD    ' Scintilla control's Direct Message pointers
       hFocus      AS DWORD    ' Handle of Sintilla control with focus
       SplitY      AS INTEGER  ' Last Y coordinate of mouse cursor
       SplitXB     AS INTEGER  ' Last bottom X coordinate of mouse cursor
       SplitXT     AS INTEGER  ' Last top X coordinate of mouse cursor
       OldX        AS INTEGER  ' Last Frame window client height
       OldY        AS INTEGER  ' Last Frame window client width
       sbH         AS WORD     ' Height of arrow bitmap on H scrollbar
       sbW         AS WORD     ' Width of arrow bitmap on V scrollbar
       hGrip       AS DWORD    ' Handle of sizegrip
       hBox        AS DWORD    ' Handle of sizebox
       ViewSz(1)   AS POINT    ' Controls with substitute scrollbars client sizes
    END TYPE
    
    TYPE ScrollInfoStruc
       hSB(2)   AS DWORD       ' Scrollbar handle array
       sci(2)   AS SCROLLINFO  ' Scrollbar parameters array
       Mgn      AS WORD        ' Tot margin widths. Needed to determine correct nPage vals for H scrollbars
       R_C      AS POINTAPI    ' Row/Column positions (not used in this example)
    END TYPE
    
    ' Globals
    GLOBAL g_hCurSplitH, g_hCurSplitV AS DWORD, si AS ScrollInfoStruc
    
    MACRO ods = OutputDebugString
    
    
    FUNCTION WINMAIN(BYVAL hInstance AS LONG, BYVAL hPrevInstance AS LONG, BYVAL lpszCmdLine AS ASCIIZ PTR, BYVAL nCmdShow AS LONG) AS LONG
       LOCal Msg AS tagMSG, wcl AS WNDCLASSEX, szWinName AS ASCIIZ * 20
    
       szWinName         = "MyWinDemo"
       ' Register the Main application window class (note what is NOT being used)
       wcl.cbSize        = sizeof(WNDCLASSEX)
       wcl.hInstance     = hInstance
       wcl.lpszClassName = varptr(szWinName)
       wcl.lpfnWndProc   = codeptr(WndProc)
       wcl.hIcon         = LoadIcon(hInstance, BYVAL %IDI_APPLICATION)
       RegisterClassEx wcl
    
       'g_hCurSplitH = LoadImage(hInstance, "Ho_split.cur", %IMAGE_CURSOR, 32, 32, %LR_LOADFROMFILE)
       'g_hCurSplitV = LoadImage(hInstance, "Vt_split.cur", %IMAGE_CURSOR, 32, 32, %LR_LOADFROMFILE)
       ' Use these instead, if we don't have access to the custom cursors above
       g_hCurSplitH = LoadCursor(BYVAL 0, BYVAL %IDC_SIZENS)
       g_hCurSplitV = LoadCursor(BYVAL 0, BYVAL %IDC_SIZEWE)
    
       ' Frame window
       CreateWindow szWinName, "Scrollbar Split Button Demo", %WS_OVERLAPPEDWINDOW OR %WS_VISIBLE,_
                300, 300, 410, 340, %NULL, %NULL, hInstance, BYVAL %NULL
    
       WHILE GetMessage(Msg, %NULL, 0, 0)
          IF IsDialogMessage(msg.hwnd, Msg) THEN '...we avoid having to subclass
             IF Msg.Message = %WM_KEYUP THEN  ' Eat <ctrl> + <SPC> keys for our use
                IF Msg.wParam = &H20 AND (GetKeyState(%VK_LCONTROL) OR GetKeyState(%VK_RCONTROL)) < 0 THEN _
                   SendMessage GetParent(msg.hwnd), %WM_USER+600, 0, 0
             END IF
          ELSE
             TranslateMessage Msg ' Do key conversions
             DispatchMessage Msg  ' Send messages to WndProc
          END IF
       WEND
       FUNCTION = Msg.wParam      ' Return app's exit code
    End Function
    '----------------------------------------------------------------------------------
    
    FUNCTION WndProc(BYVAL hWnd AS DWORD, BYVAL Msg AS DWORD, BYVAL wParam AS DWORD, BYVAL lParam AS LONG) AS LONG
       LOCal wi AS SplitWinInfoStruct PTR, pt AS POINT, ps AS PAINTSTRUCT, i AS LONG
       STATIC rc, rc2 AS RECT, Ysplit AS LONG ' THESE MUST BE STATIC, OR WM_MOUSEDOWN WILL FAIL!!!
    
       ' Get frame window data
       wi = GetWindowLong(hWnd, %GWL_USERDATA)
    
       Select Case Msg
       Case %WM_CREATE
          ' Attach pointer to splitter information structure to frame window
          wi = HeapAlloc(GetProcessHeap, %HEAP_ZERO_MEMORY, SIZEOF(@wi))
          IF wi THEN
             SetWindowLong hWnd, %GWL_USERDATA, wi  ' Store the pointer for later use
          ELSE
             FUNCTION = %TRUE : EXIT FUNCTION       ' Failed to allocate memory
          END IF
    
          ' Get scrollbar dimensions
          @wi.sbH  = GetSystemMetrics(%SM_CYHSCROLL) ' Height of arrow bitmap on H scrollbar
          @wi.sbW  = GetSystemMetrics(%SM_CXVSCROLL) ' Width of arrow bitmap on V scrollbar
    
          ' Create our substitute scrollbars - storing their handles
          si.hSB(0) = CreateScrollBar(%SBS_VERT, hWnd, %IDC_SCROLLMV)
          si.hSB(1) = CreateScrollBar(%SBS_HORZ, hWnd, %IDC_SCROLLMH)
          si.hSB(2) = CreateScrollBar(%SBS_HORZ, hWnd, %IDC_SCROLLTH)
    
          ' Must be located here
          InitScintillas hWnd, @wi   ' Create our 4 scintilla controls
    
          ' Initialize scroll bar information
          ' TODO: Not the best place for this. Without the faked text, the line count will be 1.
          FOR i = 0 TO 2
             si.sci(i).cbSize  = sizeof(SCROLLINFO)
             si.sci(i).fMask   = %SIF_ALL
             IF i THEN si.sci(i).nMax = SciMsg(@wi.pSciMsg(0), %SCI_GETSCROLLWIDTH, 0, 0)  ' SCN_MODIFIED will set V scrollbar nMax
             SetScrollInfo si.hSB(i), %SB_CTL, si.sci(i), %FALSE
          NEXT 'i
    
          ' Create a SizeGrip control - storing its handle
          @wi.hGrip = CreateScrollBar(%SBS_SIZEGRIP, hWnd, %IDC_SIZEGRIP)
    
          ' Create a sizebox control - a leftover from 16 bit windoze, it's appearance
          ' is now the same as SBS_SIZEGRIP w/o producing sizing arrows
          @wi.hBox = CreateScrollBar(%SBS_SIZEBOX, hWnd, %IDC_SIZEBOX)
          FUNCTION = %TRUE
    
       CASE %WM_PAINT
          BeginPaint hWnd, ps
             UpdFrClient hWnd, %FALSE, @wi ' The split buttons are included in any painting
          EndPaint hWnd, ps
          FUNCTION = %TRUE
    
       Case %WM_SIZE
          IF wParam <> %SIZE_MINIMIZED THEN
             IF @wi.SplitY THEN   ' Scale split windows as constant % of frame size
                @wi.SplitY = MulDiv(HI(WORD, lParam), @wi.SplitY, @wi.OldY)
                IF @wi.SplitXT THEN @wi.SplitXT = MulDiv(LO(WORD, lParam), @wi.SplitXT, @wi.OldX)
             END IF
    
             IF @wi.SplitXB THEN @wi.SplitXB = MulDiv(LO(WORD, lParam), @wi.SplitXB, @wi.OldX)
             @wi.OldY = HI(WORD, lParam) : @wi.OldX = LO(WORD, lParam)
             SetRect rc, 0, 0, @wi.OldX, @wi.OldY
             UpdFrClient hWnd, %TRUE, @wi
          END IF
          ' An MDI app must process DefMDIChildProc or maximized child will not apply menu adjustments
          FUNCTION = %TRUE
    
       CASE %WM_USER + 600
          ' Tabs between visible view controls (<ctrl> + <SPC>)
          LOCAL k AS LONG
          For i = 0 TO 3
             IF @wi.ScihWnds(i) = @wi.hFocus THEN   '...get index to control w/focus
                FOR k = i + 1 TO i + 3              ' Get index to next visible control
                   i = k mod 4                      ' OK to "borrow" (i) here
                   IF IsWindowVisible(@wi.ScihWnds(i)) THEN SetFocus @wi.ScihWnds(i) : EXIT FOR
                next
                EXIT FOR
             END IF
          NEXT
          FUNCTION = %TRUE
    
       CASE %WM_COMMAND
          IF HI(WORD, wParam) = %SCEN_SETFOCUS THEN @wi.hFocus = lParam
          FUNCTION = %TRUE
    
       Case %WM_SETFOCUS
          SetFocus @wi.hFocus
          FUNCTION = %TRUE
    
       Case %WM_NOTIFY
          LoCal scn AS SCNotification PTR
          scn = lParam
    
          SELECT CASE @scn.hdr.Code
          CASE %SCN_MODIFIED
             IF wParam = %IDC_EDIT1 THEN   ' ...we'll only need to work with this control's V scrollbar
                ' If line(s) are added or deleted, we refresh our V scrollbar's nMax value & let SCN_UPDATEUI manage
                ' the thumb positioning
                ' thumbSize = (viewportSize / (maximum – minimum + viewportSize)) × trackLength
                IF (@scn.modificationType AND %SC_MOD_INSERTTEXT) or (@scn.modificationType AND %SC_MOD_DELETETEXT) THEN
                   si.sci(0).nMax = SciMsg(@wi.pSciMsg(0), %SCI_GETLINECOUNT, 0, 0) - 1    '*** NTM ***
                   si.sci(0).nPos = SciMsg(@wi.pSciMsg(0), %SCI_GETFIRSTVISIBLELINE, 0, 0) '*** NTM ***
                   SetScrollInfo si.hSB(0), %SB_CTL, si.sci(0), %TRUE ' Update the V scrollbar data
                END IF
             END IF
             FUNCTION = %TRUE
    
          CASE %SCN_UPDATEUI
             LOCal isVH AS LONG
    
             IF wParam = %IDC_EDIT1 OR wParam = %IDC_EDIT3 THEN
                ' This code will position our substitute scrollbar thumbs only when the caret position crosses a page boundry via keyboard input
                IF (@scn.updated AND %SC_UPDATE_SELECTION AND @scn.updated > %SC_UPDATE_SELECTION) THEN
                   ' Determine which of our substitute scrollbars is being used
                   isVH = IIF&((@scn.updated AND %SC_UPDATE_V_SCROLL), 0, 1) + IIF&(wParam = %IDC_EDIT3, 1, 0)
                   si.sci(isVH).nPos = IIF&(isVH, SciMsg(@wi.pSciMsg(IIF&(isVH=2, isVH, 0)), %SCI_GETXOFFSET, 0, 0),_
                                  SciMsg(@wi.pSciMsg(0), %SCI_GETFIRSTVISIBLELINE, 0, 0))
                   SetScrollInfo si.hSB(isVH), %SB_CTL, si.sci(isVH), %TRUE
                END IF
    
                SELECT CASE @scn.updated
                CASE %SC_UPDATE_V_SCROLL
                   ' If there's only a V scroll update w/no selection update, then mousewheel was used - adj thumb pos.
                   ' Scintilla eats the WM_VSCROLL msg from a mousewheel so we never get one to process. We do it here.
                   si.sci(0).nPos = SciMsg(@wi.pSciMsg(0), %SCI_GETFIRSTVISIBLELINE, 0, 0)
                   SetScrollInfo si.hSB(0), %SB_CTL, si.sci(0), %TRUE ' We only update our substitute V scrollbar data
    
                CASE %SC_UPDATE_H_SCROLL
                   ' Ensure that the 2 substitute H scrollbar thumb positions will match the position of Sintilla's (if they were used).
                   ' Scintilla ignores the mousewheel H scroll msg (which passes it on to us via SB_THUMBTRACK)
                   isVH = 1 + IIF&(wParam = %IDC_EDIT3, 1, 0)   ' Determine which of our substitute H scrollbars is being used
                   si.sci(isVH).nPos = SciMsg(@wi.pSciMsg(IIF&(isVH=2, isVH, 0)), %SCI_GETXOFFSET, 0, 0)
                   SetScrollInfo si.hSB(isVH), %SB_CTL, si.sci(isVH), %TRUE
                END SELECT '@scn.updated
             END IF
             FUNCTION = %TRUE
    
          CASE %SCN_SAVEPOINTREACHED
             ' When we get this, we disable Sintilla's undo context menu item
             IF SciMsg(@wi.pSciMsg(0), %SCI_CANUNDO, 0, 0) THEN SciMsg @wi.pSciMsg(0), %SCI_EMPTYUNDOBUFFER, 0, 0
    
          'CASE %SCN_SAVEPOINTLEFT   ' Not needed for this example
          END SELECT '@scn.hdr.Code
          FUNCTION = %TRUE
    
       Case %WM_SETCURSOR
          ' If mouse is over scrollbars, we show the hand cursor to keep the user entertained :)
          SELECT CASE wParam
          CASE @wi.hFocus, si.hSB(0), si.hSB(1), si.hSB(2)
             SetCursor LoadCursor(%NULL, BYVAL %IDC_HAND)
             FUNCTION = %TRUE
          CASE ELSE   '...allow DefWindowProc to determine which cursor to use
             FUNCTION = DefWindowProc(hWnd, Msg, wParam, lParam)
          END SELECT 'wParam
    
       Case %WM_MOUSEMOVE   ' Manage the split button(s) activity
          pt.x = LO(WORD, lParam) : pt.y = HI(WORD, lParam) ' Cursor X/Y coordinates
          IF GetCapture() = hWnd THEN   '...we're moving a split button
             ClipCursor rc2
             IF Ysplit THEN             '...it's the vertcal scrollbar button
                @wi.SplitY = pt.y       ' Set split position = current Y coordinate of cursor
                ' Confine all split button locations. If it encounters another scrollbar,
                ' we deem its position as exessive, and reset its position back to home.
                IF pt.y < rc.nBottom - @wi.sbH AND pt.y => 0 THEN
                ELSE                    ' Disable V splitting - revert to unsplit view
                   @wi.SplitY = 0
                   ClipCursor BYVAL %NULL
                END IF
             ELSE                       ' It's a horizontal scrollbar split button
                ' If we're below the upper horizontal scrollbar, save current X coordinate of cursor
                IF pt.y > @wi.SplitY THEN @wi.SplitXB = pt.x ELSE @wi.SplitXT = pt.x
                IF pt.x < rc.nRight - @wi.sbW AND pt.x => 0 THEN
                'IF pt.x < rc.nRight AND pt.x => 0 THEN
                ELSE                    ' Disable H splitting - revert to unsplit view
                   IF pt.y > @wi.SplitY + @wi.sbH THEN @wi.SplitXB = 0 ELSE @wi.SplitXT = 0
                   ClipCursor BYVAL %NULL
                END IF
             END IF
             UpdFrClient hWnd, %TRUE, @wi
          ELSE
             ' When the mouse is not captured, we only get a WM_MOUSEMOVE message when the
             ' mouse is over the split buttons (which is the frame window's client area).
             ' Show proper split cursor at this time. Windoze will change it when we move
             ' off the split button.
             GetClientRect hWnd, rc
             SetRect rc2, rc.nLeft, @wi.SplitY, rc.nRight, @wi.SplitY + %SPLITSIZE
             IF PtInRect(rc2, pt) THEN
                SetCursor g_hCurSplitH
                Ysplit = %TRUE
             ELSE
                SetCursor g_hCurSplitV
                Ysplit = %FALSE
             END IF
          END IF
          FUNCTION = %TRUE
    
       Case %WM_LBUTTONDOWN
          IF GetCursor() = g_hCurSplitH OR GetCursor() = g_hCurSplitV THEN
             SetCapture hWnd
             GetClientRect hWnd, rc
             IF Ysplit THEN '... V scrollbar split button is being used
                pt.x = LO(WORD, lParam) : pt.y = @wi.SplitY
                ' "Lock" the x/y position of cursor to the V scrollbar split button
                SetRect rc2, pt.x, -1, pt.x + 1, rc.nBottom
             ELSE
                pt.y = HI(WORD, lParam)
                ' Determine which H scrollbar split button is being used
                pt.x = IIF&(pt.y > @wi.SplitY, @wi.SplitXB, @wi.SplitXT)
                ' "Lock" the x/y position of cursor to the selected H scrollbar split button
                SetRect rc2, -1, pt.y, rc.nRight, pt.y + 1
             END IF
             MapWindowPoints hWnd, %NULL, rc2, 2 ' Convert coordinate space
             ClientToScreen hWnd, pt             ' Convert pt to screen coordinates
             SetCursorPos pt.x, pt.y             ' Position cursor onto selected split button
             FUNCTION = %TRUE
          END IF
    
       Case %WM_LBUTTONUP
          IF GetCapture() = hWnd THEN
             ReleaseCapture
             ClipCursor BYVAL %NULL
             AssignFocus @wi
             FUNCTION = %TRUE
          END IF
    
          '-------------------------------------------------------------------------------
          '                        Scrollbar Handler
          '-------------------------------------------------------------------------------
       CASE %WM_VSCROLL, %WM_HSCROLL
          ' This code is structured to have the substitute scrollbars emulate the Sintilla's as closely as possible.
          ' Since Sintilla uses a fixed default nMax value of 2000 for the H scrollbar, all H scroll actions must
          ' be properly scaled.
          ' Scrollbars si.sci(0) and si.sci(1) are assigned to the main control: hBotR/wi.ScihWnds(0)/wi.pSciMsg(0)
          ' Scrollbar si.sci(2) is assigned to control: hTopR/wi.ScihWnds(2)/wi.pSciMsg(2)
          LOCal OldPos,_       ' Previous scroll position
                lParm,_        ' Message parameters
                wParm AS LONG
    
          ' Determine which of the substitute scrollbars is being used
          isVH  = IIF&((Msg = %WM_VSCROLL), 0, 1) + IIF&(lParam = si.hSB(2), 1, 0)
          oldPos   = si.sci(isVH).nPos
    
          SELECT CASE LO(WORD, wParam)
          CASE %SB_THUMBTRACK
             ' Unlike Scintilla's, our substitute H scrollbars WILL respond to mousewheel H scrolling
             GetScrollInfo si.hSB(isVH), %SB_CTL, si.sci(isVH)  ' Always get the 32 bit position value
             wParm = si.sci(isVH).nPos - oldpos
             IF ABS(wParm) = 2 THEN GOTO DoScroll   ' SB_THUMBTRACK from mousewheel
             si.sci(isVH).nPos = si.sci(isVH).nTrackPos
    
          CASE %SB_LINEDOWN '%SB_LINERIGHT
             IF isVH THEN   ' H scrolling
                si.sci(isVH).nPos += 20
             ELSE           ' V scrolling
                INCR si.sci(isVH).nPos
             END IF
    
          CASE %SB_LINEUP '%SB_LINELEFT
             IF isVH THEN
                si.sci(isVH).nPos -= 20
             ELSE
                DECR si.sci(isVH).nPos
             END IF
    
          CASE %SB_PAGEDOWN '%SB_PAGERIGHT
             IF isVH THEN
                si.sci(isVH).nPos += MulDiv(si.sci(isVH).nPage, 2, 3)
             ELSE
                si.sci(isVH).nPos += si.sci(isVH).nPage - 1  '*** NTM ***
             END IF
    
          CASE %SB_PAGEUP '%SB_PAGELEFT
             IF isVH THEN
                si.sci(isVH).nPos -= MulDiv(si.sci(isVH).nPage, 2, 3)
             ELSE
                si.sci(isVH).nPos -= si.sci(isVH).nPage - 1  '*** NTM ***
             END IF
    
          CASE ELSE : Exit Function
          END SELECT 'LO(WORD, wParam)
          ' If the current position hasn't changed, do nothing.
          IF si.sci(isVH).nPos = oldPos THEN FUNCTION = %TRUE : EXIT FUNCTION
    
          ' Don't exceed range boundries
          si.sci(isVH).nPos = MAX&(si.sci(isVH).nMin, MIN&(si.sci(isVH).nPos, si.sci(isVH).nMax - si.sci(isVH).nPage + 1))  '*** NTM ***
    
          IF isVH THEN
             wParm = (si.sci(isVH).nPos - oldpos) / 4  ' Amount/direction to H scroll
          ELSE
             lParm = si.sci(isVH).nPos - oldPos        ' Amount/direction to V scroll
          END IF
    
          DoScroll:
          ' Scroll Sintilla's client area of either the main window or view window 2
          SciMsg @wi.pSciMsg(IIF&(isVH=2, isVH, 0)), %SCI_LINESCROLL, wParm, lParm
    
          ' Update scroll position
          SetScrollInfo si.hSB(isVH), %SB_CTL, si.sci(isVH), %TRUE
    
          ' If any offset in client's text position when H thumb at 0, scroll to start of line
          IF isVH AND si.sci(isVH).nPos <= 0 THEN SciMsg @wi.pSciMsg(IIF&(isVH=2, isVH, 0)), %SCI_LINESCROLL, -oldPos, 0
    
       CASE %WM_DESTROY
          ' Remove Scintilla controls
          IF wi Then FOR i = 3 To 0 STEP -1 : DestroyWindow @wi.ScihWnds(i) : NEXT
          ' Free memory
          IF wi Then HeapFree GetProcessHeap, %Null, GetWindowLong(hWnd, %GWL_USERDATA)
          PostQuitMessage 0
    
       CASE ELSE
          FUNCTION = DefWindowProc(hWnd, Msg, wParam, lParam)
       END SELECT 'Msg
    END FUNCTION
    '----------------------------------------------------------------------------------
    
    SUB UpdFrClient(BYVAL hWnd AS LONG, BYVAL fMove AS LONG, wi AS SplitWinInfoStruct)
       ' Sizes & positions all controls simultaneously
       LOCAL w, h, w2, h2 AS LONG, hDwp, hDC AS DWORD, rc AS RECT
       STATIC lStyle AS LONG
    
       IF lStyle = 0 THEN lStyle = %SWP_NOZORDER OR %SWP_NOOWNERZORDER OR %SWP_SHOWWINDOW OR %SWP_NOACTIVATE OR %SWP_NOCOPYBITS
    
       ' Get width & height of frame window client area
       GetClientRect hWnd, rc
       w2 = rc.nRight : h2 = rc.nBottom
       w = w2-wi.sbW  ' Any 'w' expression would have wi.sbW subtracted from it
       h = h2-wi.sbH  ' Any 'h' expression would have wi.sbH subtracted from it
    
       IF fMove THEN
          ' Allocates memory for a multiple-window position structure and returns the handle to the structure.
          ' Specify the initial number of controls for which to store position information
          ' Can be: 1-4 view windows, 2-3 scrollbars, 1 SizeGrip, 1 Sizebox
          hDwp = BeginDeferWindowPos(IIF&(wi.SplitY, 9, 5))
             IF wi.SplitY THEN  '  '...the vertical scrollbar split button is active
                wi.ViewSz(1).x = w - wi.SplitXT : wi.ViewSz(1).y = wi.SplitY   ' Save new top view dimensions
                hDwp = DeferWindowPos(hDwp, wi.ScihWnds(2), 0, wi.SplitXT, 0, w2 - wi.SplitXT, wi.SplitY - wi.sbH, lStyle)
                hDwp = DeferWindowPos(hDwp, si.hSB(2), 0, wi.SplitXT + %SPLITSIZE, wi.SplitY-wi.sbH, w - %SPLITSIZE - wi.SplitXT, wi.sbH, lStyle)
                hDwp = DeferWindowPos(hDwp, wi.hBox, 0, w, wi.SplitY-wi.sbH, wi.sbW, wi.sbH, lStyle)   ' Position the sizebox
                IF wi.SplitXT THEN  '...the top horizontal scrollbar split button is active
                   hDwp = DeferWindowPos(hDwp, wi.ScihWnds(3), 0, 0, 0, wi.SplitXT, wi.SplitY, lStyle)
                ELSE  '...it's at the home position, so hide the top left control
                   ShowWindow wi.ScihWnds(3), %SW_HIDE
                END IF
             ELSE  '...there's no viewable top controls - so hide them, the top H scrollbar, & the sizebox
                ShowWindow wi.ScihWnds(2), %SW_HIDE
                ShowWindow wi.ScihWnds(3), %SW_HIDE
                ShowWindow si.hSB(2), %SW_HIDE
                ShowWindow wi.hBox, %SW_HIDE
             END IF
    
             IF wi.SplitY < h - %SPLITSIZE THEN  ' Position our main control
                wi.ViewSz(0).x = w - wi.SplitXB - %SPLITSIZE : wi.ViewSz(0).y = h - wi.SplitY - %SPLITSIZE   ' Save new bottom view dimensions
                hDwp = DeferWindowPos(hDwp, wi.ScihWnds(0), 0, wi.SplitXB, wi.SplitY, w - wi.SplitXB, h - wi.SplitY, lStyle)
                ' Position the main view scroll bars
                hDwp = DeferWindowPos(hDwp, si.hSB(0), 0, w, wi.SplitY + %SPLITSIZE, wi.sbW, h - wi.SplitY - %SPLITSIZE, lStyle)
                hDwp = DeferWindowPos(hDwp, si.hSB(1), 0, wi.SplitXB + %SPLITSIZE, h, w - %SPLITSIZE - wi.SplitXB, wi.sbH, lStyle)
                IF wi.SplitXB THEN  '...the bottom horizontal scrollbar split button is active
                   hDwp = DeferWindowPos(hDwp, wi.ScihWnds(1), 0, 0, wi.SplitY, wi.SplitXB, h2 - wi.SplitY, lStyle)
                ELSE               '...it's at the home position, so hide the bottom left control
                   ShowWindow wi.ScihWnds(1), %SW_HIDE
                END IF
             END IF
             hDwp = DeferWindowPos(hDwp, wi.hGrip, 0, w, h, wi.sbW, wi.sbH, lStyle)  ' Position the SizeGrip
          EndDeferWindowPos hDwp
          Upd_nPage wi
          UpdateWindow hWnd
       END IF
    
       hDC = GetDC(hWnd)
          ' Draw a split button above the main control's substitute vertical scrollbar unless it meets the H scrollbar
          IF wi.SplitY < (h-%SPLITSIZE) THEN
             SetRect rc, w,_                     ' Button left edge
                         wi.SplitY,_             ' Top = current split button location
                         rc.nRight,_             ' Button right edge (width)
                         wi.SplitY + %SPLITSIZE  ' Height of split button
             DrawFrameControl hDC, rc, %DFC_BUTTON, %DFCS_BUTTONPUSH
          END IF
    
          ' Draw a split button to the left of the main control's substitute horizontal scrollbar unless it meets the V scrollbar
          IF wi.SplitXB < (w-%SPLITSIZE) THEN
             SetRect rc, wi.SplitXB, h2, wi.SplitXB + %SPLITSIZE, h
             DrawFrameControl hDC, rc, %DFC_BUTTON, %DFCS_BUTTONPUSH
          END IF
    
          ' If visible, draw a split button to the left of the substitute upper horizontal scrollbar
          IF wi.SplitY THEN
             SetRect rc, wi.SplitXT, wi.SplitY - wi.sbH, wi.SplitXT + %SPLITSIZE, wi.SplitY
             DrawFrameControl hDC, rc, %DFC_BUTTON, %DFCS_BUTTONPUSH
          END IF
       ReleaseDC hWnd, hDC
    END SUB
    '-----------------------------------------------------------------------------------------
    
    Sub InitScintillas(hWnd AS LONG, wi AS SplitWinInfoStruct)
       Local txt As String, lStyle, pDoc, i As Long
    
       ' Using the WS_BORDER style on the controls better defines the split buttons when active
       lStyle   =  %WS_CHILD OR %WS_VISIBLE OR %WS_VSCROLL OR %WS_HSCROLL OR %ES_MULTILINE OR _
                %ES_AUTOHSCROLL OR %ES_AUTOVSCROLL OR %ES_NOHIDESEL OR %WS_BORDER
    
       ' Add 4 Scintilla controls to our frame window
       ' hBotR/wi.ScihWnds(0) is our MAIN control (bottom right)
       ' hBotL/wi.ScihWnds(1) is our bottom left VIEW control
       ' hTopR/wi.ScihWnds(2) is our top right VIEW control
       ' hTopL/wi.ScihWnds(3) is our top left VIEW control
       For i = 0 to 3
          ' Create 4 Scintilla controls obtaining the handles & direct pointers for them
          wi.ScihWnds(i) = CreateWindowEx(0, "Scintilla", "", lStyle, 0, 0, 0, 0, hWnd, %IDC_EDIT1+i, GetModuleHandle(""), BYVAL %NULL)
          ' While we're at it, store a 'direct' pointer for each control to use in direct messaging
          wi.pSciMsg(i) = SendMessage(wi.ScihWnds(i), %SCI_GETDIRECTPOINTER, 0, 0)
          ' We take advantage of direct messaging...
          SciMsg wi.pSciMsg(i), %SCI_SetMarginWidthN, 0, 25
          ' If Vista/Win7, use Direct Write for higher quality antialiased drawing
          ' If you don't care for the client area's "sparkling" when splitting, comment below out.
          'IF AfxGetWindowsVersion => 6 THEN
          '  SciMsg wi.pSciMsg(i), %SCI_SETBUFFEREDDRAW, 0, 0
          '  SciMsg wi.pSciMsg(i), %SC_TECHNOLOGY_DIRECTWRITE, 0, 0
          'END IF
       NEXT
    
       si.mgn = SciMsg(wi.pSciMsg(0), %SCI_GetMarginWidthN, 0, 0) + 16 + %SPLITSIZE  ' Includes Margin 1 default of 16
    
       ' Disable main scintilla control's scroll bars (wParam = 1 to enable)
       SciMsg wi.pSciMsg(0), %SCI_SETHSCROLLBAR, 0, 0 : SciMsg wi.pSciMsg(0), %SCI_SETVSCROLLBAR, 0, 0
    
       ' Disable top right scintilla control's horizontal scroll bar
       SciMsg wi.pSciMsg(2), %SCI_SETHSCROLLBAR, 0, 0
    
       ' We add some text to the main control
       txt = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" & $CRLF
       For i = 2 to 100 : txt = txt & "This is line" & Str$(i) & $crlf : Next
       SciMsg wi.pSciMsg(0), %SCI_SetText, 0, StrPTR(txt)
    
       ' Tell Scintilla that the current state of the document is unmodified.
       SciMsg wi.pSciMsg(0), %SCI_SETSAVEPOINT, 0, 0
    
       ' Get the document pointer from our main control...
       pDoc = SciMsg(wi.pSciMsg(0), %SCI_GETDOCPOINTER, 0, 0)
       ' & assign it to the remaining 3 edit controls - they will now share the SAME text
       IF pDoc THEN For i = 1 to 3 : SciMsg wi.pSciMsg(i), %SCI_SetDocPointer, 0, pDoc : NEXT
    
       wi.hFocus = wi.ScihWnds(0) ' Main control gets the focus at startup
    End Sub
    '----------------------------------------------------------------------------------
    
    FUNCTION CreateScrollBar(BYVAL dwStyle AS LONG, BYVAL hWndParent AS LONG, BYVAL lId AS LONG ) AS dword
       dwStyle = dwStyle OR %WS_CHILD
       function = CreateWindow( "SCROLLBAR", "", dwStyle, 0, 0, 0, 0, _
                      hWndParent, BYVAL lId, GetModuleHandle(""), BYVAL %NULL)
    END FUNCTION
    '----------------------------------------------------------------------------------
    
    SUB Upd_nPage(wi AS SplitWinInfoStruct)
       ' Called when Frame window size changes, and updates all 3 scrollbar's nPage values.
    
       ' Update vertical scroll bar page value
       si.sci(0).nPage = SciMsg(wi.pSciMsg(0), %SCI_LINESONSCREEN, 0, 0)
       SetScrollInfo si.hSB(0), %SB_CTL, si.sci(0), %TRUE
    
       ' Update bottom horizontal scroll bar page value
       si.sci(1).nPage = wi.ViewSz(0).x - si.mgn
       SetScrollInfo si.hSB(1), %SB_CTL, si.sci(1), %TRUE
    
       IF wi.SplitY THEN  ' ...update upper horizontal scroll bar page value
          si.sci(2).nPage = wi.ViewSz(1).x - si.mgn
          SetScrollInfo si.hSB(2), %SB_CTL, si.sci(2), %TRUE
       END IF
    END SUB
    '----------------------------------------------------------------------------------
    
    Sub AssignFocus(wi As SplitWinInfoStruct)
       ' Set focus to an appropriate control if control w/focus is no longer shown
       LOCAL i AS LONG
    
       FOR i = 3 TO 0 STEP -1
          IF IsWindowVisible(wi.ScihWnds(i)) THEN SetFocus wi.ScihWnds(i) : EXIT FOR
       NEXT
    END SUB
    '--------------------------------------------------------------------------------
    
    ' Helper function to obtain character count, the line number caret is in and caret position in that line
    'FUNCTION GetR_C(wi AS SplitWinInfoStruct, isVH AS LONG) AS LONG
    '  si.R_C.x = SciMsg(wi.pSciMsg(IIF&(isVH=2, isVH, 0)), %SCI_GETCURRENTPOS, 0, 0)
    '  si.R_C.y = SciMsg(wi.pSciMsg(isVH), %SCI_LINEFROMPOSITION, si.R_C.x, 0)
    '  ' Return x pos of caret in the line (collumn pos)
    '  FUNCTION = SciMsg(wi.pSciMsg(isVH), %SCI_GETCOLUMN, si.R_C.x, 0)
    'END FUNCTION
    '--------------------------------------------------------------------------------
    Last edited by Nick Melnick; 6 Dec 2014, 02:37 PM. Reason: Fixed V scroll bugs in scrollbar buttons example
    Nick

  • #2
    Thanks a lot for those perfect examples.
    thinBasic programming language
    Win10 64bit - 8GB Ram - i7 M620 2.67GHz - NVIDIA Quadro FX1800M 1GB

    Comment


    • #3
      BTW, where can I get Scintilla.inc ?

      Comment


      • #4
        > BTW, where can I get Scintilla.inc ?

        https://forum.powerbasic.com/forum/j...ers-iii-v-1-07
        Forum: http://www.jose.it-berater.org/smfforum/index.php

        Comment


        • #5
          Thanks so much Jose

          Comment

          Working...
          X