MFC刷新图片以及防止窗口抖动的方法

背景

  前置文章:MFC鼠标滚轮缩放OpenCV图片
  使用滚轮缩小图片,使其小于MFC的PictrueContral控件大小时,发现缩小前的图片依旧存在,缩小后的图片重叠在其上。具体错误图片如下所示:
  故缩放前应先刷新图片
MFC刷新图片以及防止窗口抖动的方法

刷新方法

一、先使PictrueContral控件不可见,后使其重新可见。
实现方法

  在显示图片之前添加如下代码刷新MFC的PictrueContral控件,其中IDC_STATIC为PictrueContral控件ID:

    GetDlgItem(IDC_STATIC)->ShowWindow(FALSE);
	GetDlgItem(IDC_STATIC)->ShowWindow(TRUE);

MFCApplication1Dlg.cpp全部代码如下:


// MFCApplication1Dlg.cpp: 实现文件
//

#include "stdafx.h"
#include "MFCApplication1.h"
#include "MFCApplication1Dlg.h"
#include "afxdialogex.h"

#include "opencv2/opencv.hpp"
using namespace cv;

double g_iZoom = 1;//初始缩放值
double g_iRadio = 0.1;//缩放步距
Mat g_matOriginal;//原始图片

#ifdef _DEBUG
#define new DEBUG_NEW
#endif


// CMFCApplication1Dlg 对话框



CMFCApplication1Dlg::CMFCApplication1Dlg(CWnd* pParent /*=nullptr*/)
	: CDialogEx(IDD_MFCAPPLICATION1_DIALOG, pParent)
{
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CMFCApplication1Dlg::DoDataExchange(CDataExchange* pDX)
{
	CDialogEx::DoDataExchange(pDX);
}

BEGIN_MESSAGE_MAP(CMFCApplication1Dlg, CDialogEx)
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	ON_WM_MOUSEWHEEL()
END_MESSAGE_MAP()


// CMFCApplication1Dlg 消息处理程序

BOOL CMFCApplication1Dlg::OnInitDialog()
{
	CDialogEx::OnInitDialog();

	// 设置此对话框的图标。  当应用程序主窗口不是对话框时,框架将自动
	//  执行此操作
	SetIcon(m_hIcon, TRUE);			// 设置大图标
	SetIcon(m_hIcon, FALSE);		// 设置小图标

	namedWindow("ImageShow");//创建OpenCV窗口
	HWND hWnd = (HWND)cvGetWindowHandle("ImageShow");//嵌套opencv窗口
	HWND hParent = ::GetParent(hWnd);
	::SetParent(hWnd, GetDlgItem(IDC_STATIC)->m_hWnd);
	::ShowWindow(hParent, SW_HIDE);
	g_iZoom = 1;//初始缩放值为1,表示原图
	g_matOriginal = imread("E:\\vs\\image\\11.png");//opencv读取图片
	imshow("ImageShow", g_matOriginal);//opencv显示图片
	waitKey(1);
	

	return TRUE;  // 除非将焦点设置到控件,否则返回 TRUE
}

// 如果向对话框添加最小化按钮,则需要下面的代码
//  来绘制该图标。  对于使用文档/视图模型的 MFC 应用程序,
//  这将由框架自动完成。

void CMFCApplication1Dlg::OnPaint()
{
	if (IsIconic())
	{
		CPaintDC dc(this); // 用于绘制的设备上下文

		SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

		// 使图标在工作区矩形中居中
		int cxIcon = GetSystemMetrics(SM_CXICON);
		int cyIcon = GetSystemMetrics(SM_CYICON);
		CRect rect;
		GetClientRect(&rect);
		int x = (rect.Width() - cxIcon + 1) / 2;
		int y = (rect.Height() - cyIcon + 1) / 2;

		// 绘制图标
		dc.DrawIcon(x, y, m_hIcon);
	}
	else
	{
		CDialogEx::OnPaint();
	}
}

//当用户拖动最小化窗口时系统调用此函数取得光标
//显示。
HCURSOR CMFCApplication1Dlg::OnQueryDragIcon()
{
	return static_cast<HCURSOR>(m_hIcon);
}


BOOL CMFCApplication1Dlg::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
	// TODO: 在此添加消息处理程序代码和/或调用默认值
	GetDlgItem(IDC_STATIC)->ShowWindow(FALSE);
	GetDlgItem(IDC_STATIC)->ShowWindow(TRUE);

	CRect rect;
	GetDlgItem(IDC_STATIC)->GetWindowRect(&rect);//获取MFC图像显示区域
	Point p(pt.x, pt.y);//opencv鼠标坐标
	Rect r(rect.left, rect.top, rect.right, rect.bottom);//opencv窗口显示区域
	if (r.contains(p)) //鼠标放在图片窗口上
	{
		int w = g_matOriginal.cols, h =g_matOriginal.rows;//原图宽高
		Mat mat;
		if (zDelta > 0)//放大图片
		{
			double zoom = g_iZoom + g_iRadio;
			if (w * zoom * h * zoom < 3000 * 3000)//图片宽高<3000*3000时缩放值才会变大
			{
				g_iZoom = zoom;
			}
			resize(g_matOriginal, mat, Size(), g_iZoom, g_iZoom, INTER_LINEAR); //按比例放大图片
		}
		else if (zDelta < 0)//缩小图片
		{
			double zoom = g_iZoom - g_iRadio;
			if (w * zoom * h * zoom > 100 * 100)//图片宽高>100*100时缩放值才会变小
			{
				g_iZoom = zoom;
			}
			resize(g_matOriginal, mat, Size(), g_iZoom, g_iZoom, INTER_AREA); //按比例缩小图片
		}
		imshow("ImageShow", mat);//显示缩放后图片
		waitKey(1);
	}

	return CDialogEx::OnMouseWheel(nFlags, zDelta, pt);
}
运行结果

  添加刷新代码后结果如下图所示。此方法虽然代码简单,最终结果也正确,但刷新图片时MFC图像窗口出现抖动,即刷新图片时间过长导致刷新时出现短暂空白。
MFC刷新图片以及防止窗口抖动的方法

二、填充与MFC图像窗口背景相同的颜色。
实现方法

  在显示图片前添加如下代码:

    CStatic* pStatic = (CStatic*)GetDlgItem(IDC_STATIC);
	CRect rect1;
	pStatic->GetClientRect(&rect1);//获取MFC图像窗口相对于窗口用户区左上角的坐标
	pStatic->GetDC()->FillSolidRect(rect1.left, rect1.top, rect1.Width(), rect1.Height(), RGB(255, 255, 255));//填充白色

MFCApplication1Dlg.cpp全部代码如下:


// MFCApplication1Dlg.cpp: 实现文件
//

#include "stdafx.h"
#include "MFCApplication1.h"
#include "MFCApplication1Dlg.h"
#include "afxdialogex.h"

#include "opencv2/opencv.hpp"
using namespace cv;

double g_iZoom = 1;//初始缩放值
double g_iRadio = 0.1;//缩放步距
Mat g_matOriginal;//原始图片

#ifdef _DEBUG
#define new DEBUG_NEW
#endif


// CMFCApplication1Dlg 对话框



CMFCApplication1Dlg::CMFCApplication1Dlg(CWnd* pParent /*=nullptr*/)
	: CDialogEx(IDD_MFCAPPLICATION1_DIALOG, pParent)
{
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CMFCApplication1Dlg::DoDataExchange(CDataExchange* pDX)
{
	CDialogEx::DoDataExchange(pDX);
}

BEGIN_MESSAGE_MAP(CMFCApplication1Dlg, CDialogEx)
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	ON_WM_MOUSEWHEEL()
END_MESSAGE_MAP()


// CMFCApplication1Dlg 消息处理程序

BOOL CMFCApplication1Dlg::OnInitDialog()
{
	CDialogEx::OnInitDialog();

	// 设置此对话框的图标。  当应用程序主窗口不是对话框时,框架将自动
	//  执行此操作
	SetIcon(m_hIcon, TRUE);			// 设置大图标
	SetIcon(m_hIcon, FALSE);		// 设置小图标

	namedWindow("ImageShow");//创建OpenCV窗口
	HWND hWnd = (HWND)cvGetWindowHandle("ImageShow");//嵌套opencv窗口
	HWND hParent = ::GetParent(hWnd);
	::SetParent(hWnd, GetDlgItem(IDC_STATIC)->m_hWnd);
	::ShowWindow(hParent, SW_HIDE);
	g_iZoom = 1;//初始缩放值为1,表示原图
	g_matOriginal = imread("E:\\vs\\image\\11.png");//opencv读取图片
	imshow("ImageShow", g_matOriginal);//opencv显示图片
	waitKey(1);
	

	return TRUE;  // 除非将焦点设置到控件,否则返回 TRUE
}

// 如果向对话框添加最小化按钮,则需要下面的代码
//  来绘制该图标。  对于使用文档/视图模型的 MFC 应用程序,
//  这将由框架自动完成。

void CMFCApplication1Dlg::OnPaint()
{
	if (IsIconic())
	{
		CPaintDC dc(this); // 用于绘制的设备上下文

		SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

		// 使图标在工作区矩形中居中
		int cxIcon = GetSystemMetrics(SM_CXICON);
		int cyIcon = GetSystemMetrics(SM_CYICON);
		CRect rect;
		GetClientRect(&rect);
		int x = (rect.Width() - cxIcon + 1) / 2;
		int y = (rect.Height() - cyIcon + 1) / 2;

		// 绘制图标
		dc.DrawIcon(x, y, m_hIcon);
	}
	else
	{
		CDialogEx::OnPaint();
	}
}

//当用户拖动最小化窗口时系统调用此函数取得光标
//显示。
HCURSOR CMFCApplication1Dlg::OnQueryDragIcon()
{
	return static_cast<HCURSOR>(m_hIcon);
}


BOOL CMFCApplication1Dlg::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
	// TODO: 在此添加消息处理程序代码和/或调用默认值
	CStatic* pStatic = (CStatic*)GetDlgItem(IDC_STATIC);
	CRect rect1;
	pStatic->GetClientRect(&rect1);//获取MFC图像窗口相对于窗口用户区左上角的坐标
	pStatic->GetDC()->FillSolidRect(rect1.left, rect1.top, rect1.Width(), rect1.Height(), RGB(255, 255, 255));//填充白色

	CRect rect;
	GetDlgItem(IDC_STATIC)->GetWindowRect(&rect);//获取MFC图像显示区域
	Point p(pt.x, pt.y);//opencv鼠标坐标
	Rect r(rect.left, rect.top, rect.right, rect.bottom);//opencv窗口显示区域
	if (r.contains(p)) //鼠标放在图片窗口上
	{
		int w = g_matOriginal.cols, h =g_matOriginal.rows;//原图宽高
		Mat mat;
		if (zDelta > 0)//放大图片
		{
			double zoom = g_iZoom + g_iRadio;
			if (w * zoom * h * zoom < 3000 * 3000)//图片宽高<3000*3000时缩放值才会变大
			{
				g_iZoom = zoom;
			}
			resize(g_matOriginal, mat, Size(), g_iZoom, g_iZoom, INTER_LINEAR); //按比例放大图片
		}
		else if (zDelta < 0)//缩小图片
		{
			double zoom = g_iZoom - g_iRadio;
			if (w * zoom * h * zoom > 100 * 100)//图片宽高>100*100时缩放值才会变小
			{
				g_iZoom = zoom;
			}
			resize(g_matOriginal, mat, Size(), g_iZoom, g_iZoom, INTER_AREA); //按比例缩小图片
		}
		imshow("ImageShow", mat);//显示缩放后图片
		waitKey(1);
	}

	return CDialogEx::OnMouseWheel(nFlags, zDelta, pt);
}
运行结果

  此方法同样能够刷新图片。针对小图片刷新、刷新操作速度慢、刷新频繁的情况,此方法基本可以避免窗口刷新造成抖动的情况。若快速且频繁刷新则会出现窗口抖动,图片闪烁的情况。
MFC刷新图片以及防止窗口抖动的方法

三、填充图片
实现方法:

  既然MFC刷新图片必然会出现图像窗口短暂空白的情况,若此段时间短到人眼无法识别,则无抖动闪烁情况。若时间过长则会出现窗口抖动、图片闪烁情况。
  那么可以新建一张与背景色相同的空白图片,然后在此空白图片上填充缩放后的图片。每次显示此图片时,其总能完整覆盖图片窗口而不再需要刷新图片。

实现步骤

1、添加全局变量和函数声明

Mat g_matOriginal;//原始图片
Mat FileImage(Mat mat);

2、函数定义

Mat FileImage(Mat mat)
{
	Mat zero = Mat::Mat(g_szWin, CV_8UC3, Scalar(255, 255, 255));//定义一个与图片控件一样大,与背景色相同的空白图片
	Mat roi(zero, Rect(0, 0, mat.cols, mat.rows));//新建一个与mat相同大小的感兴趣区域
	mat.copyTo(roi);//将mat拷贝到感兴趣区域
	return zero;
}

3、显示图片判断

if (w*g_iZoom < g_szWin.width || h*g_iZoom < g_szWin.height)//图片小于MFC图片窗口时
		{
			imshow("ImageShow", FileImage(mat));//显示缩放后图片
		}
		else
		{
			imshow("ImageShow", mat);//显示缩放后图片
		}

4、MFCApplication1Dlg.cpp全部代码


// MFCApplication1Dlg.cpp: 实现文件
//

#include "stdafx.h"
#include "MFCApplication1.h"
#include "MFCApplication1Dlg.h"
#include "afxdialogex.h"

#include "opencv2/opencv.hpp"
using namespace cv;

double g_iZoom = 1;//初始缩放值
double g_iRadio = 0.1;//缩放步距
Size g_szWin = Size(0, 0);//图像窗口大小
Mat g_matOriginal;//原始图片
Mat FileImage(Mat mat);

#ifdef _DEBUG
#define new DEBUG_NEW
#endif


// CMFCApplication1Dlg 对话框



CMFCApplication1Dlg::CMFCApplication1Dlg(CWnd* pParent /*=nullptr*/)
	: CDialogEx(IDD_MFCAPPLICATION1_DIALOG, pParent)
{
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CMFCApplication1Dlg::DoDataExchange(CDataExchange* pDX)
{
	CDialogEx::DoDataExchange(pDX);
}

BEGIN_MESSAGE_MAP(CMFCApplication1Dlg, CDialogEx)
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	ON_WM_MOUSEWHEEL()
END_MESSAGE_MAP()


// CMFCApplication1Dlg 消息处理程序

BOOL CMFCApplication1Dlg::OnInitDialog()
{
	CDialogEx::OnInitDialog();

	// 设置此对话框的图标。  当应用程序主窗口不是对话框时,框架将自动
	//  执行此操作
	SetIcon(m_hIcon, TRUE);			// 设置大图标
	SetIcon(m_hIcon, FALSE);		// 设置小图标

	namedWindow("ImageShow");//创建OpenCV窗口
	HWND hWnd = (HWND)cvGetWindowHandle("ImageShow");//嵌套opencv窗口
	HWND hParent = ::GetParent(hWnd);
	::SetParent(hWnd, GetDlgItem(IDC_STATIC)->m_hWnd);
	::ShowWindow(hParent, SW_HIDE);
	g_iZoom = 1;//初始缩放值为1,表示原图
	g_matOriginal = imread("E:\\vs\\image\\11.png");//opencv读取图片
	imshow("ImageShow", g_matOriginal);//opencv显示图片
	waitKey(1);

	CRect rect;
	GetDlgItem(IDC_STATIC)->GetClientRect(&rect);//获取句柄
	g_szWin = Size(rect.right - rect.left - 2, rect.bottom - rect.top - 2);//获取图像窗口大小

	return TRUE;  // 除非将焦点设置到控件,否则返回 TRUE
}

// 如果向对话框添加最小化按钮,则需要下面的代码
//  来绘制该图标。  对于使用文档/视图模型的 MFC 应用程序,
//  这将由框架自动完成。

void CMFCApplication1Dlg::OnPaint()
{
	if (IsIconic())
	{
		CPaintDC dc(this); // 用于绘制的设备上下文

		SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

		// 使图标在工作区矩形中居中
		int cxIcon = GetSystemMetrics(SM_CXICON);
		int cyIcon = GetSystemMetrics(SM_CYICON);
		CRect rect;
		GetClientRect(&rect);
		int x = (rect.Width() - cxIcon + 1) / 2;
		int y = (rect.Height() - cyIcon + 1) / 2;

		// 绘制图标
		dc.DrawIcon(x, y, m_hIcon);
	}
	else
	{
		CDialogEx::OnPaint();
	}
}

//当用户拖动最小化窗口时系统调用此函数取得光标
//显示。
HCURSOR CMFCApplication1Dlg::OnQueryDragIcon()
{
	return static_cast<HCURSOR>(m_hIcon);
}


BOOL CMFCApplication1Dlg::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
	// TODO: 在此添加消息处理程序代码和/或调用默认值

	CRect rect;
	GetDlgItem(IDC_STATIC)->GetWindowRect(&rect);//获取MFC图像显示区域
	Point p(pt.x, pt.y);//opencv鼠标坐标
	Rect r(rect.left, rect.top, rect.right, rect.bottom);//opencv窗口显示区域
	if (r.contains(p)) //鼠标放在图片窗口上
	{
		int w = g_matOriginal.cols, h =g_matOriginal.rows;//原图宽高
		Mat mat;
		if (zDelta > 0)//放大图片
		{
			double zoom = g_iZoom + g_iRadio;
			if (w * zoom * h * zoom < 3000 * 3000)//图片宽高<3000*3000时缩放值才会变大
			{
				g_iZoom = zoom;
			}
			resize(g_matOriginal, mat, Size(), g_iZoom, g_iZoom, INTER_LINEAR); //按比例放大图片
		}
		else if (zDelta < 0)//缩小图片
		{
			double zoom = g_iZoom - g_iRadio;
			if (w * zoom * h * zoom > 100 * 100)//图片宽高>100*100时缩放值才会变小
			{
				g_iZoom = zoom;
			}
			resize(g_matOriginal, mat, Size(), g_iZoom, g_iZoom, INTER_AREA); //按比例缩小图片
		}
		if (w*g_iZoom < g_szWin.width || h*g_iZoom < g_szWin.height)//图片小于MFC图片窗口时
		{
			imshow("ImageShow", FileImage(mat));//显示缩放后图片
		}
		else
		{
			imshow("ImageShow", mat);//显示缩放后图片
		}
		waitKey(1);
	}

	return CDialogEx::OnMouseWheel(nFlags, zDelta, pt);
}

Mat FileImage(Mat mat)
{
	Mat zero = Mat::Mat(g_szWin, CV_8UC3, Scalar(255, 255, 255));//定义一个与图片控件一样大,与背景色相同的空白图片
	Mat roi(zero, Rect(0, 0, mat.cols, mat.rows));//新建一个与mat相同大小的感兴趣区域
	mat.copyTo(roi);//将mat拷贝到感兴趣区域
	return zero;
}

5、运行结果
MFC刷新图片以及防止窗口抖动的方法

参考文章:

MFC里清除picture control上的图片,使他呈现原始控件的灰色