场景
- 在开发
Win32/WTL
程序时,遇到了使用CFolderDialog
(atldlgs.h
)打不开目录选择对话框的情况。具体表现是执行了窗口的DoModal
,却没有窗口弹出来。 可以确定执行操作是在主线程,并不是工作线程。调试时暂停看堆栈,知道到DoModal
方法里的SHBrowseForFolder
就会停止,没有继续执行下去。主窗口也是卡住,但是不调用DoModal
前主窗口不会卡的。偶尔情况下还是能打开CFolderDialog
的,但是大部分测试都不行。什么情况?
说明
- 看
CFolderDialog
的DoModal
实现,SHBrowseForFolder
<2>是打开一个选择文件的对话框。在之前的文章和课程说过,模态对话框在关闭前DoModal
不会返回。主线程开启一个新的消息循环来处理模态对话框的消息。 当模态对话框没弹出来时,主界面也没有消息循环处理,自然不响应任何鼠标消息,所以看起来就是卡住了。
INT_PTR DoModal(HWND hWndParent = ::GetActiveWindow())
{if(m_bi.hwndOwner == NULL) // set only if not specified beforem_bi.hwndOwner = hWndParent;// Clear out any previous resultsm_szFolderPath[0] = 0;m_szFolderDisplayName[0] = 0;::CoTaskMemFree(m_pidlSelected);INT_PTR nRet = IDCANCEL;m_pidlSelected = ::SHBrowseForFolder(&m_bi);if(m_pidlSelected != NULL){nRet = IDOK;// If BIF_RETURNONLYFSDIRS is set, we try to get the filesystem path.// Otherwise, the caller must handle the ID-list directly.if((m_bi.ulFlags & BIF_RETURNONLYFSDIRS) != 0){if(::SHGetPathFromIDList(m_pidlSelected, m_szFolderPath) == FALSE)nRet = IDCANCEL;}}return nRet;
}
- 暂停看堆栈时,发现某个界面的
OnPaint
方法和SHBrowseForFolder
竟然在同一个线程执行,正常情况下是不可能。看OnPaint
方法,在方法内打断点,发现它一直循环执行。正常情况下只有绘制区域变更时,比如调用InvalidateRect
或窗口移动时才会调用一次。所以这个OnPaint
方法循环调用是有问题。 这个方法和其他窗口的绘图方法不一样的地方就是没有调用CPaintDC dc(m_hWnd);
,只调用了Gdiplus::Graphics g(dc);
来绘制背景。 增加CPaintDC dc(m_hWnd);
后问题解决。
图1
- 看下
CPaintDC
的构造构造函数实现,发现调用了BeginPaint
[1]。 在Win32
应用程序中,处理WM_PAINT
消息通常涉及调用BeginPaint
来获取绘图相关的设备上下文。BeginPaint
会标记缓冲区为有效,并清除重绘区域,这样在绘制时不会覆盖其他的绘图。
CPaintDC(HWND hWnd)
{ATLASSERT(::IsWindow(hWnd));m_hWnd = hWnd;m_hDC = ::BeginPaint(hWnd, &m_ps);
}~CPaintDC()
{ATLASSERT(m_hDC != NULL);ATLASSERT(::IsWindow(m_hWnd));::EndPaint(m_hWnd, &m_ps);Detach();
}
- 如果不调用
BeginPaint
,那么从InvalidateRect
或InvalidateRgn
设置的更新区域就不会被标记,之后WM_PAINT
消息就会被反复调用造成死循环。
例子
- 这里实现了一个错误处理
WM_PAINT
消息,只使用Gdiplus
绘制文字的例子。
view.h
// View.h : interface of the CView class
//
/#pragma once#include "atlcrack.h"
#include <gdiplus.h>class CView : public CWindowImpl<CView>
{
public:DECLARE_WND_CLASS(NULL)BOOL PreTranslateMessage(MSG* pMsg);BEGIN_MSG_MAP_EX(CView)MESSAGE_HANDLER(WM_PAINT, OnPaint)MSG_WM_CREATE(OnCreate)END_MSG_MAP()// Handler prototypes (uncomment arguments if needed):
// LRESULT MessageHandler(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
// LRESULT CommandHandler(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
// LRESULT NotifyHandler(int /*idCtrl*/, LPNMHDR /*pnmh*/, BOOL& /*bHandled*/)LRESULT OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);int OnCreate(LPCREATESTRUCT lpCreateStruct);protected:Gdiplus::Font* font_ = nullptr;
};
view.cpp
// View.cpp : implementation of the CView class
//
/#include "stdafx.h"
#include "resource.h"#include <gdiplus.h>
#include "View.h"BOOL CView::PreTranslateMessage(MSG* pMsg)
{pMsg;return FALSE;
}int CView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{Gdiplus::FontFamily fontFamily(L"Arial");font_ = new Gdiplus::Font(&fontFamily,16,(false)?Gdiplus::FontStyleBold:Gdiplus::FontStyleRegular,Gdiplus::UnitPixel);return 0;
}LRESULT CView::OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{//CPaintDC dc(m_hWnd);Gdiplus::Graphics g(m_hWnd);//Gdiplus::Graphics g(dc);static auto kMessage = L"OnPaint\n";Gdiplus::SolidBrush brush(Gdiplus::Color::Black);g.DrawString(kMessage, wcslen(L"OnPaint\n"), font_, Gdiplus::PointF(0, 0), nullptr, &brush);OutputDebugString(L"OnPaint\n");//TODO: Add your drawing code herebHandled = true;return 0;
}
LRESULT CMainFrame::OnAppAbout(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{CFolderDialog ff(NULL, NULL, BIF_RETURNONLYFSDIRS | BIF_USENEWUI);if (IDOK == ff.DoModal()) {}return 0;
}
项目
下载地址: https://download.csdn.net/download/infoworld/90213937
参考
-
beginPaint 函数
-
SHBrowseForFolderW 函数 shlobj_core.h