Artrm Kovalchuk
Artrm Kovalchuk

Reputation: 73

How to make scroll inside WC_TABCONTROL? - Winapi

I have a tabcontrol with a lot of content and want to add a scroll bar so you can scroll down and see all the content.

I made WC_TABCONTROL control and set there WS_VSCROLL. The scroll line shows up but it does not work and the content of the box is cut off.

I am looking for some easy way to make the scroll work, not just show up.

Upvotes: 0

Views: 516

Answers (1)

YangXiaoPo-MSFT
YangXiaoPo-MSFT

Reputation: 2130

As @Jonathan Potter and the linked blog said, I combined @Barmak Shemirani answer with How to Scroll Text to get the following code.

#include <windows.h>
#include <CommCtrl.h>
#include "resource.h"
#pragma comment(lib,"comctl32.lib")
#pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")

HINSTANCE g_hinst;

struct TData {
    HWND page1, page2, page3;
    HWND tab;
} data;

BOOL CALLBACK DialogPage(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;
    TEXTMETRIC tm;
    SCROLLINFO si;

    // These variables are required to display text. 
    static int xClient;     // width of client area 
    static int yClient;     // height of client area 
    static int xClientMax;  // maximum width of client area 

    static int xChar;       // horizontal scrolling unit 
    static int yChar;       // vertical scrolling unit 
    static int xUpper;      // average width of uppercase letters 

    static int xPos;        // current horizontal scrolling position 
    static int yPos;        // current vertical scrolling position 

    int i;                  // loop counter 
    int x, y;               // horizontal and vertical coordinates

    int FirstLine;          // first line in the invalidated area 
    int LastLine;           // last line in the invalidated area 
    HRESULT hr;
    size_t abcLength;        // length of an abc[] item 

    // Create an array of lines to display. 
#define LINES 28 
    static const TCHAR* abc[] = {
           TEXT("anteater"),  TEXT("bear"),      TEXT("cougar"),
           TEXT("dingo"),     TEXT("elephant"),  TEXT("falcon"),
           TEXT("gazelle"),   TEXT("hyena"),     TEXT("iguana"),
           TEXT("jackal"),    TEXT("kangaroo"),  TEXT("llama"),
           TEXT("moose"),     TEXT("newt"),      TEXT("octopus"),
           TEXT("penguin"),   TEXT("quail"),     TEXT("rat"),
           TEXT("squid"),     TEXT("tortoise"),  TEXT("urus"),
           TEXT("vole"),      TEXT("walrus"),    TEXT("xylophone"),
           TEXT("yak"),       TEXT("zebra"),
           TEXT("This line contains words, but no character. Go figure."),
           TEXT("")
    };

    switch (msg)
    {
    case WM_INITDIALOG:
        // Get the handle to the client area's device context. 
        hdc = GetDC(hwnd);

        // Extract font dimensions from the text metrics. 
        GetTextMetrics(hdc, &tm);
        xChar = tm.tmAveCharWidth;
        xUpper = (tm.tmPitchAndFamily & 1 ? 3 : 2) * xChar / 2;
        yChar = tm.tmHeight + tm.tmExternalLeading;

        // Free the device context. 
        ReleaseDC(hwnd, hdc);

        // Set an arbitrary maximum width for client area. 
        // (xClientMax is the sum of the widths of 48 average 
        // lowercase letters and 12 uppercase letters.) 
        xClientMax = 48 * xChar + 12 * xUpper;

        return TRUE;

    case WM_SIZE:

        // Retrieve the dimensions of the client area. 
        yClient = HIWORD(lParam);
        xClient = LOWORD(lParam);

        // Set the vertical scrolling range and page size
        si.cbSize = sizeof(si);
        si.fMask = SIF_RANGE | SIF_PAGE;
        si.nMin = 0;
        si.nMax = LINES - 1;
        si.nPage = yClient / yChar;
        SetScrollInfo(hwnd, SB_VERT, &si, TRUE);

        // Set the horizontal scrolling range and page size. 
        si.cbSize = sizeof(si);
        si.fMask = SIF_RANGE | SIF_PAGE;
        si.nMin = 0;
        si.nMax = 2 + xClientMax / xChar;
        si.nPage = xClient / xChar;
        SetScrollInfo(hwnd, SB_HORZ, &si, TRUE);

        return 0;
    case WM_HSCROLL:
        // Get all the vertial scroll bar information.
        si.cbSize = sizeof(si);
        si.fMask = SIF_ALL;

        // Save the position for comparison later on.
        GetScrollInfo(hwnd, SB_HORZ, &si);
        xPos = si.nPos;
        switch (LOWORD(wParam))
        {
            // User clicked the left arrow.
        case SB_LINELEFT:
            si.nPos -= 1;
            break;

            // User clicked the right arrow.
        case SB_LINERIGHT:
            si.nPos += 1;
            break;

            // User clicked the scroll bar shaft left of the scroll box.
        case SB_PAGELEFT:
            si.nPos -= si.nPage;
            break;

            // User clicked the scroll bar shaft right of the scroll box.
        case SB_PAGERIGHT:
            si.nPos += si.nPage;
            break;

            // User dragged the scroll box.
        case SB_THUMBTRACK:
            si.nPos = si.nTrackPos;
            break;

        default:
            break;
        }

        // Set the position and then retrieve it.  Due to adjustments
        // by Windows it may not be the same as the value set.
        si.fMask = SIF_POS;
        SetScrollInfo(hwnd, SB_HORZ, &si, TRUE);
        GetScrollInfo(hwnd, SB_HORZ, &si);

        // If the position has changed, scroll the window.
        if (si.nPos != xPos)
        {
            ScrollWindow(hwnd, xChar * (xPos - si.nPos), 0, NULL, NULL);
        }

        return 0;

    case WM_VSCROLL:
        // Get all the vertial scroll bar information.
        si.cbSize = sizeof(si);
        si.fMask = SIF_ALL;
        GetScrollInfo(hwnd, SB_VERT, &si);

        // Save the position for comparison later on.
        yPos = si.nPos;
        switch (LOWORD(wParam))
        {

            // User clicked the HOME keyboard key.
        case SB_TOP:
            si.nPos = si.nMin;
            break;

            // User clicked the END keyboard key.
        case SB_BOTTOM:
            si.nPos = si.nMax;
            break;

            // User clicked the top arrow.
        case SB_LINEUP:
            si.nPos -= 1;
            break;

            // User clicked the bottom arrow.
        case SB_LINEDOWN:
            si.nPos += 1;
            break;

            // User clicked the scroll bar shaft above the scroll box.
        case SB_PAGEUP:
            si.nPos -= si.nPage;
            break;

            // User clicked the scroll bar shaft below the scroll box.
        case SB_PAGEDOWN:
            si.nPos += si.nPage;
            break;

            // User dragged the scroll box.
        case SB_THUMBTRACK:
            si.nPos = si.nTrackPos;
            break;

        default:
            break;
        }

        // Set the position and then retrieve it.  Due to adjustments
        // by Windows it may not be the same as the value set.
        si.fMask = SIF_POS;
        SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
        GetScrollInfo(hwnd, SB_VERT, &si);

        // If the position has changed, scroll window and update it.
        if (si.nPos != yPos)
        {
            ScrollWindow(hwnd, 0, yChar * (yPos - si.nPos), NULL, NULL);
            UpdateWindow(hwnd);
        }

        return 0;

    case WM_PAINT:
        // Prepare the window for painting.
        hdc = BeginPaint(hwnd, &ps);

        // Get vertical scroll bar position.
        si.cbSize = sizeof(si);
        si.fMask = SIF_POS;
        GetScrollInfo(hwnd, SB_VERT, &si);
        yPos = si.nPos;

        // Get horizontal scroll bar position.
        GetScrollInfo(hwnd, SB_HORZ, &si);
        xPos = si.nPos;

        // Find painting limits.
        FirstLine = max(0, yPos + ps.rcPaint.top / yChar);
        LastLine = min(LINES - 1, yPos + ps.rcPaint.bottom / yChar);

        for (i = FirstLine; i <= LastLine; i++)
        {
            x = xChar * (1 - xPos);
            y = yChar * (i - yPos);

            // Note that "55" in the following depends on the 
            // maximum size of an abc[] item. Also, you must include
            // strsafe.h to use the StringCchLength function.
            hr = StringCchLength(abc[i], 55, &abcLength);
            if ((FAILED(hr)) | (abcLength == NULL))
            {
                //
                // TODO: write error handler
                //
            }

            // Write a line of text to the client area.
            TextOut(hdc, x, y, abc[i], abcLength);
        }

        // Indicate that painting is finished.
        EndPaint(hwnd, &ps);
        return TRUE;
    }
    return FALSE;
}

void OnSelChange() {
    int sel = TabCtrl_GetCurSel(data.tab);
    ShowWindow(data.page1, (sel == 0) ? SW_SHOW : SW_HIDE);
    ShowWindow(data.page2, (sel == 1) ? SW_SHOW : SW_HIDE);
}

BOOL CALLBACK DialogProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
{
    switch (msg) {
    case WM_INITDIALOG: {
        data.page1 = CreateDialog(g_hinst, MAKEINTRESOURCE(IDD_DIALOG2), hwnd, DialogPage);
        data.page2 = CreateDialog(g_hinst, MAKEINTRESOURCE(IDD_DIALOG3), hwnd, DialogPage);

        data.tab = GetDlgItem(hwnd, IDC_TAB1);
        if (data.tab)
        {
            TCITEM tci = { 0 };
            tci.mask = TCIF_TEXT;
            tci.pszText = (PTCHAR)L"Page1";
            TabCtrl_InsertItem(data.tab, 0, &tci);
            tci.pszText = (PTCHAR)L"Page2";
            TabCtrl_InsertItem(data.tab, 1, &tci);

            RECT rc;//find tab control's rectangle
            GetWindowRect(data.tab, &rc);
            POINT offset = { 0 };
            ScreenToClient(hwnd, &offset);
            OffsetRect(&rc, offset.x, offset.y); //convert to client coordinates
            rc.top += 50;
            SetWindowPos(data.page1, 0, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, SWP_HIDEWINDOW);
            SetWindowPos(data.page2, 0, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, SWP_HIDEWINDOW);

            OnSelChange();
        }

        break;
    }

    case WM_NOTIFY: {
        switch (((LPNMHDR)lp)->code) {
        case TCN_SELCHANGE:
            OnSelChange();
            break;
        }
    }
                  break;

    case WM_COMMAND:
        EndDialog(hwnd, wp);
        EndDialog(data.page1, wp);
        EndDialog(data.page2, wp);
        break;
    }

    return FALSE;
}

int WINAPI wWinMain(HINSTANCE hinst, HINSTANCE, LPWSTR, int)
{
    g_hinst = hinst;
    DialogBox(hinst, MAKEINTRESOURCE(IDD_DIALOG1), 0, DialogProc);
    return 0;
}

enter image description here

Upvotes: 1

Related Questions