Стратегия создания многослойного окна с дочерними окнами (элементы управления) - PullRequest
1 голос
/ 02 ноября 2011

Я хочу создать окно неправильной формы / со скином (на данный момент только с закругленными, альфа-смешанными углами).После создания окна верхнего уровня я обрабатываю сообщение WM_CREATE и делаю следующее:

  1. Создание совместимой памяти DC
  2. Создание раздела DIB, совместимого с окном DC
  3. Выбрать DIB в памяти DC
  4. Сделать рисунок моего фона
  5. Применить альфа-канал и предварительно умножить значения RGB
  6. Вызвать UpdateLayeredWindow ()

Позже я планирую округлить края, установив альфа-канал и предварительно умножив соответствующие биты, чтобы это произошло.

Теперь, если я создам, например, кнопку в моем окне, она не будетсделать сам.Я знаю почему, и я пытаюсь найти элегантное решение для создания своих пользовательских элементов управления (дочерних окон) здесь.

Мой первоначальный подход состоял в том, чтобы сначала отказаться от использования дочерних окон и просто позволитьОкно уровня делает все рисование, а также обработку ввода, тестирование попаданий и так далее.Я обнаружил, что это утомительно, и вместо этого я хочу, чтобы Windows обрабатывал все это для меня.

Теперь я знаю, что если я создаю дочернее окно, оно, конечно, ведет себя нормально (например, реагирует на ввод пользователя)и я хочу использовать это.Я планирую создать дочерние окна (пользовательские элементы управления), как правило, используя CreateWindowEx (), чтобы они получали дескриптор окна и получали оконные сообщения, и мне не нужно беспокоиться о их передаче вручную.

Каким-то образом мне нужно получить этиОкрашенные окна, и, как я вижу, единственный возможный способ сделать это - из рутины, которая рисует все окно верхнего уровня.Мне нужно придумать какую-то логику, чтобы заставить процедуру рисования окна верхнего уровня рисовать мои дочерние окна всякий раз, когда это необходимо.Насколько я понимаю, функция UpdateLayeredWindow () должна перерисовывать все окно.

Имеет ли смысл, например, чтобы дочерние элементы управления отображали свое изображение, которое отправляется в процедуру рисования окна верхнего уровня?Как, например, дочернее окно отправляет пользовательский WM в окно верхнего уровня, передавая указатель на его отображаемое растровое изображение в виде WPARAM и указатель на структуру, определяющую его положение и размер в качестве LPARAM.

Есть какие-нибудь лучшие идеи?Это вообще имеет какой-то смысл?

Спасибо, Эйрик

Ответы [ 3 ]

2 голосов
/ 03 ноября 2011

Я думаю, что я собираюсь пойти на это решение:

Окно верхнего уровня

Окно верхнего уровня поддерживает два растровых изображения. Один, который является отображаемым окном, и один без дочерних элементов управления. Последний нужно будет перерисовать только тогда, когда окно меняет размер. В окне будет обработчик сообщений, который отображает дочерний элемент управления на отображаемом растровом изображении. Обработчик сообщений будет ожидать указатель либо на DIB, содержащий дочерний элемент управления, либо на фактические биты (не уверенные, какой из них является лучшим на данный момент), как WPARAM, и указатель на структуру, содержащую прямоугольник, которым должен быть дочерний элемент. втягивается как LPARAM. Будет выполнен вызов BitBlt (), чтобы очистить нижележащую поверхность (это то место, где появляется другое растровое изображение) до вызова AlphaBlend () для рендеринга растрового изображения дочернего элемента управления на отображаемую растровое растровое изображение.

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

Дочерние окна

При создании экземпляра дочернего элемента управления создается внутреннее растровое изображение, совместимое с изображением окна верхнего уровня. Дочерний объект отображает себя в этом внутреннем растровом изображении, и всякий раз, когда ему нужно перерисовать, он уведомляет свое родительское окно через функцию SendMessage (), передавая указатель на свое растровое изображение в качестве WPARAM и RECT в качестве LPARAM, определяющего его положение и размеры. Если родительский объект нуждается в перерисовке, он отправляет сообщение всем дочерним окнам, запрашивая их растровое изображение. Затем дети ответят тем же сообщением, которое они обычно отправляют, когда решат, что им нужно перерисовать себя.

Эйрик

2 голосов
/ 04 сентября 2013

Я пытался сделать очень похожую вещь.Из чтения этой и другой поисковой сети.Он использует рекомендуемый механизм для рисования CWnd (или HWND), и его дочерние элементы на вашем собственном CDC (или HDC) должны использовать API печати. ​​

CWnd имеет методы Print и PrintClient и которые правильно отправляют WM_PRINT.Есть также методы Win32: PrintWindow.

Сначала у меня были проблемы с тем, чтобы это сработало, но в итоге я получил правильный метод и параметры.Код, который работал для меня, был:

void Vg2pImageHeaderRibbon::Update() {
    // Get dimensions
    CRect window_rect;
    GetWindowRect(&window_rect);

    // Make mem DC + mem  bitmap
    CDC* screen_dc = GetDC(); // Get DC for the hwnd
    CDC dc;
    dc.CreateCompatibleDC(screen_dc);
    CBitmap dc_buffer;
    dc_buffer.CreateCompatibleBitmap(screen_dc, window_rect.Width(), window_rect.Height());
    auto hBmpOld = dc.SelectObject(dc_buffer);

    // Create a buffer for manipulating the raw bitmap pixels (per-pixel alpha).
    // Used by ClearBackgroundAndPrepForPerPixelTransparency and CorrectPerPixelAlpha.
    BITMAP raw_bitmap;
    dc_buffer.GetBitmap(&raw_bitmap);
    int bytes =  raw_bitmap.bmWidthBytes * raw_bitmap.bmHeight;
    std::unique_ptr<char> bits(new char[bytes]);

    // Clears the background black (I want semi-transparent black background).
    ClearBackgroundAndPrepForPerPixelTransparency(dc, raw_bitmap, bytes, bits.get(), dc_buffer);

    // To get the window and it's children to draw using print command
    Print(&dc, PRF_CLIENT | PRF_CHILDREN | PRF_OWNED);

    CorrectPerPixelAlpha(dc, raw_bitmap, bytes, bits.get(), dc_buffer);

    // Call UpdateLayeredWindow
    BLENDFUNCTION blend = {0};
    blend.BlendOp = AC_SRC_OVER;
    blend.SourceConstantAlpha = 255;
    blend.AlphaFormat = AC_SRC_ALPHA;
    CPoint ptSrc;
    UpdateLayeredWindow(
        screen_dc, 
        &window_rect.TopLeft(), 
        &window_rect.Size(), 
        &dc, 
        &ptSrc, 
        0, 
        &blend, 
        ULW_ALPHA
    );

    SelectObject(dc, hBmpOld);
    DeleteObject(dc_buffer);
    ReleaseDC(screen_dc);
}

Это работало для меня, как есть.Но если ваше окно или дети не поддерживают WM_PRINT, я посмотрел, как это было реализовано для класса CView, и обнаружил, что этот класс предоставляет виртуальный метод OnDraw (CDC * dc), который снабжен DC для рисования.WM_PAINT реализован примерно так:

CPaintDC dc(this);
OnDraw(&dc);

И WM_PAINT реализован:

CDC* dc = CDC::FromHandle((HDC)wParam);
OnDraw(dc);

Таким образом, WM_PAINT и WM_PRINT выдают OnDraw (), а код рисования реализован один раз.

Вы можете добавить эту же логику к своему производному классу CWnd.Это может быть невозможно при использовании волшебников классов visual studio.Мне пришлось добавить следующее в блок карты сообщений:

BEGIN_MESSAGE_MAP(MyButton, CButton)
    ...other messages
    ON_MESSAGE(WM_PRINT, OnPrint)
END_MESSAGE_MAP()

И мой обработчик:

LRESULT MyButton::OnPrint(WPARAM wParam, LPARAM lParam) {
    CDC* dc = CDC::FromHandle((HDC)wParam);
    OnDraw(dc);
    return 0;
}

ПРИМЕЧАНИЕ. Если вы добавите пользовательский обработчик WM_PRINT в класс, который уже поддерживает это автоматическитогда вы потеряете реализацию по умолчанию.Не существует метода CWnd для OnPrint, поэтому вы должны использовать метод Default () для вызова обработчика по умолчанию.

Я не пробовал следующее, но ожидаю, что он работает:

LRESULT MyCWnd::OnPrint(WPARAM wParam, LPARAM lParam) {
    CDC* dc = CDC::FromHandle((HDC)wParam);
    // Do my own drawing using custom drawing
    OnDraw(dc);

    // And get the default handler to draw children
    return Default();
}

Выше я определил несколько странных методов: ClearBackgroundAndPrepForPerPixelTransparency и CorrectPerPixelAlpha.Это позволяет мне установить фон моего диалогового окна полупрозрачным, когда дочерние элементы управления полностью непрозрачны (это прозрачность для каждого пикселя).

// This method is not very efficient but the CBitmap class doens't
// give me a choice I have to copy all the pixel data out, process it and set it back again.
// For performance I recommend using another bitmap class
//
// This method makes every pixel have an opacity of 255 (fully opaque).
void Vg2pImageHeaderRibbon::ClearBackgroundAndPrepForPerPixelTransparency( 
    CDC& dc, const BITMAP& raw_bitmap, int bytes, char* bits, CBitmap& dc_buffer 
) {
    CRect rect;
    GetClientRect(&rect);
    dc.FillSolidRect(0, 0, rect.Width(), rect.Height(), RGB(0,0,0));
    dc_buffer.GetBitmapBits(bytes, bits);
    UINT* pixels = reinterpret_cast<UINT*>(bits);
    for (int c = 0; c < raw_bitmap.bmWidth * raw_bitmap.bmHeight; c++ ){
        pixels[c] |= 0xff000000;
    }
    dc_buffer.SetBitmapBits(bytes, bits);
}

// This method is not very efficient but the CBitmap class doens't
// give me a choice I have to copy all the pixel data out, process it and set it back again.
// For performance I recommend using another bitmap class
//
// This method modifies the opacity value because we know GDI drawing always sets
// the opacity to 0 we find all pixels that have been drawn on since we called 
// For performance I recommend using another bitmap class such as the IcfMfcRasterImage
// ClearBackgroundAndPrepForPerPixelTransparency. Anything that has been drawn on will get an 
// opacity of 255 and all untouched pixels will get an opacity of 100.
void Vg2pImageHeaderRibbon::CorrectPerPixelAlpha( 
    CDC& dc, const BITMAP& raw_bitmap, int bytes, char* bits, CBitmap& dc_buffer 
) {
    const unsigned char AlphaForBackground = 100; // (0 - 255)
    const int AlphaForBackgroundWord = AlphaForBackground << 24;
    dc_buffer.GetBitmapBits(bytes, bits);
    UINT* pixels = reinterpret_cast<UINT*>(bits);
    for (int c = 0; c < raw_bitmap.bmWidth * raw_bitmap.bmHeight; c++ ){
        if ((pixels[c] & 0xff000000) == 0) {
            pixels[c] |= 0xff000000;
        } else {
            pixels[c] = (pixels[c] & 0x00ffffff) | AlphaForBackgroundWord;
        }
    }
    dc_buffer.SetBitmapBits(bytes, bits);
}

Вот снимок экрана моего тестового приложения,Когда пользователь наводит указатель мыши на кнопку «дополнительные кнопки», создается диалоговое окно с полупрозрачным фоном.Кнопки с «B1» по «B16» являются дочерними элементами управления, производными от CButton и отображаются с помощью показа вызова Print () выше.Вы можете видеть полупрозрачный фон на правом краю вида и между кнопками.

enter image description here

0 голосов
/ 02 ноября 2011

Цитировать страницу MSDN для WM_PAINT :

Сообщение WM_PAINT генерируется системой и не должно отправляться приложением.Чтобы заставить окно рисовать в определенном контексте устройства, используйте сообщение WM_PRINT или WM_PRINTCLIENT.Обратите внимание, что для этого требуется, чтобы целевое окно поддерживало сообщение WM_PRINTCLIENT.Наиболее распространенные элементы управления поддерживают сообщение WM_PRINTCLIENT.

Таким образом, похоже, что вы можете перебирать все дочерние окна с помощью EnumChildWindows и отправлять их WM_PRINT из вашей памятиDC между шагами 5 и 6.

Это будет выглядеть примерно так:

static BOOL CALLBACK MyPaintCallback(HWND hChild,LPARAM lParam) {
    SendMessage(hChild,WM_PRINT,(WPARAM)lParam,(LPARAM)(PRF_CHECKVISIBLE|PRF_CHILDREN|PRF_CLIENT));
    return TRUE;
}

void MyPaintMethod(HWND hWnd) {
    //steps 1-5

    EnumChildWindows(hWnd,MyPaintCallback,MyMemoryDC);

    //step 6
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...