滚动大图像故障的 MSDN 示例

MSDN example for scrolling large image malfunctions

简介和相关信息:

我正在尝试以自然大小绘制主 window 中的图像。目前我需要渲染 EMF。

通过互联网浏览后,我发现 this MSDN example

问题:

我已经试过了,发现效果不佳。

用户右键单击后,如说明所述,桌面屏幕截图已正确绘制在主 window 中。

但是,当用户调整 window 的大小时,会出现如下图所示的绘画瑕疵。当用户稍微滚动然后调整 window.

大小时,也会出现以下效果

图片如下:

这是我第一次尝试滚动图像并使用滚动条,所以我真的很难找到解决方案。

问题:

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?

为了使您的任务更轻松,这里是重现问题的最小代码。请注意,我只是 copy/pasted window 过程并为程序添加了最少的代码来创建工作演示:

#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;
}

根据您的描述,问题似乎很简单,就是您的 window 在调整大小时没有重新绘制。

在 window 放大时使用 InvalidateRect 强制重新绘制新发现的区域,或者在 [=] 上设置 CS_VREDRAWCS_HREDRAW 样式18=] class 自动重绘客户区。