Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Системное программирование и WinAPI > ScrollWindow в обработчике WM_SIZE


Автор: 0x07L 24.7.2006, 07:03
Столкнулся тут с одной особенностью применения ScrollWindow. Эта функция не хочет сдвигать изображение в область, которая только что появилась. Конечно, это не проблема, но тому, у кого есть варианты устранения этой неприятности так, чтобы не приходилось потом перерисовывать вновь появившуюся область, я буду премного благодарен.

Мое предположение основано на работе следующей программки:
Код

// Painting in WM_SIZE handler 2.cpp
//
// Возникает подозрение, что во время обработки сообщения WM_SIZE
// нельзя рисовать на вновь появившейся части окна.
// C FillRect однако все проходит нормально.
// Сейчас попробуем ScrollWindow.
//
// 24.07.2006

#include <windows.h>
#include <tchar.h>

const LPTSTR ClassName = _TEXT("Painting in WM_SIZE handler");
const LPTSTR MainWindowCaption = _TEXT("Painting in WM_SIZE handler");
const COLORREF ColorToFill = RGB(255, 69, 0);

int APIENTRY _tWinMain(HINSTANCE, HINSTANCE, LPTSTR, int);
LRESULT CALLBACK WindowProc(HWND, UINT, WPARAM, LPARAM);

int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE /* hPrevInstance */, LPTSTR /* CmdLine */, int CmdShow)
{    
    WNDCLASS wc = {0};
    wc.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW + 1);
    wc.hCursor = LoadCursor(0, IDC_ARROW);
    wc.hInstance = hInstance;
    wc.lpfnWndProc = WindowProc;
    wc.lpszClassName = ClassName;

    RegisterClass(&wc);
    
    HWND hWindow = CreateWindow(
        MainWindowCaption, ClassName, WS_OVERLAPPEDWINDOW,
        100, 100, 100, 100,
        0, 0, 0, 0);

    ShowWindow(hWindow, CmdShow);

    MSG msg;
    while (true)
    {
        int a = GetMessage(&msg, 0, 0, 0);

        if (a == 0)
        {
            return static_cast<int>(msg.wParam);
        }

        if (a == -1)
        {
            return -1;
        }

        DispatchMessage(&msg);
    }
}

LRESULT CALLBACK WindowProc(HWND hWindow, UINT Message, WPARAM wParam, LPARAM lParam)
{    
    static bool First = true;
    static int WindowHeight = 0;
    
    switch (Message)
    {
    case WM_SIZE:
        {
            RECT Rect;
            GetClientRect(hWindow, &Rect);

            if (Rect.bottom > WindowHeight && !First)
            {
                RECT NewRect = Rect;
                NewRect.bottom = WindowHeight;
                ScrollWindow(hWindow, 0, Rect.bottom - WindowHeight, &NewRect, 0);

                NewRect.top = WindowHeight;
                NewRect.bottom = Rect.bottom;
                ValidateRect(hWindow, &NewRect);
            }

            WindowHeight = Rect.bottom;
        }
        return 0;

    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hDC = BeginPaint(hWindow, &ps);

            if (First)
            {
                RECT Rect;
                GetClientRect(hWindow, &Rect);
                
                HBRUSH hBrush = CreateSolidBrush(ColorToFill);
                FillRect(hDC, &Rect, hBrush);
                DeleteObject(hBrush);

                First = false;
            }

            EndPaint(hWindow, &ps);
        }
        return 0;

    case WM_DESTROY:

        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc(hWindow, Message, wParam, lParam);
}

Как ею пользоваться? Появляется окно, выкрашенное в оранжевый цвет. Теперь попробуйте увеличить его размеры по горизонтали. В обработчике WM_SIZE я сдвигаю изображение в окне так, чтобы его нижняя граница совпала с новой нижней границей окна. В результате в верху окна появляется белая область, но это нормально. Обратите внимание на то, что белая область появляется и внизу, чего быть не должно.

Заметьте, что я применяю ValidateRect к вновь появившейся области. Это гарантирует, что по ней не пройдется реализация WM_ERASEBKGND в DefWindowProc.

PS Как тут можно exe-шник прикрепить?  

Автор: Earnest 24.7.2006, 07:57
Хм...
Попробуй делать ScrollWindow не прямо в  WM_SIZE, а после (пошли сам себе асинхронное сообщение). И еще, чтобы быть уверенным насчет EraseBackgnd, перехвати его сам, и ничего не делай. 
Файл можно прикрепить в полной форме ответа, или если будешь редактировать свой пост, там тоже будет поле для файла.  

Автор: 0x07L 24.7.2006, 08:21
WM_ERASEBKGND перехватил. Результаты те же.
А посылка асинхронного сообщения, пожалуй, действительно лучший способ решить эту проблему.

PS Интересно, почему FillRect действует, а ScrollWindow - нет.

Добавлено @ 08:30 
Кстати, пробовал также ставить ValidateRect перед ScrollWindow и вызывать перед ScrollWindow функцию UpdateWindow. Не помогает.  

Автор: Fixin 24.7.2006, 23:41
Код
case WM_GETMINMAXINFO:
        {    
            RECT Rect;    
            GetClientRect(hWindow, &Rect);    
            if (Rect.bottom > WindowHeight && !First)    
            {    
                RECT NewRect = Rect;    
                NewRect.bottom = WindowHeight;    
                ScrollWindow(hWindow, 0, Rect.bottom - WindowHeight, &NewRect, 0);
            }    
            WindowHeight = Rect.bottom;    
        }    
        return 0;
Тут суть в не в самом сообщении "гетминмаксинфо", а в том, что оно приходит до изменения размеров окна. 

Автор: 0x07L 25.7.2006, 07:07
Не пойдет. Во-первых, GetClientRect здесь возвращает старые координаты. Во-вторых, ScrollWindow не захочет двигать изображение в пока не видимую область. Это нужно делать в обработчике WM_SIZE (что не получается) или немного позже. 

Автор: Fixin 25.7.2006, 14:25
Цитата(0x07L @  25.7.2006,  07:07 Найти цитируемый пост)
Не пойдет.
А ты проверь. У меня-то пошло...
 

Автор: 0x07L 8.8.2006, 16:08
Я проверял. У меня не пошлО smile Значит, я чего-то не так сделал. Где здесь ошибка?
Код

// Painting in WM_SIZE handler 2.cpp
//
// Возникает подозрение, что во время обработки сообщения WM_SIZE
// нельзя рисовать на вновь появившейся части окна.
// C FillRect однако все проходит нормально.
// Сейчас попробуем ScrollWindow.
//
// 07.08.2006

#define _USE_METHOD_ 3
// 1 - standart method
// 2 - Fixin's method
// 3 - Earnest's method

#include <windows.h>
#include <tchar.h>

const LPTSTR ClassName = _TEXT("Painting in WM_SIZE handler");
const LPTSTR MainWindowCaption = _TEXT("Painting in WM_SIZE handler");
const COLORREF ColorToFill = RGB(255, 69, 0);

int APIENTRY _tWinMain(HINSTANCE, HINSTANCE, LPTSTR, int);
LRESULT CALLBACK WindowProc(HWND, UINT, WPARAM, LPARAM);

int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR CmdLine, int CmdShow)
{    
    WNDCLASS wc = {0};
    wc.hCursor = LoadCursor(0, IDC_ARROW);
    wc.hInstance = hInstance;
    wc.lpfnWndProc = WindowProc;
    wc.lpszClassName = ClassName;

    RegisterClass(&wc);
    
    HWND hWindow = CreateWindow(
        MainWindowCaption, ClassName, WS_OVERLAPPEDWINDOW,
        100, 100, 100, 100,
        0, 0, 0, 0);

    ShowWindow(hWindow, CmdShow);
    UpdateWindow(hWindow);

    MSG msg;
    while (true)
    {
        int a = GetMessage(&msg, 0, 0, 0);

        if (a == 0)
        {
            return static_cast<int>(msg.wParam);
        }

        if (a == -1)
        {
            return -1;
        }

        DispatchMessage(&msg);
    }
}

LRESULT CALLBACK WindowProc(HWND hWindow, UINT Message, WPARAM wParam, LPARAM lParam)
{    
    static bool First = true;
    static int WindowHeight = 0;
    
    switch (Message)
    {

    ///////////////////////////////////
    // STANDART METHOD

#if _USE_METHOD_ == 1

    case WM_SIZE:
        {
            // put a breakpoint here
            // ---------------------

            RECT Rect;
            GetClientRect(hWindow, &Rect);
            
            if (Rect.bottom > WindowHeight && !First)
            {
                RECT NewRect = Rect;
                NewRect.bottom = WindowHeight;
                
                ScrollWindow(hWindow, 0, Rect.bottom - WindowHeight, &NewRect, 0);
                            
                ValidateRect(hWindow, &Rect);
            }

            WindowHeight = Rect.bottom;
        }
        return 0;

#endif

    ///////////////////////////////////
    // FIXIN'S METHOD

#if _USE_METHOD_ == 2

    case WM_GETMINMAXINFO:
        {
            if (First)
            {
                return 0;
            }
            
            RECT Rect;
            GetClientRect(hWindow, &Rect);

            if (Rect.bottom > WindowHeight)
            {
                RECT NewRect = Rect;
                NewRect.bottom = WindowHeight;
                
                ScrollWindow(hWindow, 0, Rect.bottom - WindowHeight, &NewRect, 0);
            }
        }
        return 0;

    case WM_SIZE:
        {
            RECT Rect;
            GetClientRect(hWindow, &Rect);
            
            if (!First)
            {
                
                ValidateRect(hWindow, &Rect);
            }

            WindowHeight = Rect.bottom;
        }
        return 0;

#endif

    ///////////////////////////////////
    // EARNEST'S METHOD

#if _USE_METHOD_ == 3

    case WM_SIZE:
        {
            // put a breakpoint here
            // ---------------------

            RECT Rect;
            GetClientRect(hWindow, &Rect);
            
            if (Rect.bottom > WindowHeight && !First)
            {
                PostMessage(hWindow, WM_USER + 1, WindowHeight, 0);                            
                ValidateRect(hWindow, &Rect);
            }

            WindowHeight = Rect.bottom;
        }
        return 0;

    case WM_USER + 1:
        {
            RECT Rect;            
            GetClientRect(hWindow, &Rect);

            int Delta = static_cast<int>(Rect.bottom) - static_cast<int>(wParam);

            Rect.bottom = static_cast<long>(wParam);

            ScrollWindow(hWindow, 0, Delta, &Rect, 0);
        }
        return 0;

#endif

    ///////////////////////////////////

    case WM_ERASEBKGND:

        return TRUE;

    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hDC = BeginPaint(hWindow, &ps);

            if (First)
            {
                RECT Rect;
                GetClientRect(hWindow, &Rect);
                
                HBRUSH hBrush = CreateSolidBrush(ColorToFill);
                FillRect(hDC, &Rect, hBrush);
                DeleteObject(hBrush);

                First = false;
            }

            EndPaint(hWindow, &ps);
        }
        return 0;

    case WM_DESTROY:

        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc(hWindow, Message, wParam, lParam);
}

Автор: Fixin 8.8.2006, 19:27
немножко изменил цикл сообщений, а остальное сам заметишь.
Код
#include <windows.h>
#include <tchar.h>

const LPTSTR ClassName = _TEXT("Painting in WM_SIZE handler");
const LPTSTR MainWindowCaption = _TEXT("Painting in WM_SIZE handler");
const COLORREF ColorToFill = RGB(255, 69, 0);

int APIENTRY _tWinMain(HINSTANCE, HINSTANCE, LPTSTR, int);
LRESULT CALLBACK WindowProc(HWND, UINT, WPARAM, LPARAM);

int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR CmdLine, int CmdShow)
{    
    WNDCLASS wc = {0};
    wc.hCursor = LoadCursor(0, IDC_ARROW);
    wc.hInstance = hInstance;
    wc.lpfnWndProc = WindowProc;
    wc.lpszClassName = ClassName;

    RegisterClass(&wc);
    
    HWND hWindow = CreateWindow(
        MainWindowCaption, ClassName, WS_OVERLAPPEDWINDOW,
        100, 100, 100, 100,
        0, 0, 0, 0);

    ShowWindow(hWindow, CmdShow);
    UpdateWindow(hWindow);

    MSG msg;
    while (GetMessage(&msg, 0, 0, 0))        
        DispatchMessage(&msg);
    return static_cast<int>(msg.wParam);
}

LRESULT CALLBACK WindowProc(HWND hWindow, UINT Message, WPARAM wParam, LPARAM lParam)
{    
    static bool First = true;
    static int WindowHeight = 0;
    
    switch (Message)
    {

    case WM_GETMINMAXINFO:
        {    
            RECT Rect;    
            GetClientRect(hWindow, &Rect);    
            if (Rect.bottom > WindowHeight && !First)    
            {    
                RECT NewRect = Rect;    
                NewRect.bottom = WindowHeight;    
                ScrollWindow(hWindow, 0, Rect.bottom - WindowHeight, &NewRect, 0);
            }    
            WindowHeight = Rect.bottom;    
        }    
        return 0;


    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hDC = BeginPaint(hWindow, &ps);

            if (First)
            {
                RECT Rect;
                GetClientRect(hWindow, &Rect);
                Rect.top+=2;
                
                HBRUSH hBrush = CreateSolidBrush(ColorToFill);
                FillRect(hDC, &Rect, hBrush);
                DeleteObject(hBrush);

                First = false;
            }

            EndPaint(hWindow, &ps);
        }
        return 0;

    case WM_DESTROY:

        PostQuitMessage(0);
        return 0;
    }
    return DefWindowProc(hWindow, Message, wParam, lParam);
}

Автор: 0x07L 8.8.2006, 21:15
Все заметил. Я предвзято отнесся к коду в Вашем первом посте и не стал его просто копировать, справедливо полагая, что в таком виде, в каком он был приведен, он недееспособен, за что и поплатился smile Он работает, поскольку, когда размеры окна изменяет пользователь, после последнего сообщения WM_SIZE зачем-то приходит WM_GETMINMAXINFO, что вообще-то не следует из того, что написано в MSDN. Однако если изменить размер окна программно путем вызова SetWindowPos, такой WM_GETMINMAXINFO не приходит и Ваш способ не действует.

MSDN: "The WM_GETMINMAXINFO message is sent to a window when the size or position of the window is about to change".

Автор: 0x07L 8.8.2006, 21:33
Мой код даже более недееспособен, согласен.

Спасибо, Fixin. Спасибо, Earnest.
Тема закрыта.

Автор: Fixin 8.8.2006, 21:52
У тебя прямо талант находить глюки smile (это я вспомнил тему с ентером в листбоксе, кажется)

Автор: 0x07L 8.8.2006, 21:58
Это не талант. Просто редкое невезение  smile 

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)