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

Posted

技术标签:

【中文标题】透明 CWnd 过期时删除打开对话框的窗口句柄【英文标题】:Transparent CWnd deletes open dialog's window handle when it expires 【发布时间】:2020-11-15 22:41:23 【问题描述】:

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

SetWindowLong(GetSafeHwnd(), GWL_EXSTYLE, 0);

我正在检查应用程序启动时的更新。如果有更新,我会使用以下命令创建窗口:

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

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

这是窗口的代码。

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();

【问题讨论】:

那么……您还需要杀死导致问题的对话框,以便对话框在透明窗口之前终止。一些带有 WM_CLOSE 和计时器的消息发布可能是为了优雅地退出 嗨@Sven Nilsson 通知窗口的想法是用户可以继续他们想做的任何事情。喜欢工具->下载新版本。如果没有更好的解决方案,我可能要做的就是在用户“做事”时销毁通知窗口。但这本身就是一个有趣的挑战。但我不能把他们踢出他们呼吁的对话。 没有足够的代码来显示完整图片,但您是否尝试过将其设为 WS_CHILD 而不是 WS_POPUP 嗨@dxiv 我不能让它成为一个孩子,我的用户仍在运行 Windows 7。这是分层/透明窗口的本质。当我开始这个时,我真的想让它成为一个子窗口。 @lakeweb 然后使用spy++ 检查模态对话框。我的猜测是,透明窗口最终会成为他们祖先某个地方的父母或所有者,您需要找到一种方法来防止这种情况发生。 【参考方案1】:

原来答案很简单。没有钩子或黑客。 CMyApp::OnIdle(..) 在有一个模态对话框时不参与。所以一个安全的地方来破坏信息窗口。 info_window.h 已包含在此翻译单元中,因为这是创建窗口的位置。

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

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

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 不为空来检查信息窗口是否有效。

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

【讨论】:

以上是关于透明 CWnd 过期时删除打开对话框的窗口句柄的主要内容,如果未能解决你的问题,请参考以下文章

无法在 MFC 无窗口 Activex 中获取 Cwnd 类的句柄?

有关MFC类与其窗口句柄

(转)CWnd与HWND的区别与转换

CWnd类

捕获新创建的模态对话框的窗口句柄

MFC中怎么让子窗体大小随着父窗口的大小变化而改变?