6

INTRODUCTION AND RELEVANT INFORMATION:

I am trying to paint the image in my main window in its natural size. Currently I need to render EMF.

After browsing through Internet, I found this MSDN example.

PROBLEM:

I have given it a try, and found out it doesn't work well.

After user right clicks, as instructions say, desktop screenshot is properly painted in the main window.

However, when user resizes the window, paint artifacts occur like the ones on the image below. The below effect also occurs when user scrolls a little and then resizes the window.

Here is the image:

enter image description here

This is my first time trying to scroll an image, and using scrollbars, so I really have a hard time figuring out a solution.

QUESTIONS:

How to fix article code to remove this visual artifact?

Can you recommend another example, like tutorial/code example/etc that I can study?

Can you provide "verbal" instructions/guidelines that could help me?

To make your task even easier, here is the smallest code possible that reproduces the problem. Take note that I have simply copy/pasted the window procedure and added minimal code for program to create working demonstration:

#include <windows.h>

LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;
    SCROLLINFO si;

    // These variables are required by BitBlt. 
    static HDC hdcWin;           // window DC 
    static HDC hdcScreen;        // DC for entire screen 
    static HDC hdcScreenCompat;  // memory DC for screen 
    static HBITMAP hbmpCompat;   // bitmap handle to old DC 
    static BITMAP bmp;           // bitmap data structure 
    static BOOL fBlt;            // TRUE if BitBlt occurred 
    static BOOL fScroll;         // TRUE if scrolling occurred 
    static BOOL fSize;           // TRUE if fBlt & WM_SIZE 

    // These variables are required for horizontal scrolling. 
    static int xMinScroll;       // minimum horizontal scroll value 
    static int xCurrentScroll;   // current horizontal scroll value 
    static int xMaxScroll;       // maximum horizontal scroll value 

    // These variables are required for vertical scrolling. 
    static int yMinScroll;       // minimum vertical scroll value 
    static int yCurrentScroll;   // current vertical scroll value 
    static int yMaxScroll;       // maximum vertical scroll value 

    switch (uMsg)
    {
    case WM_CREATE:

        // Create a normal DC and a memory DC for the entire 
        // screen. The normal DC provides a snapshot of the 
        // screen contents. The memory DC keeps a copy of this 
        // snapshot in the associated bitmap. 
        hdcScreen = CreateDC(L"DISPLAY", (PCTSTR)NULL,
            (PCTSTR)NULL, (CONST DEVMODE *) NULL);
        hdcScreenCompat = CreateCompatibleDC(hdcScreen);

        // Retrieve the metrics for the bitmap associated with the 
        // regular device context. 
        bmp.bmBitsPixel =
            (BYTE)GetDeviceCaps(hdcScreen, BITSPIXEL);
        bmp.bmPlanes = (BYTE)GetDeviceCaps(hdcScreen, PLANES);
        bmp.bmWidth = GetDeviceCaps(hdcScreen, HORZRES);
        bmp.bmHeight = GetDeviceCaps(hdcScreen, VERTRES);

        // The width must be byte-aligned. 
        bmp.bmWidthBytes = ((bmp.bmWidth + 15) &~15) / 8;

        // Create a bitmap for the compatible DC. 
        hbmpCompat = CreateBitmap(bmp.bmWidth, bmp.bmHeight,
            bmp.bmPlanes, bmp.bmBitsPixel, (CONST VOID *) NULL);

        // Select the bitmap for the compatible DC. 
        SelectObject(hdcScreenCompat, hbmpCompat);

        // Initialize the flags. 
        fBlt = FALSE;
        fScroll = FALSE;
        fSize = FALSE;

        // Initialize the horizontal scrolling variables. 
        xMinScroll = 0;
        xCurrentScroll = 0;
        xMaxScroll = 0;

        // Initialize the vertical scrolling variables. 
        yMinScroll = 0;
        yCurrentScroll = 0;
        yMaxScroll = 0;

        break;

    case WM_SIZE:
    {
        int xNewSize;
        int yNewSize;

        xNewSize = LOWORD(lParam);
        yNewSize = HIWORD(lParam);

        if (fBlt)
            fSize = TRUE;

        // The horizontal scrolling range is defined by 
        // (bitmap_width) - (client_width). The current horizontal 
        // scroll value remains within the horizontal scrolling range. 
        xMaxScroll = max(bmp.bmWidth - xNewSize, 0);
        xCurrentScroll = min(xCurrentScroll, xMaxScroll);
        si.cbSize = sizeof(si);
        si.fMask = SIF_RANGE | SIF_PAGE | SIF_POS;
        si.nMin = xMinScroll;
        si.nMax = bmp.bmWidth;
        si.nPage = xNewSize;
        si.nPos = xCurrentScroll;
        SetScrollInfo(hwnd, SB_HORZ, &si, TRUE);

        // The vertical scrolling range is defined by 
        // (bitmap_height) - (client_height). The current vertical 
        // scroll value remains within the vertical scrolling range. 
        yMaxScroll = max(bmp.bmHeight - yNewSize, 0);
        yCurrentScroll = min(yCurrentScroll, yMaxScroll);
        si.cbSize = sizeof(si);
        si.fMask = SIF_RANGE | SIF_PAGE | SIF_POS;
        si.nMin = yMinScroll;
        si.nMax = bmp.bmHeight;
        si.nPage = yNewSize;
        si.nPos = yCurrentScroll;
        SetScrollInfo(hwnd, SB_VERT, &si, TRUE);

        break;
    }

    case WM_PAINT:
    {
        PRECT prect;

        hdc = BeginPaint(hwnd, &ps);

        // If the window has been resized and the user has 
        // captured the screen, use the following call to 
        // BitBlt to paint the window's client area. 
        if (fSize)
        {
            BitBlt(ps.hdc,
                0, 0,
                bmp.bmWidth, bmp.bmHeight,
                hdcScreenCompat,
                xCurrentScroll, yCurrentScroll,
                SRCCOPY);

            fSize = FALSE;
        }

        // If scrolling has occurred, use the following call to 
        // BitBlt to paint the invalid rectangle. 
        // 
        // The coordinates of this rectangle are specified in the 
        // RECT structure to which prect points. 
        // 
        // Note that it is necessary to increment the seventh 
        // argument (prect->left) by xCurrentScroll and the 
        // eighth argument (prect->top) by yCurrentScroll in 
        // order to map the correct pixels from the source bitmap. 
        if (fScroll)
        {
            prect = &ps.rcPaint;

            BitBlt(ps.hdc,
                prect->left, prect->top,
                (prect->right - prect->left),
                (prect->bottom - prect->top),
                hdcScreenCompat,
                prect->left + xCurrentScroll,
                prect->top + yCurrentScroll,
                SRCCOPY);

            fScroll = FALSE;
        }

        EndPaint(hwnd, &ps);

        break;
    }

    case WM_HSCROLL:
    {
        int xDelta;     // xDelta = new_pos - current_pos  
        int xNewPos;    // new position 
        int yDelta = 0;

        switch (LOWORD(wParam))
        {
            // User clicked the scroll bar shaft left of the scroll box. 
        case SB_PAGEUP:
            xNewPos = xCurrentScroll - 50;
            break;

            // User clicked the scroll bar shaft right of the scroll box. 
        case SB_PAGEDOWN:
            xNewPos = xCurrentScroll + 50;
            break;

            // User clicked the left arrow. 
        case SB_LINEUP:
            xNewPos = xCurrentScroll - 5;
            break;

            // User clicked the right arrow. 
        case SB_LINEDOWN:
            xNewPos = xCurrentScroll + 5;
            break;

            // User dragged the scroll box. 
        case SB_THUMBPOSITION:
            xNewPos = HIWORD(wParam);
            break;

        default:
            xNewPos = xCurrentScroll;
        }

        // New position must be between 0 and the screen width. 
        xNewPos = max(0, xNewPos);
        xNewPos = min(xMaxScroll, xNewPos);

        // If the current position does not change, do not scroll.
        if (xNewPos == xCurrentScroll)
            break;

        // Set the scroll flag to TRUE. 
        fScroll = TRUE;

        // Determine the amount scrolled (in pixels). 
        xDelta = xNewPos - xCurrentScroll;

        // Reset the current scroll position. 
        xCurrentScroll = xNewPos;

        // Scroll the window. (The system repaints most of the 
        // client area when ScrollWindowEx is called; however, it is 
        // necessary to call UpdateWindow in order to repaint the 
        // rectangle of pixels that were invalidated.) 
        ScrollWindowEx(hwnd, -xDelta, -yDelta, (CONST RECT *) NULL,
            (CONST RECT *) NULL, (HRGN)NULL, (PRECT)NULL,
            SW_INVALIDATE);
        UpdateWindow(hwnd);

        // Reset the scroll bar. 
        si.cbSize = sizeof(si);
        si.fMask = SIF_POS;
        si.nPos = xCurrentScroll;
        SetScrollInfo(hwnd, SB_HORZ, &si, TRUE);

        break;
    }

    case WM_VSCROLL:
    {
        int xDelta = 0;
        int yDelta;     // yDelta = new_pos - current_pos 
        int yNewPos;    // new position 

        switch (LOWORD(wParam))
        {
            // User clicked the scroll bar shaft above the scroll box. 
        case SB_PAGEUP:
            yNewPos = yCurrentScroll - 50;
            break;

            // User clicked the scroll bar shaft below the scroll box. 
        case SB_PAGEDOWN:
            yNewPos = yCurrentScroll + 50;
            break;

            // User clicked the top arrow. 
        case SB_LINEUP:
            yNewPos = yCurrentScroll - 5;
            break;

            // User clicked the bottom arrow. 
        case SB_LINEDOWN:
            yNewPos = yCurrentScroll + 5;
            break;

            // User dragged the scroll box. 
        case SB_THUMBPOSITION:
            yNewPos = HIWORD(wParam);
            break;

        default:
            yNewPos = yCurrentScroll;
        }

        // New position must be between 0 and the screen height. 
        yNewPos = max(0, yNewPos);
        yNewPos = min(yMaxScroll, yNewPos);

        // If the current position does not change, do not scroll.
        if (yNewPos == yCurrentScroll)
            break;

        // Set the scroll flag to TRUE. 
        fScroll = TRUE;

        // Determine the amount scrolled (in pixels). 
        yDelta = yNewPos - yCurrentScroll;

        // Reset the current scroll position. 
        yCurrentScroll = yNewPos;

        // Scroll the window. (The system repaints most of the 
        // client area when ScrollWindowEx is called; however, it is 
        // necessary to call UpdateWindow in order to repaint the 
        // rectangle of pixels that were invalidated.) 
        ScrollWindowEx(hwnd, -xDelta, -yDelta, (CONST RECT *) NULL,
            (CONST RECT *) NULL, (HRGN)NULL, (PRECT)NULL,
            SW_INVALIDATE);
        UpdateWindow(hwnd);

        // Reset the scroll bar. 
        si.cbSize = sizeof(si);
        si.fMask = SIF_POS;
        si.nPos = yCurrentScroll;
        SetScrollInfo(hwnd, SB_VERT, &si, TRUE);

        break;
    }

    case WM_RBUTTONDOWN:
    {
        // Get the compatible DC of the client area. 
        hdcWin = GetDC(hwnd);

        // Fill the client area to remove any existing contents. 
        RECT rect;
        GetClientRect(hwnd, &rect);
        FillRect(hdcWin, &rect, (HBRUSH)(COLOR_WINDOW + 1));

        // Copy the contents of the current screen 
        // into the compatible DC. 
        BitBlt(hdcScreenCompat, 0, 0, bmp.bmWidth,
            bmp.bmHeight, hdcScreen, 0, 0, SRCCOPY);

        // Copy the compatible DC to the client area.
        BitBlt(hdcWin, 0, 0, bmp.bmWidth, bmp.bmHeight,
            hdcScreenCompat, 0, 0, SRCCOPY);

        ReleaseDC(hwnd, hdcWin);
        fBlt = TRUE;
        break;
    }

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

// WinMain
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,
    int nCmdShow)
{
    WNDCLASSEX wc;
    HWND hwnd;
    MSG Msg;

    // register main window class
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = 0;
    wc.lpfnWndProc = WndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon(hInstance, IDI_APPLICATION);
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = GetSysColorBrush(COLOR_WINDOW);
    wc.lpszMenuName = NULL;
    wc.lpszClassName = L"Main_Window";
    wc.hIconSm = LoadIcon(hInstance, IDI_APPLICATION);

    if (!RegisterClassEx(&wc))
    {
        MessageBox(NULL, L"Window Registration Failed!", L"Error!",
            MB_ICONEXCLAMATION | MB_OK);

        return 0;
    }

    // create main window
    hwnd = CreateWindowEx(0, L"Main_Window", L"Scrollable map",
        WS_OVERLAPPEDWINDOW | WS_VSCROLL | WS_HSCROLL, 
        50, 50, 400, 400, NULL, NULL, hInstance, 0);

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    while (GetMessage(&Msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
    }

    return Msg.wParam;
}
AlwaysLearningNewStuff
  • 2,939
  • 3
  • 31
  • 84
  • 1
    To those who voted to close as off-topic because *Questions seeking debugging help ("why isn't this code working?") **must include the desired behavior, a specific problem or error** and **the shortest code necessary to reproduce it in the question itself**. Questions without a clear problem statement are not useful to other readers. See: How to create a Minimal, Complete, and Verifiable example.* Emphasis are mine: I have made the smallest code that demonstrates the problem. I have clearly pointed out error and have clearly described desired behavior. Please reconsider your close votes. – AlwaysLearningNewStuff Jun 22 '15 at 00:49
  • Your WM_PAINT handler should return 0, not call `DefWindowProc`. https://msdn.microsoft.com/en-us/library/windows/desktop/dd145213%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396 The same issue with `WM_HSCROLL`, `WM_VSCROLL` etc. It is highly important that you read the documentation for all of the messages you're processing -- returning the wrong value back to the Windows subsystem is a cause for a lot of errors and weird behavior. – PaulMcKenzie Jun 22 '15 at 01:18
  • @PaulMcKenzie: Not working, the same behavior persists. I have corrected all return values and no success. The problem must be something related to painting algorithm but I just can't find out what. Thank you for trying though... – AlwaysLearningNewStuff Jun 22 '15 at 01:22
  • It is more than a "try". It is a correction in your code that had to be made, regardless of whether it fixes your issue or not. Fixing the return codes would be part 1 of the actual fix. Also, there is problem a ton of other samples that show scrolling of a bitmap. Even old Win 3.x, code worked wrt scrolling a bitmap, so consider even old samples. – PaulMcKenzie Jun 22 '15 at 01:23
  • * It is a correction in your code* The code is not mine. I just try to learn how to scroll image, and am using linked article as starting point. *Even old Win 3.x, code worked wrt scrolling a bitmap, so consider even old samples.* Where can one find them? I couldn't even find modern examples in WinAPI and C/C++... – AlwaysLearningNewStuff Jun 22 '15 at 01:28
  • 1
    From your description it seems like the scrolling is a distraction - fundamentally isn't it just a repaint-on-resize issue? Try using `InvalidateRect` to force a redraw when the window is resized. – Jonathan Potter Jun 22 '15 at 02:16
  • *From your description it seems like the scrolling is a distraction - fundamentally isn't it just a repaint-on-resize issue?* Well, we can define it that way guess. This is my first time to meet such a task, so I was not able to fully grasp the issue. I have looked through Petzold's legendary book, and that article. None of them use `InvalidateRect()`, but I will give this a try. – AlwaysLearningNewStuff Jun 22 '15 at 02:32
  • @JonathanPotter: Well, horrible artifacts did disappear. Looking carefully through the Petzold book, I believe I found out why: In every Petzold example, window class has `CS_VREDRAW | CS_HREDRAW` style set. In the above code example I have omitted that style ( I was planning to return `1L` from `WM_ERASEBKGND` as well) so I can reduce flickering. It seems that adding that style , or doing as you said, fixes the problem. – AlwaysLearningNewStuff Jun 22 '15 at 02:44
  • @JonathanPotter: There is another problem that cropped up in my original code, which is a result of modification of the above sample. I believe I should ask for help in a separate question. After invalidating in `WM_SIZE` problem disappeared as you have suggested. If you post this as an answer I will upvote and accept. This might be usefull to others because it is easy to forget to invalidate once `CS_HREDRAW | CS_VREDRAW` styles are removed. Thanks again, best regards. – AlwaysLearningNewStuff Jun 22 '15 at 03:31
  • [ScrollCall](https://www.codeproject.com/Articles/5292776/ScrollCall) is a scrolling demo with code that might help. – Laurie Stearn Apr 06 '21 at 04:23

1 Answers1

5

From your description the problem seems to be simply that your window isn't being repainted when it's resized.

Either use InvalidateRect to force a repaint of the newly uncovered areas when your window is enlarged, or set the CS_VREDRAW and CS_HREDRAW styles on your window class to have the client area repainted automatically.

Jonathan Potter
  • 36,172
  • 4
  • 64
  • 79