当前位置: 首页 > article >正文

[Win32/WTL]_[初级]_[如何销毁自定义控件]

场景

  1. 在开发Win32/WTL程序时,有时候需要对创建的控件进行删除再重建。比如用某些按钮表示系统里的磁盘,当插入拔出U盘时,需要删除对应按钮,或新建新的按钮表示插入的U盘。实现这个按钮我们用的是CWindowImpl的子类,有什么要注意的地方吗?

说明

  1. Win32的窗口是对应着一个窗口句柄类型HWND。在atlwin.h里的类CWindowImpl当然也会持有一个句柄成员变量进行维护。所以我们销毁一个自定义窗口类时,当然也需要销毁这个HWND句柄。销毁句柄调用winuser.h的函数DestroyWindow[1].
BOOL DestroyWindow(
  [in] HWND hWnd
);
  1. 这个函数只是销毁HWND对象,并不会对CWindow成员变量m_hWnd赋值为NULL。这个CWindow也是整个继承树的最终父类TBase

  2. 我们看看在CWindowImpl子类调用成员方法DestroyWindow时,实际上调用的是它的父类CWindowImplRoot<TBase>DestroyWindow方法, 它里面实际调用的是全局的::DestroyWindow函数。 可以对这个全局的函数调用时打断点,可以发现它调用结束后m_hWnd的值是NULL。为什么呢?

BOOL DestroyWindow()
    {
#ifndef ATL_NO_ASSERT_ON_DESTROY_NONEXISTENT_WINDOW
        ATLASSERT(::IsWindow(this->m_hWnd));
#endif

        if (!::DestroyWindow(this->m_hWnd))
        {
            return FALSE;
        }

        return TRUE;
    }
  1. 因为在调用::DestroyWindow时,会同步发送消息WM_DESTROYWM_NCDESTROY。 。CWindowImplRoot<TBase>的子类CWindowImplBaseT注册了的消息处理函数里对这些消息进行了处理。并在CWindowImplBaseT< TBase, TWinTraits >::WindowProc方法里赋值pThis->m_hWnd = NULL;。因此上边第3点说的调用::DestroyWindow返回后m_hWndNULL. 但是要注意,它的前提条件是满足if((pThis->m_dwState & CWindowImplRoot<TBase>::WINSTATE_DESTROYED) && pOldMsg== NULL).如果没有调用这行,我们在CWindowImpl子类析构里是需要调用Detach()方法。
template <class TBase, class TWinTraits>
LRESULT CALLBACK CWindowImplBaseT< TBase, TWinTraits >::StartWindowProc(
	_In_ HWND hWnd,
	_In_ UINT uMsg,
	_In_ WPARAM wParam,
	_In_ LPARAM lParam)
{
	CWindowImplBaseT< TBase, TWinTraits >* pThis = (CWindowImplBaseT< TBase, TWinTraits >*)_AtlWinModule.ExtractCreateWndData();
	ATLASSERT(pThis != NULL);
	if(!pThis)
	{
		return 0;
	}
	pThis->m_hWnd = hWnd;

	// Initialize the thunk.  This is allocated in CWindowImplBaseT::Create,
	// so failure is unexpected here.

	pThis->m_thunk.Init(pThis->GetWindowProc(), pThis);
	WNDPROC pProc = pThis->m_thunk.GetWNDPROC();
	WNDPROC pOldProc = (WNDPROC)::SetWindowLongPtr(hWnd, GWLP_WNDPROC, (LONG_PTR)pProc);
#ifdef _DEBUG
	// check if somebody has subclassed us already since we discard it
	if(pOldProc != StartWindowProc)
		ATLTRACE(atlTraceWindowing, 0, _T("Subclassing through a hook discarded.\n"));
#else
	(pOldProc);	// avoid unused warning
#endif
	return pProc(hWnd, uMsg, wParam, lParam);
}
template <class TBase, class TWinTraits>
LRESULT CALLBACK CWindowImplBaseT< TBase, TWinTraits >::WindowProc(
    _In_ HWND hWnd,
    _In_ UINT uMsg,
    _In_ WPARAM wParam,
    _In_ LPARAM lParam)
{
    CWindowImplBaseT< TBase, TWinTraits >* pThis = (CWindowImplBaseT< TBase, TWinTraits >*)hWnd;
    // set a ptr to this message and save the old value
    _ATL_MSG msg(pThis->m_hWnd, uMsg, wParam, lParam);
    const _ATL_MSG* pOldMsg = pThis->m_pCurrentMsg;
    pThis->m_pCurrentMsg = &msg;
    // pass to the message map to process
    LRESULT lRes = 0;
    BOOL bRet = pThis->ProcessWindowMessage(pThis->m_hWnd, uMsg, wParam, lParam, lRes, 0);
    // restore saved value for the current message
    ATLASSERT(pThis->m_pCurrentMsg == &msg);

    // do the default processing if message was not handled
    if(!bRet)
    {
        if(uMsg != WM_NCDESTROY)
            lRes = pThis->DefWindowProc(uMsg, wParam, lParam);
        else
        {
            // unsubclass, if needed
            LONG_PTR pfnWndProc = ::GetWindowLongPtr(pThis->m_hWnd, GWLP_WNDPROC);
            lRes = pThis->DefWindowProc(uMsg, wParam, lParam);
            if(pThis->m_pfnSuperWindowProc != ::DefWindowProc && ::GetWindowLongPtr(pThis->m_hWnd, GWLP_WNDPROC) == pfnWndProc)
                ::SetWindowLongPtr(pThis->m_hWnd, GWLP_WNDPROC, (LONG_PTR)pThis->m_pfnSuperWindowProc);
            // mark window as destroyed
            pThis->m_dwState |= CWindowImplRoot<TBase>::WINSTATE_DESTROYED;
        }
    }
    if((pThis->m_dwState & CWindowImplRoot<TBase>::WINSTATE_DESTROYED) && pOldMsg== NULL)
    {
        // clear out window handle
        HWND hWndThis = pThis->m_hWnd;
        pThis->m_hWnd = NULL;
        pThis->m_dwState &= ~CWindowImplRoot<TBase>::WINSTATE_DESTROYED;
        // clean up after window is destroyed
        pThis->m_pCurrentMsg = pOldMsg;
        pThis->OnFinalMessage(hWndThis);
    }else {
        pThis->m_pCurrentMsg = pOldMsg;
    }
    return lRes;
}
  1. 注意,如果不在CWindowImpl子类的析构函数里调用DestroyWindow()方法,在父类的析构函数~CWindowImplRoot()里会进行m_hWnd成员变量的断言失败。
virtual ~CWindowImplRoot()
    {
#ifdef _DEBUG
        if(this->m_hWnd != NULL)    // should be cleared in WindowProc
        {
            ATLTRACE(atlTraceWindowing, 0, _T("ERROR - Object deleted before window was destroyed\n"));
            ATLASSERT(FALSE);
        }
#endif //_DEBUG
    }
  1. 完整的销毁子控件方法.
BASSwitchButton::~BASSwitchButton()
{
	if (m_hWnd) {
		DestroyWindow();
		//Detach(); // 不需要
		// delete 图片或其他
	}
}

例子

  1. 这里写了一个例子是动态创建和销毁按钮的例子。

bas_switch_button.h


#pragma once

#include <Windows.h>
#include <atlbase.h>
#include <atlapp.h>

#include <atlcrack.h>
#include <atlwin.h>
#include "atlframe.h"
#include <atlctrls.h>
#include <atlctrlx.h>
#include <atlmisc.h>
#include <GdiPlus.h>
#include <string>

class BASSwitchButton:public CWindowImpl<BASSwitchButton,CButton>
{
public:

	BEGIN_MSG_MAP_EX(BASSwitchButton)	
		MESSAGE_HANDLER(WM_CREATE, OnCreate)
		MESSAGE_HANDLER(WM_PAINT, OnPaint)
		MSG_WM_ERASEBKGND(OnEraseBkgnd)
		REFLECTED_NOTIFY_CODE_HANDLER(BCN_HOTITEMCHANGE,OnHotItemChange2)
		DEFAULT_REFLECTION_HANDLER()
	END_MSG_MAP()

	BASSwitchButton();
	~BASSwitchButton();

	void SetInitVisible(bool visible = true);
	HWND CreateButton(HWND hWndParent,_U_MENUorID MenuOrID = 0U);


	inline BASSwitchButton& SetRadio(bool radio)
	{
		is_radio_button_ = radio;
		return *this;
	}

	inline BASSwitchButton& SetHMargin(int hmargin)
	{
		hmargin_ = hmargin;
		return *this;
	}

	inline BASSwitchButton& SetVMargin(int vmargin)
	{
		vmargin_ = vmargin;
		return *this;
	}

	inline BASSwitchButton& SetColorDisable(DWORD disable_bkg_color,DWORD disable_font_color)
	{
		disable_bkg_color_ = disable_bkg_color;
		disable_font_color_ = disable_font_color;
		return *this;
	}

	inline BASSwitchButton& SetBackgroundColor(DWORD color)
	{
		color_bg_ = color;
		return *this;
	}

	inline BASSwitchButton& SetRoundRectPoint(CPoint point)
	{
		round_rect_point_ = point;
		return *this;
	}

	inline BASSwitchButton& SetRoundRect(bool is_round_rect)
	{
		is_round_rect_ = is_round_rect;
		return *this;
	}

	inline BASSwitchButton& SetColorBorder(DWORD color_border)
	{
		color_border_ = color_border;
		return *this;
	}

	BOOL Init(int nPosx,int nPosy,LPCWSTR text,
		COLORREF normal_bkg_color,COLORREF normal_font_color,
		COLORREF pressed_bkg_color,COLORREF pressed_font_color);

	void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);
	void DrawRect(Gdiplus::Graphics& graphics,Gdiplus::Rect rect,DWORD color);

	void ResetStatus();
	void SetPressedStatus();

	BOOL EnableWindow(_In_ BOOL bEnable = TRUE);
	void SetFont(Gdiplus::Font* font,bool deleteOld = true);

protected:
	LRESULT OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
	LRESULT OnHotItemChange2(int idCtrl, LPNMHDR pnmh, BOOL& bHandled);

	LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
	BOOL OnEraseBkgnd(CDCHandle dc);

private:
	inline void SetTextColor(COLORREF color){
		color_text_ = color;
	}
	void SetTextString(LPCWSTR text);

	int nPosx_;
	int nPosy_;
	bool is_radio_button_;
	Gdiplus::Font *font_;
	COLORREF color_text_;

	std::wstring text_;

	DWORD normal_bkg_color_;
	DWORD normal_font_color_;

	DWORD pressed_bkg_color_;
	DWORD pressed_font_color_;

	DWORD disable_bkg_color_;
	DWORD disable_font_color_;

	int hmargin_;
	int vmargin_;

	int button_width_;
	int button_height_;

	int status_;

	bool m_bTracking_;
	bool pressed_;

	bool is_round_rect_;
	CPoint round_rect_point_;

	COLORREF color_border_;
	COLORREF color_bg_;

	int scaling_; // 缩放系数
	bool visible_; // 是否创建可见窗口.
};

bas_switch_button.cpp



#include "stdafx.h"
#include "bas_switch_button.h"
#include <assert.h>
#include <iostream>
#include <sstream>
#include "utils.h"

namespace
{
	enum
	{
		kButtonStatusNormal,
		kButtonStatusPressed,
		kButtonStatusHover,
		kButtonStatusDisable
	};
}

void BASSwitchButton::SetInitVisible(bool visible)
{
	visible_ = visible;
}

BOOL BASSwitchButton::EnableWindow(BOOL bEnable)
{
	auto result = CWindowImpl::EnableWindow(bEnable);
	status_ = (bEnable)?kButtonStatusNormal:kButtonStatusDisable;
	// 1.需要再调用一次绘图事件绘制当前的状态。
	Invalidate(FALSE);
	UpdateWindow();
	return result;
}

BASSwitchButton::BASSwitchButton()
{
	disable_bkg_color_ = -1;
	disable_font_color_ = -1;
	color_bg_ = RGB(255,255,255);
	visible_= true;
	hmargin_ = 10;
	vmargin_ = 10;
	is_radio_button_ = true;
	color_border_ = -1;

	disable_bkg_color_ = RGB(127,127,127);
	disable_font_color_ = RGB(255,255,255);
	is_round_rect_ = false;
	round_rect_point_.x = 6.0;
	round_rect_point_.y = 6.0;

	font_ = NULL;
}

void BASSwitchButton::SetFont(Gdiplus::Font* font,bool deleteOld)
{
	if(deleteOld)
		delete font_;
	font_ = font;
}

BASSwitchButton::~BASSwitchButton()
{
	if (m_hWnd) {
		DestroyWindow();
		//Detach();
	}
}

void BASSwitchButton::SetPressedStatus()
{
	status_ = kButtonStatusPressed;
	Invalidate(FALSE);
}

void BASSwitchButton::ResetStatus()
{
	status_ = kButtonStatusNormal;
	Invalidate(FALSE);
}

void BASSwitchButton::DrawRect(Gdiplus::Graphics& graphics,Gdiplus::Rect rect,DWORD color)
{
	Gdiplus::GraphicsPath m_pPath;
	m_pPath.AddRectangle(rect);
	m_pPath.CloseFigure();
	Gdiplus::Color color_bg(GetRValue(color_bg_),GetGValue(color_bg_),
		GetBValue(color_bg_));
	Gdiplus::SolidBrush brush_bg(color_bg);
	graphics.FillPath(&brush_bg,&m_pPath);

	Gdiplus::Pen *pen = NULL;
	Gdiplus::Color gcolor(GetRValue(color),GetGValue(color),GetBValue(color));
	if(color_border_ == -1){
		pen = new Gdiplus::Pen(gcolor,1);
	}else{
		Gdiplus::Color color_border(GetRValue(color_border_),GetGValue(color_border_),
			GetBValue(color_border_));
		pen = new Gdiplus::Pen(color_border,1);
	}

	Gdiplus::SolidBrush brush_color(gcolor);
	Gdiplus::GraphicsPath path_border;
	if(is_round_rect_){
		//Utils::CreateRoundRect(path_border,rect,round_rect_point_.x);
	}else{
		path_border.AddRectangle(rect);
		path_border.CloseFigure();
	}
	graphics.FillPath(&brush_color,&path_border);

	if(color_border_ != -1)
		graphics.DrawPath(pen,&path_border);
}

LRESULT BASSwitchButton::OnHotItemChange2(int idCtrl, LPNMHDR pnmh, BOOL& bHandled)
{
	//OutputDebugString(L"OnHotItemChange \n");
	NMBCHOTITEM* item = (NMBCHOTITEM*)pnmh;
	if(item->dwFlags & HICF_ENTERING){
		//OutputDebugString(L"HICF_ENTERING \n");
		if (!is_radio_button_){
			status_ = kButtonStatusHover;
		}else if(status_ != kButtonStatusPressed){
			status_ = kButtonStatusHover;
		}
	}else{
		status_ = kButtonStatusNormal;
		//OutputDebugString(L"HICF_LEAVING \n");
	}
	SetMsgHandled(FALSE);
	return 0;
}

void BASSwitchButton::SetTextString(LPCWSTR text)
{
	text_ = text;

	Gdiplus::Graphics graphics(m_hWnd);
	Gdiplus::RectF rect_text;
	graphics.MeasureString(text_.c_str(),text_.size(),font_,
		Gdiplus::PointF(0,0),&rect_text);

	button_width_ = rect_text.Width+hmargin_*2;
	button_height_ = rect_text.Height + vmargin_*2;
}

LRESULT BASSwitchButton::OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
	// Ellipse
	CPaintDC hdc(m_hWnd);
	
	CRect rect;
	GetClientRect(&rect);
	Gdiplus::Bitmap bmp(int(rect.Width()),int(rect.Height()));
	Gdiplus::Graphics graphics(&bmp);
	graphics.SetTextRenderingHint(Gdiplus::TextRenderingHintSystemDefault);
	graphics.SetSmoothingMode(Gdiplus::SmoothingModeAntiAlias);

	// 按钮窗口创建可能有1像素的边框区域,需要夸扩大左上区域1像素才可以把边界绘制到.
	// 不清楚原因.
	rect.InflateRect(1,1,0,0);
	Gdiplus::Rect client_rect(rect.left,rect.top,rect.Width(),rect.Height());
	switch(status_)
	{
	case kButtonStatusNormal:
		{
			SetTextColor(normal_font_color_);
			DrawRect(graphics,client_rect,normal_bkg_color_);
			break;
		}
	case kButtonStatusPressed:
		{
			SetTextColor(pressed_font_color_);
			DrawRect(graphics,client_rect,pressed_bkg_color_);
			break;
		}
	case kButtonStatusHover:
		{
			SetTextColor(pressed_font_color_);
			DrawRect(graphics,client_rect,pressed_bkg_color_);
			break;
		}
	case kButtonStatusDisable:
		{
			SetTextColor(disable_font_color_);
			DrawRect(graphics,client_rect,disable_bkg_color_);
			break;
		}
	}

	if(text_.size()){
		Gdiplus::RectF rect(client_rect.X,client_rect.Y,client_rect.Width,client_rect.Height);
		Gdiplus::Color color_text;
		color_text.SetFromCOLORREF(color_text_);
		Utils::DrawTextCenter(graphics,rect,text_.c_str(),font_,&color_text);
	}

	///*Drawing on DC*/
	Gdiplus::Graphics graphics1(hdc);
	///*Important! Create a CacheBitmap object for quick drawing*/
	Gdiplus::CachedBitmap cachedBmp(&bmp,&graphics1);
	graphics1.DrawCachedBitmap(&cachedBmp,0,0);

	bHandled = TRUE;
	return 0;
}

HWND BASSwitchButton::CreateButton(HWND hWndParent,_U_MENUorID MenuOrID)
{
	m_bTracking_ = false;
	status_ = kButtonStatusNormal;

	// BS_NOTIFY WS_CLIPCHILDREN
	int flag = WS_CHILD | WS_CLIPCHILDREN ;
	if(visible_)
		flag |= WS_VISIBLE;

	
	return Create(hWndParent,NULL,L"",flag,0,MenuOrID);
}

BOOL BASSwitchButton::OnEraseBkgnd(CDCHandle dc)
{
	return TRUE;
}

LRESULT BASSwitchButton::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
	Gdiplus::FontFamily fontFamily(L"Arial");
	font_ = new Gdiplus::Font(&fontFamily,16,
			(false)?Gdiplus::FontStyleBold:Gdiplus::FontStyleRegular,Gdiplus::UnitPixel);
	color_text_ = RGB(0,0,0);
	bHandled = FALSE;
	return 0;
}

BOOL BASSwitchButton::Init(int nPosx,int nPosy,LPCWSTR text,
		COLORREF normal_bkg_color,COLORREF normal_font_color,
		COLORREF pressed_bkg_color,COLORREF pressed_font_color)
{
	normal_bkg_color_ = normal_bkg_color;
	normal_font_color_ = normal_font_color;
	pressed_bkg_color_ = pressed_bkg_color;
	pressed_font_color_ = pressed_font_color;

	nPosx_ = nPosx;
	nPosy_ = nPosy;

	SetTextString(text);

	HWND wndparent=::GetParent(m_hWnd);
	RECT rcClient;
	::GetClientRect(wndparent,&rcClient);

	if (nPosx<0)
		nPosx=rcClient.right+nPosx-button_width_;
	
	if (nPosy<0)
		nPosy=rcClient.bottom+nPosy-button_height_;

	SetWindowPos(NULL,nPosx,nPosy,button_width_,button_height_,SWP_FRAMECHANGED);
	return 0;
}

View.h

// View.h : interface of the CView class
//
/

#ifndef VIEW_H
#define VIEW_H

#include <vector>

class BASSwitchButton;
class BASCOwnerDrawButton;

namespace Gdiplus{
	class Font;
};

typedef enum ViewWindowId1 {
	kViewWindowButtonMinId = WM_USER + 1000,
	kViewWindowButtonMaxId = kViewWindowButtonMinId + 5000,
	kViewWindowButtonAddButtonId,
	kViewWindowButtonRemoveButtonId
}ViewWindowId;


class CView : public CWindowImpl<CView>
{
public:
	DECLARE_WND_CLASS(NULL)
	~CView();
	BOOL PreTranslateMessage(MSG* pMsg);

	BEGIN_MSG_MAP_EX(CView)
		MSG_WM_CREATE(onCreate)
		COMMAND_ID_HANDLER(kViewWindowButtonAddButtonId,onAddButton)
		COMMAND_ID_HANDLER(kViewWindowButtonRemoveButtonId,onDeleteButton)
		MESSAGE_HANDLER(WM_PAINT, onPaint)
		MESSAGE_HANDLER(WM_CTLCOLORSTATIC, OnCtlColor)
		REFLECT_NOTIFICATIONS()
	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*/)

protected:
	LRESULT OnCtlColor(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);

	LRESULT onPaint(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/);

	int onCreate(LPCREATESTRUCT lpCreateStruct);

	LRESULT onAddButton(WORD wNotify,WORD wID,HWND hCtrl, BOOL &bHandeld);

	LRESULT onDeleteButton(WORD wNotify,WORD wID,HWND hCtrl, BOOL &bHandeld);

	void updateLayout();

private:
	Gdiplus::Font *font_16_normal_gdi_;

	CButton buttonAdd_;

	CButton buttonRemove_;

	std::vector<BASSwitchButton*> testButtons_;

	int buttonId_ = kViewWindowButtonMinId;
};

#endif

View.cpp

// View.cpp : implementation of the CView class
//
/

#include "stdafx.h"
#include "resource.h"
#include <GdiPlus.h>
#include "View.h"
#include "base/bas_switch_button.h"
#include <functional>

CView::~CView()
{
}

BOOL CView::PreTranslateMessage(MSG* pMsg)
{
	pMsg;
	return FALSE;
}

LRESULT CView::OnCtlColor(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
	HWND hWnd = (HWND)lParam;
	HDC hdc = (HDC)wParam;
	
	bHandled = FALSE;
	return 0;
}

int CView::onCreate(LPCREATESTRUCT lpCreateStruct)
{
	auto color_normal = RGB(13,164,230);
	buttonAdd_.Create(m_hWnd,CRect(100,100,240,160),L"Add Button",
		WS_CHILD | WS_CLIPCHILDREN | WS_VISIBLE,0,kViewWindowButtonAddButtonId);
	buttonRemove_.Create(m_hWnd,CRect(300,100,440,160),L"Remove Button",
		WS_CHILD | WS_CLIPCHILDREN | WS_VISIBLE,0,kViewWindowButtonRemoveButtonId);

	return 0;
}

LRESULT CView::onPaint(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
	CPaintDC dc(m_hWnd);

	//TODO: Add your drawing code here

	return 0;
}

LRESULT CView::onAddButton(WORD wNotify,WORD wID,HWND hCtrl, BOOL &bHandeld)
{
	auto _id = buttonId_++;

	auto color_normal = RGB(13,164,230);
	auto button = new BASSwitchButton();
	button->SetInitVisible(true);
	button->CreateButton(m_hWnd,_id);
	button->SetHMargin(30)
		.SetVMargin(8)
		.SetRadio(false)
		.SetColorBorder(color_normal)
		.SetBackgroundColor(RGB(255,255,255));

	std::wstring name(L"Button_");
	name.append(std::to_wstring(_id));

	button->Init(0,0,name.c_str(),color_normal,RGB(255,255,255),
		RGB(234,245,249),RGB(0,0,0));

	testButtons_.push_back(button);
	updateLayout();

	return 0;
}

LRESULT CView::onDeleteButton(WORD wNotify,WORD wID,HWND hCtrl, BOOL &bHandeld)
{
	if (testButtons_.empty())
		return 0;

	auto last = testButtons_.back();
	testButtons_.pop_back();
	delete last;

	updateLayout();
	return 0;
}

void CView::updateLayout()
{
	CRect rect;
	GetClientRect(&rect);

	static const int marginx = 100;
	static const int marginy = 300;
	static const int gap = 20;

	int buttonx = marginx;
	int buttony = marginy;
	int maxRight = rect.right - marginx;

	CRect rectButton;
	for (auto i = 0; i < testButtons_.size(); ++i) {
		auto one = testButtons_[i];
		
		::GetClientRect(*one, &rectButton);
		if ((buttonx + rectButton.Width()) > maxRight) {
			buttonx = marginx;
			buttony = rectButton.Height() + buttony + gap;
		}

		rectButton.MoveToXY(buttonx, buttony);
		one->MoveWindow(rectButton, FALSE);

		buttonx += (rectButton.Width() + gap);
	}
}

运行图示

在这里插入图片描述

参考

  1. DestroyWindow 函数winuser.h

http://www.kler.cn/a/456463.html

相关文章:

  • [创业之路-229]:《华为闭环战略管理》-5-平衡记分卡与战略地图
  • Kali Linux系统上配置Git的全局代理
  • solr9.7 单机安装教程
  • c++入门——c++输入cin和输出cout的简单使用
  • RT-Thread中堆和栈怎么跟单片机内存相联系
  • Python爬虫完整代码拿走不谢
  • Axure RP 8安装(内带安装包)
  • python 打印圣诞树
  • AI笔记-查漏补缺
  • 3.4欧拉角插补
  • Datawhale-AI冬令营二期
  • leetcode hot 100 单词搜索
  • 【Axure高保真原型】输入框控制标签
  • 探索Spring Cloud Config:构建高可用的配置中心
  • 5.npm包
  • 如何配置线程池参数,才能创建性能最好、最稳定的Spring异步线程池?
  • StarRocks元数据无法合并
  • 力扣-数据结构-5【算法学习day.76】
  • Spring 框架基础知识
  • 【设计模式学习笔记】1. 设计模式概述
  • 系统设计及解决方案
  • EndtoEnd Object Detection with Transformers
  • BOOST 库在缺陷检测领域的应用与发展前景
  • 1、redis的基础知识和类型
  • Docker部署neo4j
  • JDBC(Tomcat)