场景
- 在开发
Win32/WTL
程序时,遇到了使用CFolderDialog
(atldlgs.h
)打不开目录选择对话框的情况。具体表现是执行了窗口的DoModal
,却没有窗口弹出来。 可以确定执行操作是在主线程,并不是工作线程。调试时暂停看堆栈,知道到DoModal
方法里的SHBrowseForFolder
就会停止,没有继续执行下去。主窗口也是卡住,但是不调用DoModal
前主窗口不会卡的。偶尔情况下还是能打开CFolderDialog
的,但是大部分测试都不行。什么情况?
说明
- 看
CFolderDialog
的DoModal
实现,SHBrowseForFolder
^<2>^是打开一个选择文件的对话框。在之前的文章和课程说过,模态对话框在关闭前DoModal
不会返回。主线程开启一个新的消息循环来处理模态对话框的消息。 当模态对话框没弹出来时,主界面也没有消息循环处理,自然不响应任何鼠标消息,所以看起来就是卡住了。
cpp
INT_PTR DoModal(HWND hWndParent = ::GetActiveWindow())
{
if(m_bi.hwndOwner == NULL) // set only if not specified before
m_bi.hwndOwner = hWndParent;
// Clear out any previous results
m_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
会标记缓冲区为有效,并清除重绘区域,这样在绘制时不会覆盖其他的绘图。
cpp
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
cpp
// 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
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 here
bHandled = true;
return 0;
}
cpp
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