如何加快CFormView中背景图片的重绘速度

问题描述 投票:0回答:1

我有一个文档模板应用程序。主视图继承了

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();
}
c++ winapi mfc
1个回答
0
投票

首先问几个问题:

  1. 为什么要创建子对话框?
    CFormView
    是一个基于对话框资源的视图,因此您可以直接在其上放置控件。
  2. 为什么使用 GDI+?只是因为您想使用 PNG 图像?如果 PNG 只是覆盖表单的背景,并且您不关心 alpha 透明度或其他什么(无论如何,“背景”图像“下面”是什么,我认为没什么),还有其他解决方案,例如使用 Windows 成像组件或 MFC
    CImage
    课。或者甚至考虑为您的图像资源使用不同的格式 - 它是嵌入式资源,而不是可以动态更改的外部文件,所以我认为这是可以接受的。
  3. 为什么使用
    OnEraseBkgnd()
    而不是
    OnPaint()
    ?它的效果要好得多 - 如果它总是绘制相同的图像,它就不会“闪烁”,只会填充无效部分。

它认为问题在于您正在执行完整的无效更新周期(这也涉及 GDI+ 调用),只是为了更新表单上的一小部分区域。我的建议是:

  • 删除整个
    Fragment
    类并将按钮和标签直接放置在表单视图的对话框资源上。否则,除了“Child”之外,还为
    Fragment
    设置“Control”属性。
  • 请勿使用
    OnEraseBkgnd()
    ,而使用
    OnPaint()
  • 如上所述,尽量避免使用 GDI+,而是使用 DDB 图像,并在初始化期间根据需要更好地修改它(例如调整大小、裁剪、平铺等)(当视图大小调整时,您可能也必须这样做),以便位图可以使用
    BitBlt()
    (1:1传输)进行绘制,非常快。

但是,仅仅为了更新(覆盖的)静态文本控件而使整个父窗口无效并重新绘制是一种矫枉过正。一个好的选择是不要无效,而是仅绘制静态控件占用的区域。这可以通过调用

GetDC()
/
ReleaseDC()
来完成,它会立即(同步)更新区域,而不是像 invalidate-erase-repaint 操作那样通过消息队列 - 它可以执行此类“实时”更新,即使 UI 线程很忙。我只讨论
OnBnClickedButton1()
操作,您仍然需要响应
OnEraseBkgnd()
OnPaint()
进行“正常”失效/重画(例如,当窗口最小化、最大化、恢复、未覆盖等时)。阅读此文档以获得更好的见解:绘画和绘图

您可能还会发现下面的链接很有趣(希望它不会让您感到困惑):
如何使用 WS_CLIPCHILDREN 并且仍然能够绘制具有透明背景的控件?

© www.soinside.com 2019 - 2024. All rights reserved.