1

不規則な形状/スキン ウィンドウを作成したい (今のところ、丸みを帯びたアルファ ブレンド コーナーのみ)。トップレベルのウィンドウを作成したら、WM_CREATE メッセージを処理し、次のことを行います。

  1. 互換性のあるメモリ DC を作成する
  2. ウィンドウ DC と互換性のある DIB セクションを作成する
  3. DIB をメモリ DC に選択
  4. 私の背景の描画を行います
  5. アルファ チャネルを適用し、RGB 値を事前に乗算する
  6. UpdateLayeredWindow() を呼び出す

後で、アルファチャンネルを設定し、問題のビットを事前に乗算してそれを実現することにより、エッジの丸めを計画しています。

たとえば、ウィンドウにボタンを作成すると、それ自体はレンダリングされません。理由はわかっています。ここでカスタム コントロール (子ウィンドウ) を作成するためのエレガントなソリューションを考え出そうとしています。

私の最初のアプローチは、最初に子ウィンドウを使用するのをやめ、すべての描画と入力処理、ヒット テストなどをトップ レベル ウィンドウに任せることでした。これは退屈な方法であることがわかりました。代わりに、Windows にすべての処理を任せたいと思います。

これで、子ウィンドウを作成すると、もちろん正常に動作することがわかりました (ユーザー入力への反応など)。これを活用したいと考えています。通常は CreateWindowEx() を使用して子ウィンドウ (カスタム コントロール) を作成して、ウィンドウ ハンドルを取得し、手動で渡すことを心配することなくウィンドウ メッセージを受信することを計画しています。

どういうわけか、これらのウィンドウをペイントする必要があります。私が見たところ、これを行う唯一の方法は、トップ レベル ウィンドウ全体をペイントするルーチンを使用することです。必要に応じて、トップ レベル ウィンドウのペイント ルーチンで子ウィンドウをペイントするための何らかのロジックを発明する必要があります。私が理解している限り、UpdateLayeredWindow() 関数はウィンドウ全体を再描画する必要があります。

たとえば、最上位ウィンドウのペイント ルーチンに送信される自分自身のイメージを子コントロールにレンダリングさせることは賢明でしょうか? たとえば、子ウィンドウがユーザー WM を最上位ウィンドウに送信し、レンダリングされたビットマップへのポインターを WPARAM として渡し、その位置とサイズを LPARAM として定義する構造体へのポインターを渡します。

より良いアイデアはありますか?これはまったく意味がありますか?

ありがとう、エイリック

4

3 に答える 3

2

私は非常に似たようなことをしようとしていました。これと他の検索ウェブを読むことから。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() になり、描画コードは 1 回実装されます。

基本的に、これと同じロジックを独自の 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 ハンドラを追加すると、デフォルトの実装が失われます。OnPrint には CWnd メソッドがないため、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() 呼び出しを使用して描画されています。ビューの右端とボタンの間に半透明の背景が表示されます。

ここに画像の説明を入力

于 2013-09-04T11:48:01.707 に答える
2

私はこの解決策に行くつもりだと思います:

トップレベルウィンドウ

最上位ウィンドウは 2 つのビットマップを維持します。1 つは表示されたウィンドウで、もう 1 つは子コントロールがレンダリングされていません。後者は、ウィンドウのサイズが変更されたときにのみ再描画が必要になります。ウィンドウには、表示されたビットマップに子コントロールをレンダリングするメッセージ ハンドラーがあります。メッセージ ハンドラーは、WPARAM として、子コントロールを含む DIB または実際のビット (現時点ではどちらが最適かは不明) へのポインター、および子が存在する四角形を含む構造体へのポインターを期待します。 LPARAM として引き込まれます。子コントロールのビットマップを表示されたビットマップ サーフェスにレンダリングするための AlphaBlend() 呼び出しの前に、下層のサーフェスをクリアするために BitBlt() への呼び出しが行われます (これは他のビットマップが入る場所です)。

親ウィンドウは、サイズが変更されたとき、または何らかの理由で子を再描画する必要があるときはいつでも EnumChildWindows を呼び出します。もちろん、子コントロールの不必要なレンダリングを減らすために、ここで強制される何らかの無効化体制が存在する可能性があります。ただし、速度の向上が努力に値するかどうかはわかりません.

子ウィンドウ

子コントロール インスタンスの作成時に、最上位ウィンドウのビットマップと互換性のある内部ビットマップが作成されます。子はこの内部ビットマップに自分自身をレンダリングし、再描画が必要なときはいつでも、SendMessage() 関数を介して親ウィンドウに通知し、ビットマップへのポインターを WPARAM として渡し、RECT を LPARAM として位置と寸法を定義します。親が再描画を必要とする場合、ビットマップを要求するすべての子ウィンドウにメッセージを発行します。チャイルドは、自分自身を再描画する必要があると判断したときに通常送信するのと同じメッセージで応答します。

エイリク

于 2011-11-03T18:00:08.100 に答える
0

WM_PAINTの MSDN ページを引用するには:

WM_PAINT メッセージはシステムによって生成され、アプリケーションによって送信されるべきではありません。ウィンドウを特定のデバイス コンテキストに強制的に描画するには、WM_PRINT または WM_PRINTCLIENT メッセージを使用します。これには、WM_PRINTCLIENT メッセージをサポートするターゲット ウィンドウが必要であることに注意してください。最も一般的なコントロールは、WM_PRINTCLIENT メッセージをサポートしています。

したがって、EnumChildWindows を使用してすべての子ウィンドウを反復処理し、手順 5 と 6 の間でメモリ DC を使用してWM_PRINTを送信できるようです。

次のようになります。

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
}
于 2011-11-02T15:20:55.103 に答える