Пример MSDN для прокрутки больших изображений

ВВЕДЕНИЕ И СООТВЕТСТВУЮЩАЯ ИНФОРМАЦИЯ:

Я пытаюсь нарисовать изображение в своем главном окне в его естественном размере. В настоящее время мне нужно визуализировать EMF.

Просматривая Интернет, я нашел этот пример MSDN.

ПРОБЛЕМА:

Я попробовал и обнаружил, что это плохо работает.

После того, как пользователь щелкнет правой кнопкой мыши, как говорится в инструкции, скриншот рабочего стола правильно отображается в главном окне.

Однако когда пользователь изменяет размер окна, возникают артефакты рисования, как показано на изображении ниже. Эффект ниже также возникает, когда пользователь немного прокручивает, а затем изменяет размер окна.

Вот изображение:

введите здесь описание изображения

Я впервые пытаюсь прокрутить изображение и использовать полосы прокрутки, поэтому мне действительно сложно найти решение.

ВОПРОСЫ:

Как исправить код статьи, чтобы удалить этот визуальный артефакт?

Можете ли вы порекомендовать другой пример, например учебник/пример кода/и т. д., который я могу изучить?

Можете ли вы предоставить «устные» инструкции/рекомендации, которые могли бы мне помочь?

Чтобы сделать вашу задачу еще проще, вот наименьший возможный код, который воспроизводит проблему. Обратите внимание, что я просто скопировал/вставил оконную процедуру и добавил минимальный код для программы, чтобы создать рабочую демонстрацию:

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

person AlwaysLearningNewStuff    schedule 22.06.2015    source источник
comment
Тем, кто проголосовал за закрытие как не по теме, потому что Вопросы, требующие помощи в отладке (почему этот код не работает?) должны включать желаемое поведение, конкретную проблему или ошибку и кратчайший код, необходимый для его воспроизведения в самом вопросе. Вопросы без четкой формулировки проблемы бесполезны для других читателей. См.: Как создать минимальный, полный и проверяемый пример. Акцент сделан на мне: я сделал наименьший код, демонстрирующий проблему. Я четко указал на ошибку и четко описал желаемое поведение. Пожалуйста, пересмотрите свои близкие голоса.   -  person AlwaysLearningNewStuff    schedule 22.06.2015
comment
Ваш обработчик WM_PAINT должен возвращать 0, а не вызывать DefWindowProc. msdn.microsoft.com/en-us/library/windows/desktop/ Та же проблема с WM_HSCROLL, WM_VSCROLL и т. д. Очень важно, чтобы вы читали документацию по всем сообщениям, которые вы обрабатываете -- возврат неправильного значения обратно в подсистему Windows является причиной множества ошибок и странного поведения.   -  person PaulMcKenzie    schedule 22.06.2015
comment
@PaulMcKenzie: не работает, такое же поведение сохраняется. Я исправил все возвращаемые значения и не добился успеха. Проблема должна быть связана с алгоритмом рисования, но я просто не могу понять, что именно. Хотя спасибо за попытку...   -  person AlwaysLearningNewStuff    schedule 22.06.2015
comment
Это больше, чем попытка. Это исправление в вашем коде, которое нужно было сделать, независимо от того, решит ли оно вашу проблему или нет. Исправление кодов возврата будет частью 1 фактического исправления. Кроме того, есть проблема с множеством других образцов, которые показывают прокрутку растрового изображения. Даже в старой Win 3.x код работал с прокруткой растрового изображения, так что учитывайте даже старые образцы.   -  person PaulMcKenzie    schedule 22.06.2015
comment
* Это исправление в вашем коде * Код не мой. Я просто пытаюсь научиться прокручивать изображение и использую связанную статью в качестве отправной точки. Даже в старой Win 3.x код работал с прокруткой растрового изображения, так что рассмотрим даже старые примеры. Где их найти? Я даже не смог найти современные примеры в WinAPI и C/C++...   -  person AlwaysLearningNewStuff    schedule 22.06.2015
comment
Из вашего описания кажется, что прокрутка отвлекает - в принципе, разве это не проблема перерисовки при изменении размера? Попробуйте использовать InvalidateRect для принудительной перерисовки при изменении размера окна.   -  person Jonathan Potter    schedule 22.06.2015
comment
Из вашего описания кажется, что прокрутка отвлекает внимание — в принципе, разве это не просто проблема перерисовки при изменении размера? Ну, мы можем определить это таким образом, предположим. С такой задачей сталкиваюсь впервые, поэтому не смог полностью разобраться в вопросе. Я просмотрел легендарную книгу Петцольда и эту статью. Ни один из них не использует InvalidateRect(), но я попробую.   -  person AlwaysLearningNewStuff    schedule 22.06.2015
comment
@JonathanPotter: Ну, ужасные артефакты исчезли. Внимательно просматривая книгу Петцольда, я, кажется, понял, почему: в каждом примере Петцольда класс окна имеет набор стилей CS_VREDRAW | CS_HREDRAW. В приведенном выше примере кода я опустил этот стиль (я также планировал вернуть 1L из WM_ERASEBKGND), чтобы уменьшить мерцание. Кажется, что добавление этого стиля или выполнение того, что вы сказали, решает проблему.   -  person AlwaysLearningNewStuff    schedule 22.06.2015
comment
@JonathanPotter: В моем исходном коде возникла еще одна проблема, которая является результатом модификации приведенного выше примера. Я считаю, что я должен обратиться за помощью в отдельном вопросе. После инвалидации в WM_SIZE проблема исчезла, как вы и предполагали. Если вы опубликуете это как ответ, я проголосую и приму. Это может быть полезно другим, потому что легко забыть сделать недействительными после удаления CS_HREDRAW | CS_VREDRAW стилей. Еще раз спасибо, с наилучшими пожеланиями.   -  person AlwaysLearningNewStuff    schedule 22.06.2015
comment
ScrollCall — это демонстрация прокрутки с кодом, который может помочь.   -  person Laurie Stearn    schedule 06.04.2021


Ответы (1)


Судя по вашему описанию, проблема заключается в том, что ваше окно не перекрашивается при изменении его размера.

Либо используйте InvalidateRect, чтобы принудительно перерисовать недавно открытые области, когда ваше окно увеличено, либо установите стили CS_VREDRAW и CS_HREDRAW в вашем классе окна, чтобы клиентская область перерисовывалась автоматически.

person Jonathan Potter    schedule 22.06.2015