我有一个文档模板应用程序。主视图继承了
CFormView
,它在背景上绘制png图像。另外,我想将基于 CDialog
的对象放入此 CFormView
中。该对象 Fragment
有一个 CStatic
控制我要更新的文本。该控件的背景变得透明。我发现,如果 CFormView
中有背景图像,则 Fragment
控件上的文本会在更新时覆盖自身。我尝试使 CStatic
和 Fragment
上的相应区域无效,但这没有帮助 - 看起来文本是在父级的背景上绘制的。所以,我开始使CFormView
的区域无效。
它有帮助,但是有一个问题,在文本更新时
CStatic
闪烁,就像它(或其背景)正在绘制滞后(或太慢)一样,我想避免这种闪烁。 (我尝试录制gif,但上面看不到闪烁。)
这是代码:
fragment.h
:
#pragma once
#include "afxdialogex.h"
#include "Fragment.h"
#include "resource.h"
class Fragment : public CDialog
{
public:
Fragment(CWnd* pParent = nullptr) : CDialog(IDD_FRAGMENT1, pParent) {}
HBRUSH OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor);
void OnBnClickedButton1();
#ifdef AFX_DESIGN_TIME
enum
{
IDD = IDD_FRAGMENT1
};
#endif
protected:
DECLARE_DYNAMIC(Fragment)
DECLARE_MESSAGE_MAP()
void DoDataExchange(CDataExchange* pDX);
CStatic m_label;
};
fragment.cpp
:
#include "pch.h"
#include "Fragment.h"
IMPLEMENT_DYNAMIC(Fragment, CDialog)
BEGIN_MESSAGE_MAP(Fragment, CDialog)
ON_WM_CTLCOLOR()
ON_BN_CLICKED(IDC_BUTTON1, &Fragment::OnBnClickedButton1)
END_MESSAGE_MAP()
void Fragment::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
DDX_Control(pDX, IDC_LABEL, m_label);
}
void Fragment::OnBnClickedButton1()
{
for (int i = 0; i < 10; ++i) {
CString text;
text.Format(L"Hello, static - %d%%", i);
m_label.SetWindowText(text);
CRect rect;
m_label.GetWindowRect(&rect);
GetParent()->ScreenToClient(&rect);
GetParent()->InvalidateRect(&rect);
GetParent()->UpdateWindow();
Sleep(1000);
}
}
HBRUSH Fragment::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
pDC->SetBkMode(TRANSPARENT);
return (HBRUSH)GetStockObject(NULL_BRUSH);
}
'MainView.h':
#pragma once
#include <memory>
class Fragment;
class MainView : public CFormView
{
public:
#ifdef AFX_DESIGN_TIME
enum{ IDD = IDD_TRANSPARENTDIALOG_FORM };
#endif
MainDoc* GetDocument() const;
virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
protected:
MainView() noexcept;
DECLARE_DYNCREATE(MainView)
void DoDataExchange(CDataExchange* pDX);
void OnInitialUpdate();
protected:
DECLARE_MESSAGE_MAP()
Fragment* fragment = nullptr;
std::unique_ptr<Gdiplus::Image> m_backgroundImage = nullptr;
BOOL MainView::OnEraseBkgnd(CDC* dc);
};
'MainView.cpp':
#include "pch.h"
#include "framework.h"
#include "resource.h"
#include "Fragment.h"
#include "TransparentDialog.h"
#include "MainDoc.h"
#include "MainView.h"
IMPLEMENT_DYNCREATE(MainView, CFormView)
BEGIN_MESSAGE_MAP(MainView, CFormView)
ON_WM_ERASEBKGND()
END_MESSAGE_MAP()
Gdiplus::Image* LoadImageFromMemory(uint8_t* data, ULONG size)
{
IStream* memoryStream = nullptr;
ULONG writen = 0;
auto createStreamResult = CreateStreamOnHGlobal(NULL, TRUE, &memoryStream);
if (createStreamResult != S_OK) {
return nullptr;
}
HRESULT hr = memoryStream->Write(data, size, &writen);
if (!SUCCEEDED(hr)) {
memoryStream->Release();
return nullptr;
}
auto image = new Gdiplus::Image(memoryStream);
memoryStream->Release();
return image;
}
Gdiplus::Image* LoadPngFromResources(UINT resourceId)
{
Gdiplus::Image* image = nullptr;
HRSRC resourceHandle = FindResource(NULL, MAKEINTRESOURCE(resourceId), L"PNG");
if (resourceHandle) {
HGLOBAL resource = LoadResource(NULL, resourceHandle);
if (resource) {
uint8_t* resourceData = reinterpret_cast<uint8_t*>(LockResource(resource));
if (resourceData) {
image = LoadImageFromMemory(resourceData, SizeofResource(NULL, resourceHandle));
}
}
}
return image;
}
MainView::MainView() noexcept
: CFormView(IDD_TRANSPARENTDIALOG_FORM)
{
m_backgroundImage = std::unique_ptr<Gdiplus::Image>(LoadPngFromResources(IDB_PNG1));
}
void MainView::DoDataExchange(CDataExchange* pDX)
{
CFormView::DoDataExchange(pDX);
}
BOOL MainView::PreCreateWindow(CREATESTRUCT& cs)
{
return CFormView::PreCreateWindow(cs);
}
void MainView::OnInitialUpdate()
{
CFormView::OnInitialUpdate();
GetParentFrame()->RecalcLayout();
ResizeParentToFit();
fragment = new Fragment(this);
fragment->Create(IDD_FRAGMENT1, this);
fragment->ShowWindow(true);
}
BOOL MainView::OnEraseBkgnd(CDC* dc)
{
Gdiplus::Graphics graphics(dc->m_hDC);
graphics.DrawImage(
m_backgroundImage.get(), 0, 0, m_backgroundImage.get()->GetWidth(), m_backgroundImage.get()->GetHeight());
return true;
}
MainDoc* MainView::GetDocument() const
{
return (MainDoc*)m_pDocument;
}
注意:点击按钮更新文本 10 次并在中间睡觉只是为了测试目的。删除
Sleep
并设置为每次单击按钮仅更新一次文本后,闪烁仍然会持续。
此外,我发现更新文本在这里无关紧要,即使仅使用以下代码更新父窗口,闪烁仍然存在:
void Fragment::OnBnClickedButton1()
{
CRect rect;
m_label.GetWindowRect(&rect);
GetParent()->ScreenToClient(&rect);
GetParent()->InvalidateRect(&rect);
GetParent()->UpdateWindow();
}
首先问几个问题:
CFormView
是一个基于对话框资源的视图,因此您可以直接在其上放置控件。CImage
课。或者甚至考虑为您的图像资源使用不同的格式 - 它是嵌入式资源,而不是可以动态更改的外部文件,所以我认为这是可以接受的。OnEraseBkgnd()
而不是 OnPaint()
?它的效果要好得多 - 如果它总是绘制相同的图像,它就不会“闪烁”,只会填充无效部分。它认为问题在于您正在执行完整的无效更新周期(这也涉及 GDI+ 调用),只是为了更新表单上的一小部分区域。我的建议是:
Fragment
类并将按钮和标签直接放置在表单视图的对话框资源上。否则,除了“Child”之外,还为 Fragment
设置“Control”属性。OnEraseBkgnd()
,而使用 OnPaint()
。BitBlt()
(1:1传输)进行绘制,非常快。但是,仅仅为了更新(覆盖的)静态文本控件而使整个父窗口无效并重新绘制是一种矫枉过正。一个好的选择是不要无效,而是仅绘制静态控件占用的区域。这可以通过调用
GetDC()
/ReleaseDC()
来完成,它会立即(同步)更新区域,而不是像 invalidate-erase-repaint 操作那样通过消息队列 - 它可以执行此类“实时”更新,即使 UI 线程很忙。我只讨论 OnBnClickedButton1()
操作,您仍然需要响应 OnEraseBkgnd()
或 OnPaint()
进行“正常”失效/重画(例如,当窗口最小化、最大化、恢复、未覆盖等时)。阅读此文档以获得更好的见解:绘画和绘图。
您可能还会发现下面的链接很有趣(希望它不会让您感到困惑):
如何使用 WS_CLIPCHILDREN 并且仍然能够绘制具有透明背景的控件?