前面的三章几缭,我們說了多態(tài)的一些技術(shù)內(nèi)幕還有一些關(guān)于C++對(duì)象模型的內(nèi)容想许,所以我就在想是要繼續(xù)深入C++的知識(shí)點(diǎn)呢還是就目前的內(nèi)容我們來聊聊如何來設(shè)計(jì)一個(gè)應(yīng)用程序伶授,最后選擇了后者,這一章的內(nèi)容我們來說說如何搭建一個(gè)GUI框架流纹,由于GUI框架涉及到方方面面糜烹,所以我們這里只能算是一個(gè)簡(jiǎn)單的切入點(diǎn),不涉及詳細(xì)的編碼的實(shí)現(xiàn)漱凝。
GUI框架很多疮蹦,在windows上面C++有MFC,WTL茸炒,還有跨平臺(tái)的Qt等等愕乎,我們可以隨便找一個(gè)來作為參考,有了參考之后我們還需要對(duì)我們的框架的模塊規(guī)劃壁公。
我們打算寫一個(gè)DirectUI框架感论, 所以我們需要一個(gè)窗口——CDxWindowWnd。
創(chuàng)一個(gè)小群紊册,供大家學(xué)習(xí)交流聊天
如果有對(duì)學(xué)C++方面有什么疑惑問題的比肄,或者有什么想說的想聊的大家可以一起交流學(xué)習(xí)一起進(jìn)步呀。
也希望大家對(duì)學(xué)C++能夠持之以恒
C++愛好群囊陡,
如果你想要學(xué)好C++最好加入一個(gè)組織芳绩,這樣大家學(xué)習(xí)的話就比較方便,還能夠共同交流和分享資料撞反,給你推薦一個(gè)學(xué)習(xí)的組織:快樂學(xué)習(xí)C++組織 可以點(diǎn)擊組織二字妥色,可以直達(dá)
CDxWindowWnd,作為我們的基本窗口類痢畜,該類我們只需要對(duì)HWND進(jìn)行簡(jiǎn)單的包裝垛膝。
//+--------------------------
//
// class CDxWindowWnd
// windows的基本窗口、
//
//
class CDxWindowWnd{
public:
CDxWindowWnd();
virtual ~CDxWindowWnd();
operator HWND() const;
void ShowWindow(bool bShow = true, bool bTakeFocus = true);
UINT ShowModal();
virtual void CloseHwnd(UINT nRet = IDOK);
void CenterWindow();
protected:
virtual LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam);
private:
HWND m_Hwnd{nullptr};
};
//+-------------------------------
窗口有了丁稀,我們需要一個(gè)消息循環(huán)吼拥,對(duì)于windows應(yīng)用程序來說寫個(gè)消息循環(huán)很簡(jiǎn)單:
//+--------------------------------
MSG msg = { 0 };
while (::GetMessage(&msg, NULL, 0, 0)) {
::TranslateMessage(&msg);
::DispatchMessage(&msg);
if (msg.message == WM_CLOSE && ::GetParent(msg.hwnd) == NULL)
break;
}
//+-------------------------------
雖然這幾句代碼可以實(shí)現(xiàn)我們所要的消息循環(huán),但是我們可以將該消息循環(huán)進(jìn)行封裝线衫,并且將該模塊作為單例凿可,這樣一樣我們可以在里面進(jìn)行更多的操作:
//+-------------------------------
//
// class CDxApplication?
// 負(fù)責(zé)窗口程序的消息循環(huán)
// 以即一些公有資料的管理
//
class CDxApplication{
public:
CDxApplication(HINSTANCE __hInstance = nullptr);
~CDxApplication();
static CDxApplication* InstanceWithArgs(HINSTANCE __hInstance);
static CDxApplication* Instance();
static void Destroy();
static void SetFont(const MString& fontName, unsigned fSize, bool isBold = false, bool isItalic = false);
static HINSTANCE GetHInstance();
static HFONT GetFont();
static DXFontInfo GetFontInfo();
static MString GetExePath();
static MString GetCurrentTime();
static MString GetUUIDStr();
//
// 消息循環(huán)
//
void Quit();
int Run();
protected:
static CDxApplication* ?__sPtr;
};
//
// 現(xiàn)在我們可以直接創(chuàng)建窗口并顯示出來
//
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE prevInstance, LPWSTR cmdLine, int cmdShow)
{
DxUI::CDxApplication* App = DxUI::CDxApplication::InstanceWithArgs(hInstance);
DxUI::CDxWindowWnd WndWindow;
WndWindow.Create(nullptr, L"TestWindow", DXUI_WNDSTYLE_FRAME, 0);
WndWindow.ShowWindow();
App->Run();
DxUI::CDxApplication::Destroy();
return 0;
}
//+--------------------------------
窗口我們創(chuàng)建出來了,但不符我們的DirectUI的預(yù)期,我們需要將標(biāo)題欄去掉枯跑,所以我們可以在CDxWindowWnd的基礎(chǔ)上進(jìn)一步修改:
//+--------------------------------
class CDxWindowImpl : public CDxWindowWnd
{
public:
CDxWindowImpl();
~CDxWindowImpl();
virtual LRESULT OnNcHitTest(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
virtual LRESULT OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
virtual LRESULT OnSysCommand(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
virtual LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
virtual LRESULT OnLButtonDown(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled);
LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam); // 從基類基礎(chǔ)而來
//
// 其他
//
protected:
int mIdentifyId{ DX_InvalidID };
bool bIsVisible{ true };
bool bIsZoomable{ true };
RECT mCaptionBox;
RECT mSizeBox;
SIZE mMaxSize;
SIZE mMinSize;
SIZE mRoundRectSize;
};
//+-------------------------------
修改窗口風(fēng)格我們放在OnCreate函數(shù)中進(jìn)行實(shí)現(xiàn):
//+------------------------------
LRESULT CDxWindowImpl::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
//+-------------------
//
// 調(diào)整窗口樣式
//
//+--------------------
LONG styleValue = ::GetWindowLong(*this, GWL_STYLE);
styleValue &= ~WS_CAPTION;
::SetWindowLong(*this, GWL_STYLE, styleValue | WS_CLIPSIBLINGS | WS_CLIPCHILDREN);
return 0;
}
//+------------------------------
當(dāng)然惨驶,如果我們就這樣把標(biāo)題欄去掉之后,窗口就沒法拉動(dòng)敛助,也沒法關(guān)閉粗卜,就一直停在桌面上,一動(dòng)不動(dòng)纳击,所以為了解決這個(gè)問題续扔,我們必須換種方式把標(biāo)題欄給重新繪制出來,這就是 OnNcHitTest 的功勞了纱昧。
//+-----------------------------
LRESULT CDxWindowImpl::OnNcHitTest(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
POINT pt;
pt.x = GET_X_LPARAM(lParam);
pt.y = GET_Y_LPARAM(lParam);
::ScreenToClient(*this, &pt);
RECT rcClient;
::GetClientRect(*this, &rcClient);
if (!::IsZoomed(*this) && bIsZoomable)
{
RECT rcSizeBox = mSizeBox;
if (pt.y < rcClient.top + rcSizeBox.top)
{
if (pt.x < rcClient.left + rcSizeBox.left) return HTTOPLEFT;
if (pt.x > rcClient.right - rcSizeBox.right) return HTTOPRIGHT;
return HTTOP;
}
else if (pt.y > rcClient.bottom - rcSizeBox.bottom)
{
if (pt.x < rcClient.left + rcSizeBox.left) return HTBOTTOMLEFT;
if (pt.x > rcClient.right - rcSizeBox.right) return HTBOTTOMRIGHT;
return HTBOTTOM;
}
if (pt.x < rcClient.left + rcSizeBox.left) return HTLEFT;
if (pt.x > rcClient.right - rcSizeBox.right) return HTRIGHT;
}
RECT rcCaption = mCaptionBox;
if (-1 == rcCaption.bottom)
{
rcCaption.bottom = rcClient.bottom;
}
if (pt.x >= rcClient.left + rcCaption.left && pt.x < rcClient.right - rcCaption.right
&& pt.y >= rcCaption.top && pt.y < rcCaption.bottom)
{
return HTCAPTION;
}
return HTCLIENT;
}
//+-------------------------------------
標(biāo)題欄的關(guān)鍵在于mCaptionBox的bottom的值,所以我們可以設(shè)置mCaptionBox來修改標(biāo)題欄的高度洒扎。
我們現(xiàn)在雖然得到了我們想要的無標(biāo)題欄窗口邓线,但是這只是一張白板,所以我們還需要對(duì)齊進(jìn)行繪制,我們稱該層為繪制資源管理層:
//+-------------------------------------
class CDxRendImpl : public CDxWindowImpl
{
public:
CDxRendImpl();
~CDxRendImpl();
virtual bool OnInitResource2D();
virtual bool OnInitResource3D();
virtual void UnInitResource();
virtual void OnRender();
virtual void OnRender2D();
virtual void OnRender3D();
virtual void SaveToFile(const MString& fileName);
virtual void OnRendWindow(IPainterInterface* painter);
};
//+--------------------------------------
我們想要繪制那么我們就需要一個(gè)繪制模塊,繪制的時(shí)候我們還需要效果挡篓,所以我們還需要兩個(gè)模塊:
//+-------------------------------------
//
// 效果接口
//
class CDxEffects
{
///
/// 多種效果
///
};
//
// 二維平面變換矩陣
//
struct TransformMatrix{
FLOAT _11;
FLOAT _12;
FLOAT _21;
FLOAT _22;
FLOAT _31;
FLOAT _32;
};
//
// 繪圖接口
//
class IPainterInterface{
public:
//+-------------------------------------------
//
// 為繪制方便官研,下面的接口都得實(shí)現(xiàn)
// 當(dāng)然如果實(shí)際沒有用處的可以簡(jiǎn)單的實(shí)現(xiàn)即可
//
//+-------------------------------------------
virtual ~IPainterInterface(){};
virtual void BeginDraw() = 0;? // 開始繪制
virtual void Clear(const DxColor& col) = 0; // 使用特定色彩擦除背景
virtual void EndDraw() = 0;? // 結(jié)束繪制
virtual void DrawRectangle(const RECT& rc, const DxColor& col,double size) = 0;
virtual void DrawRoundedRectangle(const RECT& rc, const SIZE& radius, const DxColor& col, double size) = 0;
virtual void DrawEllipse(const RECT& rc, const SIZE& radius, const DxColor& col, double size) = 0;
virtual void DrawDashRectangle(const RECT& rc, const DxColor& col, double size) = 0;
virtual void DrawDashRoundedRectangle(const RECT& rc, const SIZE& radius, const DxColor& col, double size) = 0;
virtual void DrawDashEllipse(const RECT& rc, const SIZE& radius, const DxColor& col, double size) = 0;
virtual void FillRectangle(const RECT& rc, const DxColor& col) = 0;
virtual void FillRoundedRectangle(const RECT& rc, const SIZE& radius, const DxColor& col) = 0;
virtual void FillEllipse(const RECT& rc, const SIZE& radius, const DxColor& col) = 0;
virtual void FillRectangle(const RECT& rc, CDxEffects* pEffects) = 0;
virtual void FillRoundedRectangle(const RECT& rc, const SIZE& radius, CDxEffects* pEffects) = 0;
virtual void FillEllipse(const RECT& rc, const SIZE& radius, CDxEffects* pEffects) = 0;
virtual void DrawBitmap(const RECT& rc, CDxEffects* pEffects) = 0;
virtual void DrawBitmap(const RECT& rc, const MString& bitmap,int w = -1,int h = -1) = 0;
virtual void DrawText(const MString& Text, const RECT& rc, CDxEffects* pEffects) = 0;? // 只繪制文本,超出區(qū)域不管 效率更高
virtual void DrawText(const MString& Text, const RECT& rc, const DxColor& col, const DXFontInfo& font,DXAlignment alignt) = 0; // 不使用效果直接繪制
virtual void DrawTextWithClip(const MString& Text, const RECT& rc, CDxEffects* pEffects, const RECT& cliprc = { 0, 0, 0, 0 }) = 0; // 繪制文本闯睹,超出區(qū)域裁剪
virtual void BeginClipRect(const RECT& rc) = 0;? // 在繪制之前調(diào)用
virtual void BeginClipRect(const std::vector& points) = 0;? // 在繪制之前調(diào)用
virtual void EndClipRect() = 0; // 在繪制完成之后調(diào)用
//
// 繪制線體
// DrawLines效率更高
//
virtual void DrawLine(const DxPointD& first, const DxPointD& second, const DxColor& col, double Size) = 0;
virtual void DrawLines(const std::vector& points, const DxColor& col, double Size) = 0;
virtual void DrawDashLine(const DxPointD& first, const DxPointD& second, const DxColor& col, double Size) = 0;
virtual void DrawDashLines(const std::vector& points, const DxColor& col, double Size) = 0;
//
// 變換
//
virtual void SetTransform(const TransformMatrix& mat) = 0;
};
//+---------------------------
IPainterInterface?是一個(gè)純虛類戏羽,換句話說就是接口類,該類沒有對(duì)他的子類提供任何便利楼吃,反而要求子類必須實(shí)現(xiàn)自己定義的所有純虛接口始花,而所謂純虛接口就是虛函數(shù)等于0的函數(shù),而只要含有純虛函數(shù)的類就是純虛類孩锡,也就是所謂的接口類酷宵,之所以我們這里要將繪制操作定義為純虛類主要是考慮到以后可能會(huì)使用不同的圖像引擎來繪制圖形,比如我們這里可以使用D2D躬窜,也可以使用OpenGL浇垦,還可以使用GDI等等,那么我們?yōu)槭裁茨軌蛲瑫r(shí)展示二維和三維圖形荣挨,所以我們選擇D2D:
//+---------------------------
class CDxPainter : public IPainterInterface
{
public:
typedef ID2D1RenderTarget* LPID2D1HwndRenderTarget;
public:
CDxPainter(ID2D1RenderTarget* render);
~CDxPainter();
ID2D1RenderTarget* getRenderTarget() const;
ID2D1RenderTarget* operator->() const;
operator LPID2D1HwndRenderTarget() const;
operator bool() const;
//
// 下面是 IPainterInterface 繼承而來的所有接口
//
private:
ID2D1RenderTarget* pRenderTarget;
CDxCharaterFormat* pTextRender{ nullptr };
ID2D1Layer* p_ClipLayout{ nullptr };
ID2D1Geometry* p_ClipGeometry{ nullptr };
};
//+------------------------------
到現(xiàn)在我們已經(jīng)擁有了窗口男韧,效果,繪圖三個(gè)模塊默垄,所以我們只需要將三個(gè)模塊組合起來就可以進(jìn)行圖形繪制此虑,這個(gè)操作我們放在CDxRendImpl::OnRender()中,不過CDxRendImpl::OnRender()中我們默認(rèn)繪制2D平面口锭,所以真正的操作我們放在CDxRendImpl::OnRender2D()中:
//+------------------------------
void CDxRendImpl::OnRender(){
OnRender2D();
}
void CDxRendImpl::OnRender2D(){
CDxPainter painter(pRendTarget);
painter.BeginDraw();
this->OnRendWindow(&painter);
painter.EndDraw();
}
void CDxRendImpl::OnRendWindow(IPainterInterface* painter){
;
}
//+-------------------------------
事實(shí)上我們并不對(duì)該層進(jìn)行渲染寡壮,所以該層的OnRendWindow函數(shù)被實(shí)現(xiàn)為空,因?yàn)槲覀冃枰氖亲鲆粋€(gè)DirectUI框架,而我們現(xiàn)在還是基于窗口HWND的况既,所以我們還差我們的DirectUI窗口这溅,當(dāng)然DirectUI至少需要一個(gè)持有HWND,所以我們必須繼承至CDxRendImpl.
//+-------------------------------
//
// DirectUI 窗口類
//
class CDxWidget : public? CDxRendImpl
{
public:
CDxWidget();
~CDxWidget();
//+---------------------------
//
// 創(chuàng)建Hwnd
//
//+---------------------------
virtual void CreateHwnd();
virtual void CreateHwnd(HWND parent);
//
// 其他
//
};
//+---------------------------------
我們可以通過CDxWidget::CreateHwnd()決定是否需要?jiǎng)?chuàng)建HWND棒仍,我們只對(duì)主窗口創(chuàng)建HWND對(duì)于子窗口不創(chuàng)建悲靴,在渲染的時(shí)候我們先渲染當(dāng)前窗口,再對(duì)子窗口進(jìn)行渲染莫其。
//+--------------------------------
//
// 繪制窗口
//
void CDxWidget::OnRendWindow(IPainterInterface* painter){
if (bIsVisible == false){
return;
}
mEffects.SetCurrentStatus(GetWindowStatus());
//+---------------
//
// 渲染Title
//
//+--------------
if (mHwnd && !pCaptionLabel&& !::GetParent(mHwnd)){
pCaptionLabel = new CDxCaption;
pCaptionLabel->SetParent(this);
RECT rc = mFrameArea;
rc.bottom =? mCaptionBox.bottom;
pCaptionLabel->SetGeomety(rc);
mRendArea = mFrameArea;
mRendArea.X(mFrameArea.X() + mSizeBox.left);
mRendArea.Y(mRendArea.Y() + mCaptionBox.bottom);
mRendArea.Width(mFrameArea.Width() - mSizeBox.left - mSizeBox.right);
mRendArea.Height(mFrameArea.Height() - mCaptionBox.bottom - mSizeBox.bottom);
if (!mIcon.empty()){
pCaptionLabel->GetIconEffects()->SetBitmaps(Dx_Normal, mIcon);
}
UpdateChildWindowPos();
}
if (pCaptionLabel){
RECT rc = mFrameArea;
rc.bottom = rc.top + pCaptionLabel->GetFrameRect().Height();
pCaptionLabel->SetGeomety(rc);
pCaptionLabel->SetText(mTitle);
pCaptionLabel->OnRendWindow(painter);
}
if (mEffects.GetEffectType() == CDxEffects::Dx_ImageType){
painter->DrawBitmap(mImageRendArea, &mEffects);
}
else if (mEffects.GetEffectType() == CDxEffects::Dx_ColorType){
DXShape shape = GetWindowShape();
switch (shape)
{
case DxUI::Dx_Rectangle:
painter->FillRectangle(mRendArea, &mEffects);
break;
case DxUI::Dx_RoundedRectangle:
painter->FillRoundedRectangle(mRendArea, mRoundRectSize, &mEffects);
if (bIsNeedBorder && mBorderWidth > 0){
painter->DrawRoundedRectangle(mRendArea, mRoundRectSize, mBorderColor, mBorderWidth);
}
break;
case DxUI::Dx_Ellipse:
painter->FillEllipse(mRendArea, mRoundRectSize, &mEffects);
break;
default:
break;
}
}
else{
DXShape shape = GetWindowShape();
switch (shape)
{
case DxUI::Dx_Rectangle:
painter->FillRectangle(mRendArea, &mEffects);
painter->DrawBitmap(mImageRendArea, &mEffects);
break;
case DxUI::Dx_RoundedRectangle:
painter->FillRoundedRectangle(mRendArea, mRoundRectSize, &mEffects);
painter->DrawBitmap(mImageRendArea, &mEffects);
break;
case DxUI::Dx_Ellipse:
painter->FillEllipse(mRendArea, mRoundRectSize, &mEffects);
painter->DrawBitmap(mImageRendArea, &mEffects);
break;
default:
break;
}
}
if (!mText.empty() ){
painter->DrawText(mText, mTextRendArea, &mEffects);
}
if (pLayout){
pLayout->OnRendWindow(painter);
}
//+-----------------------------
//
// 渲染子窗口
//
//+-----------------------------
if (!mChildList.empty()){
UpdateChildWindowPos();
for (auto& window : mChildList){
CDxWidget*& windowref = window.ref();
if (windowref->GetHwnd() == nullptr){
windowref->OnRendWindow(painter);
}
}
}
if (bIsNeedBorder){
RECT rc = mRendArea;
rc.left += 1;
rc.right -= 1;
rc.top += 1;
rc.bottom -= 1;
if (mEffects.GetEffectType() == CDxEffects::Dx_ImageType){
painter->DrawRectangle(rc, mBorderColor, 1);
}
else{
DXShape shape = GetWindowShape();
switch (shape)
{
case DxUI::Dx_Rectangle:
painter->DrawRectangle(rc, mBorderColor, 1);
break;
case DxUI::Dx_RoundedRectangle:
painter->DrawRoundedRectangle(rc, mRoundRectSize, mBorderColor, 1);
break;
case DxUI::Dx_Ellipse:
painter->DrawEllipse(rc, mRoundRectSize, mBorderColor, 1);
break;
default:
break;
}
}
}
if (!bIsEnabel){
DXShape shape = GetWindowShape();
mEffects.SetCurrentStatus(Dx_Disable);
mEffects.SetDisabelColor(mDisabelColor);
switch (shape)
{
case DxUI::Dx_Rectangle:
painter->FillRectangle(mRendArea, &mEffects);
break;
case DxUI::Dx_RoundedRectangle:
painter->FillRoundedRectangle(mRendArea, mRoundRectSize, &mEffects);
break;
case DxUI::Dx_Ellipse:
painter->FillEllipse(mRendArea, mRoundRectSize, &mEffects);
break;
default:
break;
}
}
}
//+----------------------------------
顯然對(duì)CDxRendImpl::OnRender2D()進(jìn)行完善:
//+---------------------------------
void CDxRendImpl::OnRender2D(){
CDxWidget* window = dynamic_cast(this);
if (window->IsNeedRender() == false)
return;
if (pRendTarget && window){
pRendTarget->BeginDraw();
pRendTarget->Clear(ToD2DColor(window->GetBackGroundColor()));
window->OnRendWindow(pPainter);
if(window->GetWindowSelfDesc() == Dx_PopWindow)
pPainter->DrawRoundedRectangle(window->GetFrameRect(), window->GetRoundRectSize(), RgbI(128, 128, 128), 2);
HRESULT hr = pRendTarget->EndDraw();
if (FAILED(hr)){
DxTRACE(L"渲染出錯(cuò)[%1]\n", hr);
}
}
else{
if (window->GetWindowSelfDesc() == Dx_Layout){
if (window->GetParent()){
window->GetParent()->OnRender2D();
return;
}
}
else if (window && window->IsVisible() ){
if (window->GetOpacity() < 0.99999999){
if (window->GetParent()){
window->GetParent()->OnRender2D();
return;
}
}
DxColor col = window->GetEraseColor();
CDxWidget* parent = window->GetParent();
if (col.rgb.a == 0 || (parent && parent->HasFloatWindow())){
if (parent){
parent->OnRender2D();
return;
}
}
auto render = GetRenderTarget();
if (render){
CDxPainter* painter = nullptr;
if (g_PainterMap.count(render)){
painter = g_PainterMap.at(render);
}
else{
painter = new CDxPainter(render);
g_PainterMap[render] = painter;
}
render->BeginDraw();
ID2D1Layer* p_ClipLayout{ nullptr };
ID2D1Geometry* p_ClipGeometry{ nullptr };
safe_release(p_ClipGeometry);
safe_release(p_ClipLayout);
render->CreateLayer(&p_ClipLayout);
RECT rc = window->GetInvalidateRect();
p_ClipGeometry = CDxResource::CreateRectGeometry(rc);
if (p_ClipLayout == nullptr || p_ClipGeometry == nullptr)
return;
render->PushLayer(D2D1::LayerParameters(
D2D1::InfiniteRect(),
p_ClipGeometry,
D2D1_ANTIALIAS_MODE_PER_PRIMITIVE,
D2D1::IdentityMatrix(),
1.0f,
NULL,
D2D1_LAYER_OPTIONS_NONE
), p_ClipLayout);
render->Clear(ToD2DColor(col));
window->OnRendWindow(painter);
render->PopLayer();
safe_release(p_ClipGeometry);
safe_release(p_ClipLayout);
HRESULT hr = render->EndDraw();
if (FAILED(hr)){
DxTRACE(L"渲染出錯(cuò)[%1]\n", hr);
}
}
}
}
}
//+---------------------------------
我們現(xiàn)在我們可以通過子類化CDxWidget實(shí)現(xiàn)各種控件癞尚,實(shí)現(xiàn)不同的效果,最終我們可以很簡(jiǎn)單的編寫出各種樣式的界面乱陡。
這一章的重點(diǎn)并非是在這個(gè)GUI框架的實(shí)現(xiàn)上浇揩,而是主要讓我們對(duì)class,繼承和多態(tài)的使用有進(jìn)一步的理解憨颠,以及對(duì)純虛類的引入胳徽,當(dāng)然至于對(duì)該框架感興趣的同學(xué),我們會(huì)在后續(xù)的內(nèi)容里面一點(diǎn)點(diǎn)的引入爽彤,畢竟里面有很多模板的東西养盗,現(xiàn)在一下子拿出來很多東西可能會(huì)比較吃力,所以這一章的內(nèi)容還是在對(duì)C++程序設(shè)計(jì)上的一些理解适篙,怎么組合class往核,怎么使用繼承,怎么使用多態(tài)嚷节,怎么使用純虛類等聂儒,這些其實(shí)是仁者見仁智者見智的問題,但在自己沒有比較好的想法的時(shí)候有個(gè)參考總歸是好的硫痰。