透明 CWnd 在过期时删除打开对话框的 window 句柄

Transparent CWnd deletes open dialog's window handle when it expires

我正在使用透明 window 在我的应用程序中显示不显眼的消息。一切正常,但有一点。如果我在模式对话框过期时打开它,对话框 window 就会被销毁。这当然会导致崩溃,因为没有用于对话消息循环的 wnd 句柄。我已经检查过了,它发生在几个不同的对话框中,比如 AboutDlg。我猜测由于模态消息循环而出现问题。如果我没有打开对话框,应用程序会继续正常运行。我在 DestroyWindow 中试过这个,但没有帮助:

SetWindowLong(GetSafeHwnd(), GWL_EXSTYLE, 0);

我正在检查应用程序启动时的更新。如果有更新,我会创建 window:

        pFrm->SetInfoPtr(InfoWnd::InitWindow(pFrm->GetActiveView(), IDB_NEWVERSION));

如果父级在桌面上,它可以独立存在。但是如果在视图上方并且当主框架移动或调整大小时...那么现在主框架将 InfoWnd 感知。这可能是解决方案的一部分。

这是 window 的代码。

info_window.h

class InfoWnd : public CWnd
{
protected:
    static InfoWnd* self_ptr;
    CWnd* parent = nullptr;
    HDC memdc = nullptr;
    static BLENDFUNCTION blend;
    int width;
    int height;
    CBitmap m_bitmap;
    InfoWnd() {};

public:
    static auto GetSelfPointer() { return &self_ptr; }
    virtual ~InfoWnd() { self_ptr = nullptr; }
    virtual void PostNcDestroy() { self_ptr = nullptr; delete this; }
    static InfoWnd** InitWindow(CWnd* parnt, UINT bitmapID);
    void MoveInfoWindow(LPCRECT lpRect);

protected:
    BOOL Create(CWnd* pParentWnd = NULL);
DECLARE_MESSAGE_MAP()
    afx_msg void OnPaint();
    afx_msg void OnTimer(UINT nIDEvent);
};

info_window.cpp

BEGIN_MESSAGE_MAP(InfoWnd, CWnd)
    ON_WM_CREATE()
    ON_WM_PAINT()
    ON_WM_TIMER()
END_MESSAGE_MAP()

InfoWnd** InfoWnd::InitWindow(CWnd* parent, UINT bitmapID) {
    assert(!self_ptr);
    self_ptr = new InfoWnd;
    if (!self_ptr->m_bitmap.LoadBitmap(bitmapID)) {
        delete self_ptr;
        self_ptr = nullptr;
        return nullptr;
    }
    BITMAP bm;
    self_ptr->m_bitmap.GetBitmap(&bm);
    self_ptr->width = bm.bmWidth;
    self_ptr->height = bm.bmHeight;
    self_ptr->parent = parent;
    self_ptr->Create(parent);
    return &self_ptr;
}

BOOL InfoWnd::Create(CWnd* pParentWnd)
{
    parent = pParentWnd;
    CRect parentRect;
    pParentWnd->GetWindowRect(parentRect);

    BOOL result = CreateEx( WS_EX_TOPMOST,
        AfxRegisterWndClass(0, AfxGetApp()->LoadStandardCursor(IDC_CROSS)),
        NULL, WS_POPUP | WS_VISIBLE, 0, 0, width, height, pParentWnd->GetSafeHwnd(), NULL);
    if (!result)
        return FALSE;

    std::cout << "InfoWnd: " << GetSafeHwnd() << std::endl;
    MoveWindow(parentRect.right - width, parentRect.top, parentRect.right, parentRect.top);

    blend.BlendOp = AC_SRC_OVER;                    // the only BlendOp defined in Windows 2000
    blend.BlendFlags = 0;                                   // nothing else is special ...
    blend.AlphaFormat = 0;                              // ...
    blend.SourceConstantAlpha = 255;    // the initial alpha value

    ShowWindow(SW_SHOW);
    SetTimer(1, 200, NULL);
    return TRUE;
}

void InfoWnd::MoveInfoWindow(LPCRECT lpRect)
{
    MoveWindow(lpRect->right - width, lpRect->top, lpRect->right, lpRect->top);
}

void InfoWnd::OnPaint()
{
    CPaintDC dc(this);
    CDC dcImage;
    if (!dcImage.CreateCompatibleDC(&dc))
        return;

    BITMAP bm;
    m_bitmap.GetBitmap(&bm);
    CBitmap* pOldBitmap = dcImage.SelectObject(&m_bitmap);
    dc.BitBlt(0, 0, bm.bmWidth, bm.bmHeight, &dcImage, 0, 0, SRCCOPY);
    dcImage.SelectObject(pOldBitmap);
}

void InfoWnd::OnTimer(UINT nIDEvent)
{
    if (blend.SourceConstantAlpha) {
        if (blend.SourceConstantAlpha == 255) {
            SetWindowLong(GetSafeHwnd(), GWL_EXSTYLE, WS_EX_LAYERED | WS_EX_NOACTIVATE | WS_EX_TRANSPARENT);
        }
        if (blend.SourceConstantAlpha) {
            UpdateLayeredWindow( NULL, NULL, NULL, NULL, NULL, NULL, &blend, ULW_ALPHA);
        }
        blend.SourceConstantAlpha-= 5;
        return;
    }
    KillTimer(nIDEvent);
    //this will be replaced with a post message to the parent?
    DestroyWindow();
}

原来答案很简单。没有钩子或黑客。 CMyApp::OnIdle(..) 在有模式对话框时不参与。因此,销毁信息的安全场所 window。 info_window.h 已包含在此翻译单元中,因为这是创建 window 的地方。

在上面的例子中,删除在InfoWnd::OnTimer(..)

中找到的DestroyWindow()

在声明中添加一个成员(注意,有一个,但现在只是 returns 指针):

bool IsDone() const { return !blend.SourceConstantAlpha; }
static auto GetSelfPointer() { return self_ptr; }

然后在应用程序中 OnIdle:

if(InfoWnd::GetSelfPointer() && InfoWnd::GetSelfPointer()->IsDone())
    InfoWnd::GetSelfPointer()->DestroyWindow();

指针指向指针的原因还是被MainFrame使用了。它通过检查 self_ptr 是否为空来检查信息 window 是否有效。

CMainFrame::OnMoving(){
    if (*pInfoWnd){
        //Move the window
    }
}