設置對話框、顏色對話框、字體對話框、示例對話框、改變對話框和控件的背景及文本顏色、位圖顯示

🔳🔳 繪製線條 、畫刷繪圖、繪製連續線條、繪製扇形效果的線條


🔳🔳 插入符【文本插入符|圖形插入符】、窗口重繪、路徑、字符輸入【設置字體|字幕變色】


🔳🔳 菜單命令響應函數、菜單命令的路由、基本菜單操作、動態菜單操作、電話本實例


🔳🔳 對話框的創建與顯示、動態創建按鈕、控件的訪問【控件調整|靜態文本控件|編輯框控件】、對話框伸縮功能、輸入焦點的傳遞、默認按鈕的說明


🔳🔳 MFC對話框:逃跑按鈕、屬性表單、嚮導創建


🔳🔳 在對話框程序中讓對話框捕獲WM_KEYDOWN消息


🔳🔳修改應用程序窗口的外觀【窗口光標|圖標|背景】、模擬動畫圖標、工具欄編程、狀態欄編程、進度欄編程、在狀態欄上顯示鼠標當前位置、啓動畫面


🔳🔳設置對話框、顏色對話框、字體對話框、示例對話框、改變對話框和控件的背景及文本顏色、位圖顯示

先學習簡單繪圖:MFC–簡單繪圖,瞭解基本知識。

新建一個單文檔類型的MFC工程,取名:Graphic。此程序將實現簡單的繪圖功能。

一、簡單繪圖

實現簡單的繪圖功能,包括點、直線和橢圓的繪製。爲了實現這些功能:
⭕⭕1)首先爲此程序添加一個子菜單,菜單名稱爲“繪圖”;

⭕⭕2)爲其添加四個子菜單項,分別用來控制不同圖形的繪製。當用戶選擇其中的一個菜單項後,程序將按照當前的選擇進行相應圖形的繪製。添加的四個菜單項的ID及名稱如下表:


⭕⭕3)然後分別爲這四個菜單項添加命令響應,本程序讓視類(CGraphicView)對這些菜單命令進行響應:



最終生成的代碼:
CGraphicView.h:

public:
	afx_msg void OnDot();
	afx_msg void OnLine();
	afx_msg void OnRectangle();
	afx_msg void OnEllipse();

CGraphicView.cpp:

BEGIN_MESSAGE_MAP(CGraphicView, CView)
	// 標準打印命令
	ON_COMMAND(ID_FILE_PRINT, &CView::OnFilePrint)
	ON_COMMAND(ID_FILE_PRINT_DIRECT, &CView::OnFilePrint)
	ON_COMMAND(ID_FILE_PRINT_PREVIEW, &CGraphicView::OnFilePrintPreview)
	ON_WM_CONTEXTMENU()
	ON_WM_RBUTTONUP()
	ON_COMMAND(IDM_DOT, &CGraphicView::OnDot)
	ON_COMMAND(IDM_LINE, &CGraphicView::OnLine)
	ON_COMMAND(IDM_RECTANGLE, &CGraphicView::OnRectangle)
	ON_COMMAND(IDM_ELLIPSE, &CGraphicView::OnEllipse)
END_MESSAGE_MAP()

      ......
            
void CGraphicView::OnDot()
{
	// TODO: 在此添加命令處理程序代碼
	
}
void CGraphicView::OnLine()
{
	// TODO: 在此添加命令處理程序代碼
}
void CGraphicView::OnRectangle()
{
	// TODO: 在此添加命令處理程序代碼
}
void CGraphicView::OnEllipse()
{
	// TODO: 在此添加命令處理程序代碼
}

⭕⭕4)在程序運行以後,當用戶單擊某個菜單項時,應該把用戶的選擇保存起來,以便隨後的繪圖操作使用。因此,在CGraphicView類中添加一個私有變量用來保存用戶的選擇:

private:
   UINT m_nDrawType;

接着,在視類的構造函數中將此變量初始化爲0:

CGraphicView::CGraphicView() noexcept
{
	// TODO: 在此處添加構造代碼
	m_nDrawType = 0;
}

⭕⭕5)當用戶選擇繪圖菜單下的不同子菜單項時,將變量m_nDrawType設置爲不同的值:

void CGraphicView::OnDot()
{
	// TODO: 在此添加命令處理程序代碼
	m_nDrawType = 1;
}
void CGraphicView::OnLine()
{
	// TODO: 在此添加命令處理程序代碼
	m_nDrawType = 2;
}
void CGraphicView::OnRectangle()
{
	// TODO: 在此添加命令處理程序代碼
	m_nDrawType = 3;
}
void CGraphicView::OnEllipse()
{
	// TODO: 在此添加命令處理程序代碼
	m_nDrawType = 4;
}

⭕⭕6)對於直線、矩形和橢圓,在繪圖時都可以由2個點來確定其圖形。當鼠標左鍵按下時得到一個點,當鼠標左鍵鬆開時又得到另外一個點。也就是說,在鼠標左鍵按下時將當前點保存爲繪圖原點,當鼠標左鍵鬆開時,就可以繪圖了。

6.1)因此就需要爲視類CGraphicView分別捕獲鼠標左鍵按下和鼠標左鍵鬆開這兩個消息。

void CGraphicView::OnLButtonDown(UINT nFlags, CPoint point)
{
	// TODO: 在此添加消息處理程序代碼和/或調用默認值

	CView::OnLButtonDown(nFlags, point);
}


void CGraphicView::OnLButtonUp(UINT nFlags, CPoint point)
{
	// TODO: 在此添加消息處理程序代碼和/或調用默認值

	CView::OnLButtonUp(nFlags, point);
}

6.2)另外,當鼠標左鍵按下時,需要將鼠標當前按下點保存起來,因此爲CGraphicView類再增加一個CPoint類型的私有成員變量:m_ptOrigin。



並在CGraphicView類構造函數中,將該變量的值設置爲0,即將原點設置爲(0,0)。

CGraphicView::CGraphicView() noexcept
{
	// TODO: 在此處添加構造代碼
	m_nDrawType = 0;
	m_ptOrigin = (0, 0);
}

6.3)在鼠標左鍵按下消息響應函數中,保存當前點:

void CGraphicView::OnLButtonDown(UINT nFlags, CPoint point)
{
	// TODO: 在此添加消息處理程序代碼和/或調用默認值
	m_ptOrigin = point;

	CView::OnLButtonDown(nFlags, point);
}

6.4)鼠標左鍵鬆開消息響應函數中實現繪圖功能。通過前面的知識知道,爲了進行繪圖操作,首先需要有DC對象,所以首先定義了一個CClientDC類型的變量: dc。在具體繪圖時應根據用戶的選擇來進行,該選擇已經保存在變量:m_nDrawType中了。可以用switch/case語句,分別完成相應圖形的繪製:

  • 🔳🔳 如果設置一個點,需要用到函數:SetPixel,這也是CDC類的一個成員方法,該函數是在指定的點設置一個像素。該函數兩種聲明形式,其中一種聲明如下:

    COLORREF SetPixel(POINT point,COLORREF crColor);
    

    ◼ point
    指定的點
    ◼ crColor
    指定的顏色。在程序中設定的顏色在系統顏色表中可能不存在,但系統會選擇一種和這個顏色最接近的顏色。

  • 🔳🔳 當用戶選擇直線時,這時就需要繪製直線,首先調用MoveTo函數移動到原點,然後調用LineTo函數繪製到終點

  • 🔳🔳 繪製矩形時可以使用Rectangle函數,該函數有一種聲明:

    BOOL Rectangle(LPCRECT IpRect);
    

    該函數有一個指向CRect對象的參數,而CRect可以利用兩個點來構造。

    📢📢📢 Rectangle函數需要的是指向CRect對象的指針,而傳遞的此參數卻是CRect對象,但程序編譯時卻能成功通過,運行時也不會報錯的原因:
      C系列的語言都是強類型語言,如果類型不匹配的話,需寒進行強制類型轉換。CRect類提供一個成員函數:重載LPCRECT操作符,其作用是將CRect轉換爲LPCRECT類型。因此,當在程序中給Rectangle函數的參數賦值時,如果它發現該參數是一個CRect對象,它就會隱式地調用LPCRECT操作符,將CRect類型的對象轉換爲LPCRECT類型。

  • 🔳🔳 當用戶選擇橢圓菜單項時,調用Ellipse函數繪製一個橢圓。

void CGraphicView::OnLButtonUp(UINT nFlags, CPoint point)
{
	// TODO: 在此添加消息處理程序代碼和/或調用默認值
	CClientDC dc(this);
	switch (m_nDrawType)
	{
	case 1:
		dc.SetPixel(point, RGB(255, 0, 0));
		break;
	case 2:
		dc.MoveTo(m_ptOrigin);
		dc.LineTo(point);
		break;
	case 3:
		dc.Rectangle(CRect(m_ptOrigin, point));
		break;
	case 4:
		dc.Ellipse(CRect(m_ptOrigin, point));
		break;
	}

	CView::OnLButtonUp(nFlags, point);
}

運行Graphic程序,由於DC中有一個默認的白色畫刷,在繪製圖形時會使用這個默認畫刷填充其內部,因此在繪製時,如果存在重疊部分,那麼先前繪製的圖形會被後來繪製的圖形所覆蓋:

⭕⭕7)繪製其他顏色的線條。
繪製的直線,以及矩形和橢圓的邊框都是黑色的。一般來說,在程序運行過程中,用戶都希望能夠使用他們自己指定的顏色來繪製各種圖形。通過前面章節的介紹,知道線條的顏色是由DC中畫筆的顏色確定的,爲了繪製其他顏色的線條就需要:

  1. 構造一個CPen對象,
  2. 爲它指定一種顏色,
  3. 將此畫筆選入設備描述表中。
void CGraphicView::OnLButtonUp(UINT nFlags, CPoint point)
{
	// TODO: 在此添加消息處理程序代碼和/或調用默認值
	CClientDC dc(this);
	CPen pen(PS_SOLID, 20, RGB(150, 140, 32));
	dc.SelectObject(&pen);
	switch (m_nDrawType)
	{
	case 1:
		dc.SetPixel(point,RGB(200,0, 0));
		break;
	case 2:
		dc.MoveTo(m_ptOrigin);
		dc.LineTo(point);
		break;
	case 3:
		dc.Rectangle(CRect(m_ptOrigin, point));
		break;
	case 4:
		dc.Ellipse(CRect(m_ptOrigin, point));
		break;
	}

	CView::OnLButtonUp(nFlags, point);
}


⭕⭕8)把DC中的畫刷設置爲透明。
不想使用DC默認的白色畫刷來填充矩形或橢圓的內部,而是希望能夠看到這些圖形內部的內容,可以把DC中的畫刷設置爲透明的。

  1. 利用參數NULL BRUSH調用GetStockObject函數可以創建透明畫刷,
  2. 然後調用CBrush類的靜態成員函數FromHandle將畫刷句柄轉換爲指向畫刷對象的指針,但是該函數的參數需要的是HBRUSH類型,GetStockObject函數返回的是HGDIOBJ類型,因此需要進行強制轉換,將其轉換爲畫刷的句柄,即HBRUSH類型對象。
  3. 將創建的新畫刷選入設備描述表中。
void CGraphicView::OnLButtonUp(UINT nFlags, CPoint point)
{
	// TODO: 在此添加消息處理程序代碼和/或調用默認值
	CClientDC dc(this);
	//改變畫筆顏色
	CPen pen(PS_SOLID, 5, RGB(150, 140, 32));
	dc.SelectObject(&pen);
	//設置畫刷透明
	CBrush* pBrush = CBrush::FromHandle((HBRUSH)GetStockObject(NULL_BRUSH));
	dc.SelectObject(pBrush);
	switch (m_nDrawType)
	{
	case 1:
		dc.SetPixel(point,RGB(200,0, 0));
		break;
	case 2:
		dc.MoveTo(m_ptOrigin);
		dc.LineTo(point);
		break;
	case 3:
		dc.Rectangle(CRect(m_ptOrigin, point));
		break;
	case 4:
		dc.Ellipse(CRect(m_ptOrigin, point));
		break;
	}

	CView::OnLButtonUp(nFlags, point);
}

再次運行Graphic程序,選擇相應菜單項,然後繪製各種圖形,這時可以看到所有繪製的線條都可以看到了,因爲現在使用的是透明畫刷。

二、設置對話框

許多軟件都爲用戶提供了設置對話框,或者稱爲選項對話框,允許用戶通過設置一些選項來改變軟件的某些行爲和特性。

實現:
  給Graphic程序添加一個設置對話框,允許用戶指定畫筆的類型、線寬,並且讓隨後的繪圖操作就使用用戶指定的新設置值來進行繪製。

爲了實現這一功能,首先需要爲Graphic程序添加一個對話框資源,並按照下表所列內容修改其屬性。

創建對話框:

2.1 設置線寬

⭕⭕1)首先實現線寬的設置。爲新添加的設置對話框資源添加一個靜態文本框,並將其Caption屬性設置爲: 線寬;

⭕⭕2)再添加個編輯框,讓用戶輸入設定的線寬,將其ID設置爲: IDC_LINE_WIDTH。

⭕⭕3)創建一個相應的對話框類。類名設置爲CSettingDlg,基類:CDialog。

📢📢📢 此時生成的類所關聯的ID並不是自己想關聯的對話框的ID。所以需要自己手動指定關聯的對話框ID號。


需要更改的關聯位置有兩處:

⭕⭕4)爲對話框中的編輯框控件增加一個成員變量:m_nLineWidth,類型:UINT。

因爲對於線,不希望用戶設置的值小於0,因此將它的類型選擇爲無符號整型(UINT)。

改正上面的視頻內容:
m_nLineWidth設置爲公開public類型——由於後面發現需要在別的類中訪問此成員變量,所以需要設置爲公開類型。

public:
    UINT m_nLineWidth;

⭕⭕5)顯示設置對話框。
5.1)爲Graphic程序在繪圖子菜單下再增加一個菜單項,名稱爲:設置,並將其ID設置爲:IDM_SETTING

5.2)用戶單擊該菜單項後,程序應立即顯示剛纔新建的設置對話框。因此爲此菜單項添加一個命令響應,並選擇視類(CGraphicView)對此消息做出響應:

5.3)將CSettingDlg的頭文件包含到視類源文件CGraphicView.cpp中:

#include "CSettingDlg.h"

5.4)首先構造設置對話框對象(dlg),然後調用該對象的DoModal函數顯示該對話框:

void CGraphicView::OnSetting()
{
	// TODO: 在此添加命令處理程序代碼
	CSettingDlg dlg;
	dlg.DoModal();
}

運行程序:

⭕⭕6)當用戶在線寬編輯框中輸入線寬值並確定此操作後,程序應把這個線寬值保存起來,然後隨後的繪圖都使用這個線寬值來設置線的寬度。
6.1)爲CGraphicView類添加一個私有的成員變量:m_nLineWidth;類型:UINT,用來保存用戶輸入的線寬:

6.2)在CGraphicView類的構造函數中將其初始化爲0。

CGraphicView::CGraphicView() noexcept
{
	// TODO: 在此處添加構造代碼
	m_nDrawType = 0;
	m_ptOrigin = (0, 0);
	m_nLineWidth = 0;
}

6.3)判斷用戶關閉設置對話框時的選擇。
  在用戶輸入線寬後,應該是在用戶單擊OK按鈕後才保存這個線寬值;如果用戶選擇的是Cancle按鈕,並不需要保存這個線寬值。因此在CGraphicView類的OnSetting函數中需要判斷一下用戶關閉設置對話框時的選擇,如果選擇的是OK按鈕,則保存用戶輸入的線寬值

void CGraphicView::OnSetting()
{
	// TODO: 在此添加命令處理程序代碼
	CSettingDlg dlg;
	if (IDOK == dlg.DoModal()) {
		m_nLineWidth = dlg.m_nLineWidth;
	}
}

DoModal函數:
。。。。。。。。。。。。。。。。。。。

⭕⭕7)在構造畫筆對象時,其寬度就可以利用m_nLineWidth這個變量來代替了:


void CGraphicView::OnLButtonUp(UINT nFlags, CPoint point)
{
	// TODO: 在此添加消息處理程序代碼和/或調用默認值
	CClientDC dc(this);
	//改變畫筆顏色
	//CPen pen(PS_SOLID, 5, RGB(150, 140, 32));
	CPen pen(PS_SOLID, m_nLineWidth, RGB(150, 140, 32));
	dc.SelectObject(&pen);
	//設置畫刷透明
	CBrush* pBrush = CBrush::FromHandle((HBRUSH)GetStockObject(NULL_BRUSH));
	dc.SelectObject(pBrush);
	switch (m_nDrawType)
	{
	case 1:
		dc.SetPixel(point,RGB(200,0, 0));
		break;
	case 2:
		dc.MoveTo(m_ptOrigin);
		dc.LineTo(point);
		break;
	case 3:
		dc.Rectangle(CRect(m_ptOrigin, point));
		break;
	case 4:
		dc.Ellipse(CRect(m_ptOrigin, point));
		break;
	}

	CView::OnLButtonUp(nFlags, point);
}

在程序運行時,當用戶設置線寬後,在下一次繪圖時,就會以用戶輸入的線寬創建畫筆,那麼隨後的繪圖就是按照用戶設置的線寬來繪製的。

運行Graphic程序,單擊繪圖下的設置菜單項,在彈出的設置對話框中指定新線寬,並單擊OK按鈕關閉設置對話框。然後再繪圖,可以發現程序使用的是用戶指定的新線寬來繪製圖形的。

但是,當再次打開設置對話框時,線寬編輯框的值又變回0了。一般來說,當再次回到這個設置對話框時,應該看到上次設置的值,但這裏的情況並不是這樣的。

📋📋原因:由於設置對話框對象dlg是一個局部對象。當再次單擊單擊繪圖下的設置菜單項,即再次調用OnSetting函數時,又將重新構造dlg這個設置對話框對象。因此該對象的所有成員變量都將被初始化,而CSettingDlg對象的構造函數中m_nLineWidth初始化爲0,所以每次打開設置對話框時,看到的編輯框內都爲0:

CSettingDlg::CSettingDlg(CWnd* pParent /*=nullptr*/)
	: CDialog(IDD_DLG_SETTING, pParent)
	, m_nLineWidth(0)
{

}

🟢🟢解決:爲了解決這個問題,當CSettingDlg對話框(dlg)對象產生之後,應該將CGraphicView類中保存的用戶先前設置的線寬再傳回給這個設置對話框:

void CGraphicView::OnSetting()
{
	// TODO: 在此添加命令處理程序代碼
	CSettingDlg dlg;
	//將CGraphicView類中保存的用戶先前設置的線寬再傳回給這個設置對話框
	dlg.m_nLineWidth = m_nLineWidth;
	if (IDOK == dlg.DoModal()) {
		m_nLineWidth = dlg.m_nLineWidth;
	}
}

在這裏插入圖片描述

2.2 設置線型

實現:
  爲Graphic程序添加允許用戶設置線型的功能。提供一些單選按鈕讓用戶從多種線型中選擇一種。

⭕⭕1)首先再爲Graphic程序已有的設置對話框資源添加一個組框,並設置Caption:線型;ID:IDC_LINE_STYLE

組框的作用通常是起標示作用,所以它的ID默認情況下是IDC_STATIC。但如果在程序中需要對組框進行操作的話,那麼其ID就不能是默認的IDC_STATIC了,需要修改這個ID。

因爲後面的程序會對這個組框進行一些操作,所以這裏將它的ID修改爲: IDC_LINE_STYLE。

⭕⭕2)接着在此組框內放置三個單選按鈕,保持它們默認的ID值不變,將它們的名稱分別設置爲:實線、虛線、點線。

然後將這三個單選按鈕設置成爲一組。方法:在第一個單選按鈕(實線)上單擊鼠標右鍵,打開其屬性對話框,選中Group選項。這時,這三個單選按鈕就成爲一組的了。

⭕⭕3)利用類嚮導爲這組單選按鈕關聯一個成員變量:

這樣在程序運行時:

  • 如果選中第1個單選按鈕,該變量的值就是0;
  • 如果選中第2個單選按鈕,該變量的值就是1;
  • 如果選中第3個單選按鈕,該變量的值就是2;
  • 如果都沒有選中,那麼該變量的值是-1。

生成代碼:
CSettingDlg.h

public:
	UINT m_nLineWidth;
	// 記錄線型的值
	int m_nLineStyle;

CSettingDlg.cpp

CSettingDlg::CSettingDlg(CWnd* pParent /*=nullptr*/)
	: CDialog(IDD_DLG_SETTING, pParent)
	, m_nLineWidth(0)
	, m_nLineStyle(0)
{

}

void CSettingDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	DDX_Text(pDX, IDC_LINE_WIDTH, m_nLineWidth);
	DDX_Radio(pDX, IDC_RADIO1, m_nLineStyle);
}

⭕⭕4)保存用戶選擇的線型。
同上,當用戶單擊設置對話框上的OK按鈕關閉該對話框後,應該將用戶選擇的線型保存下來,因此需要:
4.1)爲CGraphicView類再添加一個int類型的私有成員變量:m_nLineStyle:

4.2)並在該類的構造函數中將其初始化爲0。

CGraphicView::CGraphicView() noexcept
{
	// TODO: 在此處添加構造代碼
	m_nDrawType = 0;
	m_ptOrigin = (0, 0);
	m_nLineWidth = 0;
	m_nLineStyle = 0;
}

4.3)然後在CGraphicView類的OnSetting函數中,當用戶單擊設置對話框的OK按鈕關閉該對話框後,將用戶選擇的線型保存到CGraphicView類的m_nLineStyle變量中。

void CGraphicView::OnSetting()
{
	// TODO: 在此添加命令處理程序代碼
	CSettingDlg dlg;
	dlg.m_nLineWidth = m_nLineWidth;
	if (IDOK == dlg.DoModal()) {
		m_nLineWidth = dlg.m_nLineWidth;
		//戶選擇的線型保存到CGraphicView類的m_nLineStyle變量中
		m_nLineStyle = dlg.m_nLineStyle;
	}
}

4.4)與前面線寬的設置一樣,爲了把上一次選擇的線型保存下來,同樣需要在CGraphicViev類中把已保存的線型值再設置回設置對話框的線型變量:

void CGraphicView::OnSetting()
{
	// TODO: 在此添加命令處理程序代碼
	CSettingDlg dlg;
	dlg.m_nLineWidth = m_nLineWidth;
    //把已保存的線型值再設置回設置對話框的線型變量
	dlg.m_nLineStyle = m_nLineStyle;
	if (IDOK == dlg.DoModal()) {
		m_nLineWidth = dlg.m_nLineWidth;
		m_nLineStyle = dlg.m_nLineStyle;
	}
}

⭕⭕5)根據用戶指定的線型創建畫筆。
獲得用戶指定的線型後,程序應根據此線型創建畫筆。在wingdi.h文件中定義了一些符號常量:

可以在CGraphicView類OnLButtonUp函數中構造畫筆的那行代碼中的符號常量: PS_SOLID上單擊鼠標右鍵打開wingdi.h文件,並定位於該常量符號的定義處。

在這裏插入圖片描述
從中可以看到PS_SOLID (實線)的值本身就是0,PS_DASH (虛線)就是1,PS_DOT(點線)就是2,這正好與CGraphicView類的成員變量m_nLineStyle的取值一一對應。這是因爲在設置對話框中排列的線型順序正好是按照實線、虛線和點線的順序來做的。因此,程序在構造畫筆對象時,可以直接使用m_nLineStyle變量作爲線型參數的值:

void CGraphicView::OnLButtonUp(UINT nFlags, CPoint point)
{
	// TODO: 在此添加消息處理程序代碼和/或調用默認值
	CClientDC dc(this);
	//改變畫筆顏色
	//CPen pen(PS_SOLID, 5, RGB(150, 140, 32));
	//直接使用m_nLineStyle變量作爲線型參數的值
	CPen pen(m_nLineStyle, m_nLineWidth, RGB(150, 140, 32));
	dc.SelectObject(&pen);
	//設置畫刷透明
	CBrush* pBrush = CBrush::FromHandle((HBRUSH)GetStockObject(NULL_BRUSH));
	dc.SelectObject(pBrush);
	switch (m_nDrawType)
	{
	case 1:
		dc.SetPixel(point,RGB(200,0, 0));
		break;
	case 2:
		dc.MoveTo(m_ptOrigin);
		dc.LineTo(point);
		break;
	case 3:
		dc.Rectangle(CRect(m_ptOrigin, point));
		break;
	case 4:
		dc.Ellipse(CRect(m_ptOrigin, point));
		break;
	}

	CView::OnLButtonUp(nFlags, point);
}


運行Graphic程序,首先打開設置對話框,可以看到初始的選擇是實線,這是因爲在CGraphicView類的構造函數中將線型變量(m_nLineStyle)初始設置爲0,即實線。然後程序在構造設置對話框對象之後,將CGraphicView類的線型變量賦給了這個對話框對象的線型變量,因此該對話框初始顯示時,選中的線型是實線。

📢📢📢 畫筆寬度小於等於1時,虛線和點線線型纔有效。否則,線型會自動改爲實線。

三、顏色對話框

顏色對話框類似於Windows提供的畫圖程序中選擇編輯顏色萊單項後出現的對話框:

利用顏色對話框,可以讓用戶選擇一種顏色,程序隨後按照此顏色創建繪圖所需的畫筆。顏色對話框看起來比較複雜。實際上,MFC提供了一個類:CColorDialog,可以很方便地創建一個顏色對話框。該類的派生層次結構:

由此,可以知道顏色對話框也是一個對話框。
CColorDialog類的構造函數:

CColorDialog(COLORREF clrInit = 0, DWORD dwFlags = 0,CWnd* pParentWnd = NULL);

◼ clrInit
指定默認的顏色選擇。默認是黑色。
◼dwFlags
指定一組標記,用於定製顏色對話框的功能和它的外觀。
◼ pParentWnd
指向顏色對話框父窗口或擁有者窗口的指針。

爲了在Graphic程序中增加顏色對話框的顯示:
⭕⭕1)首先爲該程序增加一個菜單項,當用戶選擇此菜單項時,程序將顯示顏色對話框。
將這個新菜單項放置在已有的繪圖子菜單下,並設置ID:IDM_COLOR、Caption:顏色

⭕⭕2)爲其增加一個命令響應,並選擇CGraphicView類對此菜單項命令做應。

⭕⭕3)在響應函數OnColor中顯示顏色。

void CGraphicView::OnColor()
{
	// TODO: 在此添加命令處理程序代碼
	CColorDialog dlg;
	dlg.DoModal();
}

運行Graphic程序,點擊顏色菜單項,即可以看到出現了一個顏色對話框:
可以看到在該對話框左邊顏色塊的黑色塊上有一個黑色的邊框,說明默認選擇的是黑色。

⭕⭕4)將用戶選擇的顏色保存下來。
4.1)CColorDialog類有一個CHOOSECOLOR結構體類型的成員變量:m_cc。CHOOSECOLOR結構體的定義:

typedef struct {
   //指定結構的長度(字節)
   DWORD        lStructSize; 
   //擁有對話框的窗口的句柄。該成員可以是任意有效的窗口句柄,或在對話框沒有所有者時,可爲NULL
   HWND         hwndOwner; 
   //如果Flag成員設置了CC_ENABLETEMPLATEHANDLE標識符時,該成員是一個包含了對話框模板的內存對象的句柄。如果 CC_ENABLETEMPLATE 標識符被設置時,該成員是一個包含了對話框的模塊句柄。如果上述兩個標識符都未被設置,則該成員被忽略。
   HWND         hInstance;
   //如果CC_RGBINIT標識符被設置時,該成員指定了對話框打開時默認的選擇顏色。如果指定的顏色值不在有效的範圍內,系統會自動選擇最近的顏色值。如果該成員爲0或CC_RGBINIT未被設置,初始顏色是黑色。如果用戶單擊OK按鈕,該成員指定了用戶選擇的顏色。
   COLORREF     rgbResult;
   //指向一個包含16個值的數組,該數組包含了對話框中自定義顏色的紅、綠、藍(RGB)值。如果用戶修改這些顏色,系統將用新的顏色值更新這個數組。如果要在多個ChooseColor函數中保存這個新的數組,應該爲該數組分配靜態內存空間。
   COLORREF*    lpCustColors;
   //一個可以讓你初始化顏色對話框的位集。當對話框返回時,它用來這些標識符來標識用戶的輸入。該成員可以爲一組標識符的任意組合。
   DWORD        Flags;
   //指定應用程序自定義的數據,該數據會被系統發送給鉤子程序。當系統的發送WM_INITDIALOG消息給鉤子程序時,消息的lParam參數是一個指向CHOOSECOLOR結構的指針。鉤子程序可以利用該指針獲得該成員的值。
   LPARAM       lCustData;
   //指向CCHookProc鉤子程序的指針,該鉤子可以處理髮送給對話框的消息。該成員只在CC_ENABLEHOOK標識被設定的情況下才可用,否則該成員會被忽略。
   LPCCHOOKPROC lpfnHook;
   //指向一個NULL結尾的字符串,該字符串是對話框模板資源的名字。
   LPCWSTR      lpTemplateName;
}CHOOSECOLOR, *LPCHOOSECOLOR;

當用戶單擊顏色對話框上的OK按鈕後,這個結構體中的rgbResult變量就保存了用戶選擇的顏色。因此,在程序中通過這個變量就可以獲得用戶選擇的顏色。

4.2)在Graphic程序中,爲了保存用戶選擇的顏色,爲CGraphicView類再增加一個COLORREF類型的私有成員變量:m_clr。右鍵點擊類視圖類中的CGraphicView,選項添加–>添加變量

4.3)並在CGraphicView類的構造函數中將其初始化爲紅色:

CGraphicView::CGraphicView() noexcept
{
	// TODO: 在此處添加構造代碼
	m_nDrawType = 0;
	m_ptOrigin = (0, 0);
	m_nLineWidth = 0;
	m_nLineStyle = 0;
	m_clr = RGB(255,0, 0);

}

4.4)在顏色子菜單的響應函數OnColor函數中進行判斷:如果用戶單擊的OK按鈕,就將用戶選擇的顏色保存下來。

void CGraphicView::OnColor()
{
	// TODO: 在此添加命令處理程序代碼
	CColorDialog dlg;
	if (IDOK == dlg.DoModal()) {
		m_clr = dlg.m_cc.rgbResult;
	}	
}

⭕⭕5)當用戶選擇顏色後,隨後進行的繪圖操作都應用此顏色來繪製,也就說應該按此顏色創建繪圖用的畫筆。
所以修改CGraphicVview類OnLButtonUp函數中已有的創建畫筆的代碼,將用戶當前選擇的顏色(即m_clr變量)傳遞給CPen構造函數的第三個參數。此外,還需要修改該函數中繪製點圖形的代碼,用用戶當前選擇的顏色來設置像素點的顏色。


void CGraphicView::OnLButtonUp(UINT nFlags, CPoint point)
{
	// TODO: 在此添加消息處理程序代碼和/或調用默認值
	CClientDC dc(this);
	//改變畫筆顏色
	//CPen pen(PS_SOLID, 5, RGB(150, 140, 32));
	CPen pen(m_nLineStyle, m_nLineWidth, m_clr);
	dc.SelectObject(&pen);
	//設置畫刷透明
	CBrush* pBrush = CBrush::FromHandle((HBRUSH)GetStockObject(NULL_BRUSH));
	dc.SelectObject(pBrush);
	switch (m_nDrawType)
	{
	case 1:
		dc.SetPixel(point,m_clr);
		break;
	case 2:
		dc.MoveTo(m_ptOrigin);
		dc.LineTo(point);
		break;
	case 3:
		dc.Rectangle(CRect(m_ptOrigin, point));
		break;
	case 4:
		dc.Ellipse(CRect(m_ptOrigin, point));
		break;
	}

	CView::OnLButtonUp(nFlags, point);
}

運行Graphic程序,打開顏色對話框,選擇某種顏色,然後進行繪圖操作,可以發現這時所繪製的圖形邊框的顏色就是剛纔選擇的顏色。但是當再次打開顏色對話框時,它默認選擇的仍是黑色,而不是剛纔選擇的顏色。

⭕⭕6)顏色值設置回顏色對話框。
6.1)自然就會想到應該像上面的處理一樣,將用戶選擇的顏色,即CGraphicView類的m_clr變量保存的顏色值設置回顏色對話框對象,因此修改CGraphicView類的OnColor函數:

void CGraphicView::OnColor()
{
	// TODO: 在此添加命令處理程序代碼
	CColorDialog dlg;
	dlg.m_cc.rgbResult = m_clr;
	if (IDOK == dlg.DoModal()) {
		m_clr = dlg.m_cc.rgbResult;
	}
	
}

再次運行Graphic程序,先選擇一種顏色,然後進行圖形的繪製,可是當再次打開顏色對話框時,將會發現結果仍不對,默認選中的顏色仍是黑色。

6.2)實際上如果想要設置顏色對話框初始選擇的顏色,則需要設置該對話框的CC_RGBINIT標記

  • 這個標記可以在創建顏色對話框時通過其構造函數的第二個參數來設置;
  • 也可以在該對話框創建之後,設置其m_cc成員變量的Flags成員。

採用後一種方法,修改CGraphicView類的OnColor函數:

void CGraphicView::OnColor()
{
	// TODO: 在此添加命令處理程序代碼
	CColorDialog dlg;
	dlg.m_cc.Flags = CC_RGBINIT;
	dlg.m_cc.rgbResult = m_clr;
	if (IDOK == dlg.DoModal()) {
		m_clr = dlg.m_cc.rgbResult;
	}
	
}

再次運行Graphic程序,選擇顏色菜單項,出現一個非法操作提示對話框:

6.3)實際上,當在創建CColorDialog對象dlg時,它的數據成員m_cc中的Flags成員已經具有了一些初始的默認標記。將CC_RGBINIT標記直接賦給Flags成員時,就相當於將Flags成員初始默認的標記都去掉了。所以不能給Flags標記直接賦值,應利用或操作(|)將CC_RGBINIT標記與Flags先前的標記組合起來

void CGraphicView::OnColor()
{
	// TODO: 在此添加命令處理程序代碼
	CColorDialog dlg;
	dlg.m_cc.Flags = CC_RGBINIT| dlg.m_cc.Flags;
	dlg.m_cc.rgbResult = m_clr;
	if (IDOK == dlg.DoModal()) {
		m_clr = dlg.m_cc.rgbResult;
	}
}

再次運行Graphic程序,打開顏色對話框,可以看到初始選擇的就是紅色。接着,選擇其他某種顏色並關閉該對話框。然後再打開顏色對話框,這時就可以看到現在選中的是先前選擇的顏色了。

6.4)另外, Flags成員的取值還有一個常用標記: CC-FULLOPEN,該標記的作用就是讓顏色對話框完全展開

void CGraphicView::OnColor()
{
	// TODO: 在此添加命令處理程序代碼
	CColorDialog dlg;
	dlg.m_cc.Flags = CC_RGBINIT| dlg.m_cc.Flags|CC_FULLOPEN;
	dlg.m_cc.rgbResult = m_clr;
	if (IDOK == dlg.DoModal()) {
		m_clr = dlg.m_cc.rgbResult;
	}
	
}

再次運行Graphic程序,打開顏色對話框,這時可以看到這個顏色對話框處於完全展開狀態:

四、字體對話框

下面爲Graphic程序添加字體對話框應用,該對話框類似於在對話框資源的屬性對話框中所看到的字體對話框。

與上面的顏色對話框一樣,字體對話框的創建也很簡單,因爲MFC也提供了一個相應的類: CFontDialog,該類的派生層次結構:

由此可知該類派生於CDialog,所以其也是一個對話框類。

CFontDialog類的構造函數如下:

CFontDialog(
        LPLOGFONT lplfInitial = NULL,
		DWORD dwFlags = CF_EFFECTS | CF_SCREENFONTS,
		CDC* pdcPrinter = NULL,
		CWnd* pParentWnd = NULL
);
CFontDialog(
        const CHARFORMAT& charformat,
		DWORD dwFlags = CF_SCREENFONTS,
		CDC* pdcPrinter = NULL,
		CWnd* pParentWnd = NULL
);

◼ lplfInitial
指向LOGFONT結構體的指針,允許用戶設置一些字體的特徵。
◼ dwFlags
s主要設置一個或多個與選擇的字體相關的標記。
◼ pdcPrinter
指向打印設備上下文的指針。
◼ pParentWnd
指向字體對話框父窗口的指針。

由CFontDialog類的構造函數的聲明可知,它的參數都有默認值,因此在構造字體對話框時可以不用指定任何參數。字體對話框的創建與前面的顏色對話框的一樣,首先構造一個字體對話框對象,然後調用該對象的DoModal函數顯示這個對話。

⭕⭕1)爲Graphic程序增加一個菜單項用來顯示字體對話框,將其ID設置爲: IDM_FONT;Caption:字體

⭕⭕2)接着爲其增加一個命令響應,並選擇CGraphic類對此菜單項命令做出響應。

⭕⭕3)然後在此響應函數中添加創建並顯示字體對話框的代碼。

void CGraphicView::OnFont()
{
	// TODO: 在此添加命令處理程序代碼
	CFontDialog dlg;
	dlg.DoModal();
}

運行程序,點擊字體菜單項:

⭕⭕4)程序保存用戶通過字體對話框選擇的字體信息。
當用戶通過字體對話框選擇某種字體後,程序應該把當前選擇保存下來,然後在CGraphicView類中利用此字體將用戶選擇的字體名稱顯示出來。

CFontDialog類有一個CHOOSEFONT結構體類型的數據成員:m_cf。CHOOSEFONT結構體的定義如下:

typedef struct {
   //指定這個結構的大小,以字節爲單位
   DWORD           lStructSize;
   //調用窗口句柄,指向所有者對話框窗口的句柄。這個成員可以是任意有效窗口句柄,或如果對話框沒有所有者它可以爲NULL。
   HWND            hwndOwner;     
    //顯示的設備環境句柄,一般爲NULL;    
   HDC             hDC;       
   //選中的字體返回值,這裏的字體是邏輯字體       
   LPLOGFONTW      lpLogFont;  
   //字體的大小
   INT             iPointSize; 
   //字體的位標記,用來初始化對話框。當對話框返回時,這些標記指出用戶的輸入。      
   DWORD           Flags;      
   //字體顏色
   COLORREF        rgbColors;  
   //自定義數據,這數據是能被lpfnHook成員識別的系統傳到的鉤子程序
   LPARAM          lCustData; 
   //做鉤子程序用的回調函數
   LPCFHOOKPROC    lpfnHook; 
   //指向一個以空字符結束的字符串,字符串是對話框模板資源的名字,資源保存在能被hInstance成員識別的模塊中
   LPCWSTR         lpTemplateName; 
   //實例句柄
   HINSTANCE       hInstance; 
   //字體風格
   LPWSTR          lpszStyle;
   //字體類型
   WORD            nFontType;
   //字體允許的最小尺寸
   INT             nSizeMin;  
   //字體允許的最大尺寸
   INT             nSizeMax; 
} CHOOSEFONT *LPCHOOSEFONT;

其中成員IpLogFont是指向邏輯字體(LOGFONT類型)的指針。LOGFONT結構的定義:

typedef struct tagLOGFONT
{
    LONG      lfHeight;
    LONG      lfWidth;
    LONG      lfEscapement;
    LONG      lfOrientation;
    LONG      lfWeight;
    BYTE      lfItalic;
    BYTE      lfUnderline;
    BYTE      lfStrikeOut;
    BYTE      lfCharSet;
    BYTE      lfOutPrecision;
    BYTE      lfClipPrecision;
    BYTE      lfQuality;
    BYTE      lfPitchAndFamily;
    WCHAR     lfFaceName[LF_FACESIZE];
} LOGFONT;

各成員的含義:LOGFONT——百度百科

其中成員lfFaceName中存放的就是字體的名稱。也就是說,可以通過此成員得到字體的名稱。


至於字體對象的創建:

  • 首先可以利用CFont類構造一個字體對象,
  • 然後利用CFont類的CreateFontIndirect成員函數根據指定特徵的邏輯字體(LOGFONT類型)來初始化這個字體對象。

CreateFontIndirect函數的功能就是利用參數IpLogFont指向的LOGFONT結構體中的一些特徵來初始化CFont對象。該函數的聲明:

BOOL CreateFontIndirect( CONST LOGFONT* lpLogFont);

所以,爲了保存用戶選擇的字體:

4.1)首先通過類嚮導爲Graphic工程的CGraphicView類增加兩個私有的成員變量:
   ▪▪▪ CFont :m_font,用於存放字體對象;
   ▪▪▪ CString:m_strFontName,用來保存所選字體的名稱。


4.2)然後在CGraphicView類的構造函數中初始化m_strFontName爲空字符串。

類嚮導創建的成員變量已經自動被初始化。


4.3)然後在CGraphicView類的OnFont函數中進行判斷,如果用戶單擊的是字體對話框的OK按鈕,就用所選字體信息初始化m_font對象,並保存所選字體的名稱。

void CGraphicView::OnFont()
{
	// TODO: 在此添加命令處理程序代碼
	CFontDialog dlg;
	if (IDOK == dlg.DoModal()) {
		//保存字體對話框選擇的字體樣式
		m_font.CreateFontIndirectW(dlg.m_cf.lpLogFont);
		//保存設置的字體名稱
		m_strFontName = dlg.m_cf.lpLogFont->lfFaceName;
	}
}

4.4)在視類窗口中把字體名稱用選擇的字體顯示出來。

  • 在上述OnFont函數中,在保存完字體的名稱之後需要調用Invalidat函數讓窗口無效,這樣當下一次發生WM_PAINT消息時,窗口就會進行重繪。
    void CGraphicView::OnFont()
    {
    	// TODO: 在此添加命令處理程序代碼
    	CFontDialog dlg;
    	if (IDOK == dlg.DoModal()) {
    		//保存字體對話框選擇的字體樣式
    		m_font.CreateFontIndirectW(dlg.m_cf.lpLogFont);
    		//保存設置的字體名稱
    		m_strFontName = dlg.m_cf.lpLogFont->lfFaceName;
    		//讓窗口無效
    		Invalidate();
    	}
    }
    
  • 在CGraphicView類的OnDraw函數中把 “字體名稱” 用選擇的字體顯示出來。首先把用戶選擇的新字體選入設備上下文,然後在窗口的(0, 0)處顯示所選字體的名稱,最後恢復初始的字體,將初始的字體選入設備上下文。
    void CGraphicView::OnDraw(CDC* pDC)
    {
    	CGraphicDoc* pDoc = GetDocument();
    	ASSERT_VALID(pDoc);
    	if (!pDoc)
    		return;
    
    	// TODO: 在此處爲本機數據添加繪製代碼
    	CFont* pOldFont = pDC->SelectObject(&m_font);
    	pDC->TextOutW(0, 0, m_strFontName);
    	pDC->SelectObject(pOldFont);
    }
    

運行Graphic程序,選擇字體菜單項,這時將打開字體對話框,可以選擇任一種字體、字形,還可以指定字體的大小。然後單擊字體對話框的OK按鈕,這時在視類窗口中就可以看到用選定的字體、字形和大小輸出了所選字體的名稱:

可是當再次選擇字體菜單項後,程序將出現非法操作。
📋📋 原因:這是因爲當第一次選擇字體後,OnFont函數中就把m_font對象與這種字體資源相關聯了。當再次選擇一種字體後,OnFont函數又會試圖把m_font對象與新字體資源相關聯,這時當然就會出錯。
🟢🟢 解決:在點擊OK菜單項後,在字體菜單項的命令響應函數中實現:在保存用戶選擇的字體資源前應該進行一個判斷,如果mfont對象已經與一個字體資源相關聯了,首先就需要切斷這種關聯,釋放該字體資源,然後才能與新資源相關聯。

如何釋放CFont資源:
  要想釋放先前的資源,可以利用CGdiObject類(CPen、 CFont、CBitmap、CBrush都派生於該類)的成員函數DeleteObject來實現。該函數通過釋放所有爲Windows GDI對象所分配的資源,從而刪除與CGdiObject對象相關聯的Windows GDI對象,同時與CGdiObjec對象相關的存儲空間並不會受此調用影響。
  


CGdiObjec對象 和 Windows GDI對象區別:
  CGdiObjec對象是一個類的對象;而Windows GDI是一種資源對象。就好像窗口類的對象和窗口的關係一樣。例如視類對象和視類窗口,它們之間的聯繫在於視類對象有一個數據成員:m_hWnd保存了窗口資源的句柄值。
   CGdiObject類的對象和Windows GDI資源對象是兩個不同的概念,它們之間也是一個數據成員來維繫的。當刪除Windows GDI資源對象後,對於CGdiObiect類所定義的對象來說,並不受影響,只是它們之間的聯繫被切斷了。

如果想判斷m_font對象是否已經與某個字體資源相關聯了,最簡單的方法就是利用CGdiObject對象的數據成員m_hObject來判斷,該變量保存了與CGdiObject對象相關聯的Windows GDI資源的句柄。如果已經有關聯了,則調用DeleteObject釋放這個字體資源,然後再和新的資源相關聯。

void CGraphicView::OnFont()
{
	// TODO: 在此添加命令處理程序代碼
	CFontDialog dlg;
	if (IDOK == dlg.DoModal()) {
		//判斷m_font對象是否已經與某個字體資源相關聯了
		//m_hObject變量保存了與CGdiObject對象相關聯的Windows GDI資源的句柄,
		//若不爲空,即爲真,則說明已經關聯過
		if (m_font.m_hObject)
		{
			//如果已經有關聯了,則調用DeleteObject釋放這個字體資源
			m_font.DeleteObject();
		}
		//保存字體對話框選擇的字體樣式
		m_font.CreateFontIndirectW(dlg.m_cf.lpLogFont);
		//保存設置的字體名稱
		m_strFontName = dlg.m_cf.lpLogFont->lfFaceName;
		//讓窗口無效
		Invalidate();
	}
}

運行程序,一切正常:

五、示例對話框

再爲Graphic程序添加一個類似的功能,也就是在已有的設置對話框中作一個示例區,當用戶改變線寬或選擇不同的線型時,在示例區也能看到這種改變。

⭕⭕1)首先在設置對話框中增加一個組框,並將它的Caption設置爲:示例。組框的默認ID:IDC_STATIC。如果在程序中需要對組框進行操作的話,需要修改它的ID。因爲後面的程序會對這個示例組框進行一些操作,所以ID設置爲: IDC_SAMPLE。

⭕⭕2)爲了反映用戶對線寬和線型所做的改變,CSettingDlg類需要捕獲編輯框控件和單選按鈕的通知消息,以反映出用戶所做的改變。

  • 對編輯框控件來說,當用戶在其上面對文本進行改變時,它會向其父窗口,即對話框發送一個EN_CHANGE通知消息。
  • 當用戶單擊單選按鈕時,該按鈕會向對話框發送BN_CLICKED消息。

2.1)首先利用類嚮導爲CSettingDlg類添加編輯框控件(IDC_LINE_WIDTH)的EN_CHANGE消息的響應函數OnChangeLineWidth。

2.2)然後分別對三個單選按鈕(IDC_RADIO1、IDC_RADIO2和IDC_RADIO3),都選擇BN_CLICKED消息,用3種方式添加它們的消息響應函數:OnRadio1、OnRadio2和OnRadio3。

  • 方法1:通過類嚮導添加命令響應函數
  • 方法2:直接鼠標左鍵點擊資源視圖中Setting對話框中的虛線按鈕:
  • 方法3:

2.3)完成示例線條的繪製。
如果把在示例組框中繪製線條的代碼在這四個消息響應函數中都寫一遍,代碼重複性則太高,不太合適。可以考慮這麼做:
2.3.1)在這四個函數中調用Invalidate函數讓窗口無效,當下一次發生WM_PAINT消息時重繪窗口,

void CSettingDlg::OnChangeLineWidth()
{
	// TODO:  如果該控件是 RICHEDIT 控件,它將不
	// 發送此通知,除非重寫 CDialog::OnInitDialog()
	// 函數並調用 CRichEditCtrl().SetEventMask(),
	// 同時將 ENM_CHANGE 標誌“或”運算到掩碼中。

	// TODO:  在此添加控件通知處理程序代碼
	Invalidate();
}


void CSettingDlg::OnClickedRadio1()
{
	// TODO: 在此添加控件通知處理程序代碼
	Invalidate();
}


void CSettingDlg::OnBnClickedRadio2()
{
	// TODO: 在此添加控件通知處理程序代碼
	Invalidate();
}


void CSettingDlg::OnBnClickedRadio3()
{
	// TODO: 在此添加控件通知處理程序代碼
	Invalidate();
}

2.3.2)然後在該消息響應函數: OnPaint中完成示例線條的繪製。爲CSettingDlg類添加WM_PAINT消息的響應函數,並在此函數中完成示例線條的繪製。

void CSettingDlg::OnPaint()
{
	CPaintDC dc(this); // device context for painting
					   // TODO: 在此處添加消息處理程序代碼
					   // 不爲繪圖消息調用 CDialog::OnPaint()					   
	//首先根據指定的線寬和線型創建畫筆,先將畫筆的顏色設置爲紅色			 
	CPen pen(m_nLineStyle, m_nLineWidth, RGB(255, 0, 0));
	//然後將該畫筆對象選入設備描述表中
	dc.SelectObject(&pen);

	//獲得IDC_SAMPLE組框的矩形
	CRect rect;
	//通過調月GetDlgltem函數來得到指向組框窗口對象的指針
	//然後利用GetWindowRect函數獲得組相窗口矩形區域的大小
	GetDlgItem(IDC_SAMPLE)->GetWindowRect(&rect);
	//用選擇的畫筆線型和線寬畫一條示例線,
	//示例線的座標參考組框矩形的座標位置,起點橫座標:矩形左上角右移20;起點縱座標:矩形中間位置的縱座標
	//即畫一條距組框左側20pixel到距組框右側20像素處的線。
	dc.MoveTo(rect.left + 20, rect.top + rect.Height() / 2);
	dc.LineTo(rect.right - 20, rect.top + rect.Height() / 2);



}

運行Graphic程序,打開設置對話框,改變線寬的值,但是在示例組框中並沒有發現繪製的線條。但是把設置對話框移動到屏幕左則,會出現示例內容:
📋📋 原因:之所以發生這種現象的原因主要是因爲GetWindowRect函數的調用,該函數的聲明:

void GetwindowRect(LPRECT IpRect) const;

該函數的參數是指向CRect或RECT結構體的變量,接收屏幕座標,也就是說,通過該函數接收到的是屏幕座標。而在繪圖時是以對話框左上角爲原點的客戶區座標,而通過GetWindowRect函數得到的屏幕座標是以屏幕左上角爲原點的,這樣得到的rect對象的各個座標值是相對屏幕左上角的,值都比較大。但是繪製線條時又是以對話框客戶區的左上角爲原點進行的,因此就繪製到對話框的外面,就看不到線條了。

🟢🟢 解決:通過上面的分析,可以知道在得到組框的矩形區域大小後,需要將其由屏幕座標轉爲客戶座標。這可以利用ScreenToClient函數來實現。該函數的聲明:

void ScreenToclient(LPRECT IpRect) const;

ScreenToClient函數的聲明中要求該函數的參數是LPRECT類型,但下面添加的代碼中傳遞的卻是CRect對象,程序卻能成功編譯,這與之前介紹的情況一樣,因爲CRect類重載了LPRECT操作符。一般情況下,爲了明白起見,通常還是爲此參數添加一個取地址符,表示傳遞的是指針。即:

void CSettingDlg::OnPaint()
{
	CPaintDC dc(this); // device context for painting
					   // TODO: 在此處添加消息處理程序代碼
					   // 不爲繪圖消息調用 CDialog::OnPaint()
	CPen pen(m_nLineStyle,m_nLineWidth, RGB(244,0,0));
	dc.SelectObject(&pen);

	//獲得IDC_SAMPLE組框的矩形
	CRect rect;
	GetDlgItem(IDC_SAMPLE)->GetWindowRect(&rect);
	ScreenToClient(&rect);
	//用選擇的畫筆線型和線寬畫一條示例線,
	//示例線的座標參考組框矩形的座標位置,起點橫座標:矩形左上角右移20;起點縱座標:矩形中間位置的縱座標
	//即畫一條距組框左側20pixel到距組框右側20像素處的線。
	dc.MoveTo(rect.left + 20, rect.top + rect.Height() / 2);
	dc.LineTo(rect.right - 20, rect.top + rect.Height() / 2);
}

再次運行程序,發現示例框中出現了一條示例線條。

改變線寬的值,但是發現示例組框中線條的寬度並沒有發生變化。當一個控件與一個成員變量關聯時,

2.3.3)如果想讓控件上的值反映到成員變量上,必須調用UpdateData函數。

void CSettingDlg::OnPaint()
{
	CPaintDC dc(this); // device context for painting
					   // TODO: 在此處添加消息處理程序代碼
					   // 不爲繪圖消息調用 CDialog::OnPaint()
	UpdateData();
	CPen pen(m_nLineStyle,m_nLineWidth, RGB(244,0,0));
	dc.SelectObject(&pen);

	//獲得IDC_SAMPLE組框的矩形
	CRect rect;
	GetDlgItem(IDC_SAMPLE)->GetWindowRect(&rect);
	ScreenToClient(&rect);
	//用選擇的畫筆線型和線寬畫一條示例線,
	//示例線的座標參考組框矩形的座標位置,起點橫座標:矩形左上角右移20;起點縱座標:矩形中間位置的縱座標
	//即畫一條距組框左側20pixel到距組框右側20像素處的線。
	dc.MoveTo(rect.left + 20, rect.top + rect.Height() / 2);
	dc.LineTo(rect.right - 20, rect.top + rect.Height() / 2);
}

再次運行Graphic程序,打開設置對話框,改變線寬,或在線寬爲1時,選擇不同線型,這時在設置對話框的示例組中隨時可以看到用戶所做的改變。

⭕⭕3)選擇的顏色也要反映到示例線條上。
如果希望用戶選擇了顏色之後,也要反映到示例線條上,就要:
3.1)爲設置對話框再添加一個COLORREF類型的成員變量: m_clr,因爲將在CGraphicView類中訪問這個變量,因此將此變量設置爲公有的。
在這裏插入圖片描述

3.2)並在對話框的構造函數中將此變量初始化紅色:

CSettingDlg::CSettingDlg(CWnd* pParent /*=nullptr*/)
	: CDialog(IDD_DLG_SETTING, pParent)
	, m_nLineWidth(0)
	, m_nLineStyle(0)
{
	m_clr = RGB(255, 0, 0);
}

3.3)因爲對現在的Graphic程序來說,當用戶利用顏色對話框選擇某種顏色後,選擇的結果就會保存到CGraphicView類的m_clr變量,所以應該在CGraphicView類中在設置對話框顯示時將該變量保存的顏色值傳遞給設置對話框的m_clr變量,即在CGraphicView類的OnSetting函數中,在調用CSettingDlg對象的DoModal函數之前將變量保存的顏色值傳遞給設置對話框的m_clr變量:

void CGraphicView::OnSetting()
{
	// TODO: 在此添加命令處理程序代碼
	CSettingDlg dlg;
	dlg.m_nLineWidth = m_nLineWidth;
	dlg.m_nLineStyle = m_nLineStyle;
	//變量保存的顏色值傳遞給設置對話框的m_clr變量
	dlg.m_clr = m_clr;
	if (IDOK == dlg.DoModal()) {
		m_nLineWidth = dlg.m_nLineWidth;
		m_nLineStyle = dlg.m_nLineStyle;
	}
}

3.4)然後修改CSettingDlg類的OnPaint函數中創建畫筆的代碼,將m_clr的值傳遞給該創建函數的第三個參數:

void CSettingDlg::OnPaint()
{
	CPaintDC dc(this); // device context for painting
					   // TODO: 在此處添加消息處理程序代碼
					   // 不爲繪圖消息調用 CDialog::OnPaint()
	UpdateData();
	CPen pen(m_nLineStyle,m_nLineWidth, m_clr);
	dc.SelectObject(&pen);

	//獲得IDC_SAMPLE組框的矩形
	CRect rect;
	GetDlgItem(IDC_SAMPLE)->GetWindowRect(&rect);
	ScreenToClient(&rect);
	//用選擇的畫筆線型和線寬畫一條示例線,
	//示例線的座標參考組框矩形的座標位置,起點橫座標:矩形左上角右移20;起點縱座標:矩形中間位置的縱座標
	//即畫一條距組框左側20pixel到距組框右側20像素處的線。
	dc.MoveTo(rect.left + 20, rect.top + rect.Height() / 2);
	dc.LineTo(rect.right - 20, rect.top + rect.Height() / 2);
}

再次運行Graphic程序,打開顏色對話框選擇某種顏色,然後打開設置對話框,這時可以看到示例組框中線條的顏色就是剛纔所選的顏色。

六、改變對話框和控件的背景及文本顏色

通常,看到的對話框及其上的控件的背景都是淺灰色的,有時爲了使程序的界面更加美觀,需要更改它們的背景,以及控件上的文本的顏色。

首先介紹一個消息:WM_CTLCOLOR,它的響應函數是CWnd類的OnCtIColor。該函數的聲明:

afx_msg HBRUSH OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor);

■ pDC
指向當前要繪製的控件的顯示上下文的指針。
◼ pWnd
指向當前要繪製的控件的指針。
◼ nCtlColor
指定當前要繪製的控件的類型,它的取值如下:

該函數將返回將被用來繪製控件背景的畫刷的句柄當一個子控件將要被繪製時,它都會向它的父窗口發送一個WM_CTLCOLOR(這個父窗口通常都是對話框)消息來準備一個設備上下文(即上述pDC參數),以便使用正確的顏色來繪製該控件。如果想要改變該控件上的文本顏色,可以在OnCtlColor函數中以指定的顏色爲參數調用SetTextColor函數來實現

對對話框來說,它上面的每一個控件在繪製時都要向它發送WM_CTLCOLOR消息,它需要爲每一個控件準備一個DC,該DC將通過pDC參數傳遞給OnCtlColor函數。也就是說,對話框對象的OnCtlColor這個消息響應函數會被多次調用。

6.1 改變整個對話框及其上子控件的背景色

下面爲Graphic程序的設置對話框(CSettingDlg對象)捕獲WM_CTLCOLOR消息,即添加該消息的響應處理:

HBRUSH CSettingDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
	HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);

	// TODO:  在此更改 DC 的任何特性

	// TODO:  如果默認的不是所需畫筆,則返回另一個畫筆
	return hbr;
}

可以看到,在OnCtlColor這個消息響應函數中,首先調用對話框基類: CDialog的OnCtIColor函數,返回一個畫刷句柄(hbr),然後該函數直接返回這個畫刷句柄。之後,系統就會使用這個畫刷句柄來繪製對話框及其子控件的背景。如果想要改變對話框的背景色,只需要自定義一個畫刷,然後讓OnCtIColor函數返回這個畫刷句柄即可

⭕⭕1)首先爲CSettingDlg類定義一個CBrush類型的私有成員變量: m_brush:

private:
	CBrush m_brush;

⭕⭕2)在其構造函數中利用CreateSolidBrush函數將該畫刷初始化爲一個粉色的畫刷:

CSettingDlg::CSettingDlg(CWnd* pParent /*=nullptr*/)
	: CDialog(IDD_DLG_SETTING, pParent)
	, m_nLineWidth(0)
	, m_nLineStyle(0)
{
	m_clr = RGB(255, 0, 0);
	//初始化爲一個粉色的畫刷
	m_brush.CreateSolidBrush(RGB(255,193,193));

}

⭕⭕3)然後在OnCtIColor響應函數返回時返回上述自定義的畫刷:m_brush:

HBRUSH CSettingDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
	HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);

	// TODO:  在此更改 DC 的任何特性

	// TODO:  如果默認的不是所需畫筆,則返回另一個畫筆
	//return hbr;
	return m_brush;
}

運行Graphic程序,打開設置對話框:
在這裏插入圖片描述
可以看到對話框和控件的背景都變成了粉色:這是因爲在該對話框繪製時,會調用OnCtlColor函數繪製整個對話框的背景,即用m_brush畫刷來繪製對話框背景。當繪製其子控件時,也是調用這個OnCtlColor函數,也是用m_brush這個畫刷來繪製背景的。因此我們看到子控件和對話框的背景都是粉色的。而對於OKCancel兩個按鈕的背景不改變的原因,稍後介紹。

6.2 僅改變某個子控件的背景及文本顏色

如果想要精確控制對話框上某個控件(例如本例中的線型組框)的背景的繪製,就需要判斷當前繪製的是哪一個控件

⭕⭕1)通過上面的介紹,通過OnCtlColor函數的第二個參數:pWnd能夠知道當前繪製的控件窗口對象,這時可以通過調用Cwnd類的GetDlgCtrlID成員函數得到該控件的ID,然後判斷該ID是否就是需要控制其背景繪製的控件ID,若是就處理。GetDlgCtrllD函數的聲明:

int GetDlgCtrlID() const;

  該函數不僅能返回對話框子控件的ID,還能返回子窗口的ID。但是因爲頂層窗口不具有ID值,所以如果調用該函數的CWnd對象是一個頂層窗口,該函數返回的值就是一個無效值。

⭕⭕2)在CSettingDlg類的OnCtIColor函數中就可以利用傳遞進來的pWnd來調用GetDlgCtrlD函數,然後判斷一下如果其返回值等於線型組框的ID (IDC_LINE_STYLE),那麼就可以知道當前繪製是的線型組框,那就需要改變該控件的背景色,即返回自定義的畫刷,對其他控件仍使用先前的畫刷。

HBRUSH CSettingDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
	HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
	// TODO:  在此更改 DC 的任何特性

	// TODO:  如果默認的不是所需畫筆,則返回另一個畫筆
	//ID=IDC_LINE_STYLE的控件的背景設置爲粉色
	if (pWnd->GetDlgCtrlID()==IDC_LINE_STYLE)
	{
		return m_brush;
	}
	//其他的背景色保存默認。
	return hbr;
}

運行Graphic程序,打開設置對話框,可以發現線型組框的背景色變成了粉色。

OnCtlColor函數要求返回HBRUSH類型的畫刷句柄,但上述代碼中返回了一個CBrush類型的畫刷對象。這是因爲CBrush類重載了HBRUSH操作符。

⭕⭕3)爲了改變線型組框控件上的文本顏色,應在OnCtlColor消息響應函數對當前繪製的控件進行判斷,如果判斷出當前繪製的控件就是線型組框控件,在返回自定義的畫刷之前,就調用SetTextColor函數將該控件上的文本設置爲希望的顏色。本例將線型組框控件上的文本設置爲綠色:

HBRUSH CSettingDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
	HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);

	// TODO:  在此更改 DC 的任何特性

	// TODO:  如果默認的不是所需畫筆,則返回另一個畫筆
	//ID=IDC_LINE_STYLE的控件的背景設置爲粉色
	if (pWnd->GetDlgCtrlID()==IDC_LINE_STYLE)
	{
		//將線型組框控件上的文本設置爲綠色
		pDC->SetTextColor(RGB(0, 100, 0));
		return m_brush;
	}
	//其他的背景色保存默認。
	return hbr;
}

設置的顏色無效,暫時不知道原因。


⭕⭕4)實現編輯框控件背景的改變。實現原理同上,這時在OnCtlColor函數中如果判斷當前繪製的是編輯框控件,就設置文本顏色,並返回自定義的畫刷。
因此可以參照上面修改線型組框控件的代碼來實現編輯框控件背景色和文本顏色的改變:
4.1)改變編輯框的背景顏色

HBRUSH CSettingDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
	HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);

	// TODO:  在此更改 DC 的任何特性

	// TODO:  如果默認的不是所需畫筆,則返回另一個畫筆
	/*ID=IDC_LINE_STYLE的控件的背景設置爲粉色
	if (pWnd->GetDlgCtrlID()==IDC_LINE_STYLE)
	{
		//將線型組框控件上的文本設置爲綠色
		pDC->SetTextColor(RGB(0,100,0));
		//將線型組框上的文字的背景設置爲透明的
		pDC->SetBkMode(TRANSPARENT);
		return m_brush;
	}*/
	
	if (pWnd->GetDlgCtrlID()== IDC_LINE_WIDTH)
	{
		pDC->SetTextColor(RGB(0, 100, 0));
		return m_brush;
	}
	//其他的背景色保存默認。
	return hbr;
}


4.2)改變編輯框字體的顏色爲紅色:

HBRUSH CSettingDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
	HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);

	// TODO:  在此更改 DC 的任何特性

	// TODO:  如果默認的不是所需畫筆,則返回另一個畫筆
	/*ID=IDC_LINE_STYLE的控件的背景設置爲粉色
	if (pWnd->GetDlgCtrlID()==IDC_LINE_STYLE)
	{
		//將線型組框控件上的文本設置爲綠色
		pDC->SetTextColor(RGB(0,100,0));
		//將線型組框上的文字的背景設置爲透明的
		pDC->SetBkMode(TRANSPARENT);
		return m_brush;
	}*/
	
	if (pWnd->GetDlgCtrlID()== IDC_LINE_WIDTH)
	{
		pDC->SetTextColor(RGB(255, 0, 0));
		return m_brush;
	}
	//其他的背景色保存默認。
	return hbr;
}

在這裏插入圖片描述
4.3)有些控件上的文本本身也有背景色,設置背景色爲透明模式,將編輯框上的文字的背景設置爲透明的:

HBRUSH CSettingDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
	HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);

	// TODO:  在此更改 DC 的任何特性

	// TODO:  如果默認的不是所需畫筆,則返回另一個畫筆
	/*ID=IDC_LINE_STYLE的控件的背景設置爲粉色
	if (pWnd->GetDlgCtrlID()==IDC_LINE_STYLE)
	{
		//將線型組框控件上的文本設置爲綠色
		pDC->SetTextColor(RGB(0,100,0));
		//將線型組框上的文字的背景設置爲透明的
		pDC->SetBkMode(TRANSPARENT);
		return m_brush;
	}*/
	
	if (pWnd->GetDlgCtrlID()== IDC_LINE_WIDTH)
	{
		pDC->SetTextColor(RGB(255, 0, 0));
		pDC->SetBkMode(TRANSPARENT);
		return m_brush;
	}
	//其他的背景色保存默認。
	return hbr;
}


4.4)如果要改變單行編輯框控件的背景顏色,可以調用SetBkColor函數設置其背景色。設置其背景色爲黑色:

HBRUSH CSettingDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
	HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);

	// TODO:  在此更改 DC 的任何特性

	// TODO:  如果默認的不是所需畫筆,則返回另一個畫筆
	/*ID=IDC_LINE_STYLE的控件的背景設置爲粉色
	if (pWnd->GetDlgCtrlID()==IDC_LINE_STYLE)
	{
		//將線型組框控件上的文本設置爲綠色
		pDC->SetTextColor(RGB(0,100,0));
		//將線型組框上的文字的背景設置爲透明的
		pDC->SetBkMode(TRANSPARENT);
		return m_brush;
	}*/
	
	if (pWnd->GetDlgCtrlID()== IDC_LINE_WIDTH)
	{
		pDC->SetTextColor(RGB(255, 0, 0));
		//pDC->SetBkMode(TRANSPARENT);
		pDC->SetBkColor(RGB(0, 0, 0));
		return m_brush;
	}
	//其他的背景色保存默認。
	return hbr;
}

📢📢📢

  • SetTextColor()函數很明顯是設置文本顏色的;
  • SetBkColor()函數不是用來設置控件背景顏色的,而是用來設置文本背景顏色的,就是包含文本的矩形;
  • SetBkMode()是用來設定文字背景模式的,參數只有兩個選擇OPAQUE、TRANSPARENT表示是否透明。

6.3 改變控件的文本字體

接下來,改變控件上文本的字體。也就是說,在繪製控件時爲其準備一種字體,讓它按照此種字體顯示。

⭕⭕1)爲了顯示控件字體的改變效果,再爲Graphic程序的設置對話框資源增加一個靜態文本控件,設置其ID爲: IDC_TEXT, Caption:WaitFoF,你好啊!

⭕⭕2)然後在程序中修改該控件的文本字體。先爲CSettingDlg類增加一個CFont類型的私有成員變量: m_font:

⭕⭕3)其構造函數中初始化該變量,創建一個大小爲70,名稱爲“漢儀綜藝體簡”的字體。:

CSettingDlg::CSettingDlg(CWnd* pParent /*=nullptr*/)
	: CDialog(IDD_DLG_SETTING, pParent)
	, m_nLineWidth(0)
	, m_nLineStyle(0)
{
	m_clr = RGB(255, 0, 0);
	//初始化爲一個粉色的畫刷
	m_brush.CreateSolidBrush(RGB(255, 193, 193));
	//創建一個大小爲70,名稱爲“漢儀綜藝體簡”的字體
	m_font.CreatePointFont(70, _T("漢儀綜藝體簡"));

}

⭕⭕4)然後在CSettingDlg類的OnCtlColor函數中判斷當前繪製的如果是靜態文本框控件,那麼就將新建的字體m_font選入設備描述表中,這樣DC中的字體就被改變了,它就會使用新創建的字體來顯示文本:

HBRUSH CSettingDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
	HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);

	// TODO:  在此更改 DC 的任何特性

	// TODO:  如果默認的不是所需畫筆,則返回另一個畫筆
	//ID=IDC_LINE_STYLE的控件的背景設置爲粉色
	if (pWnd->GetDlgCtrlID()==IDC_LINE_STYLE)
	{
		//將線型組框控件上的文本設置爲綠色
		pDC->SetTextColor(RGB(0,100, 0));
		//將線型組框上的文字的背景設置爲透明的
		pDC->SetBkMode(TRANSPARENT);
		return m_brush;
	}
	if (pWnd->GetDlgCtrlID()== IDC_LINE_WIDTH)
	{
		pDC->SetTextColor(RGB(255,0,0));
		//pDC->SetBkMode(TRANSPARENT);
		pDC->SetBkColor(RGB(0, 0, 0));
		return m_brush;
	}
	if (pWnd->GetDlgCtrlID() == IDC_TEXT)
	{
		pDC->SelectObject(&m_font);
	}
	//其他的背景色保存默認。
	return hbr;
}

6.4 改變按鈕控件的背景色及文本顏色

接下來,根據上面的知識。按照同樣的方法改變Graphic程序中設置對話框上的OK按鈕的背景色及其文字的顏色。

⭕⭕1)在CSettingDlg類的OnCtlColor函數中,如果判斷出當前繪製的控件的ID等於OK按鈕的ID:IDOK,那麼就返回自定義的畫刷:

HBRUSH CSettingDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
	HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);

	// TODO:  在此更改 DC 的任何特性

	// TODO:  如果默認的不是所需畫筆,則返回另一個畫筆
	//ID=IDC_LINE_STYLE的控件的背景設置爲粉色
	if (pWnd->GetDlgCtrlID()==IDC_LINE_STYLE)
	{
		//將線型組框控件上的文本設置爲綠色
		pDC->SetTextColor(RGB(255, 0, 0));
		//pDC->SetBkColor(RGB(255, 0, 0));
		//將線型組框上的文字的背景設置爲透明的
		//pDC->SetBkMode(TRANSPARENT);
		return m_brush;
	}
	if (pWnd->GetDlgCtrlID()== IDC_LINE_WIDTH)
	{
		pDC->SetTextColor(RGB(255,0,0));
		//pDC->SetBkMode(TRANSPARENT);
		//背景黑色
		pDC->SetBkColor(RGB(0, 0, 0));
		return m_brush;
	}
	if (pWnd->GetDlgCtrlID() == IDC_TEXT)
	{
		pDC->SelectObject(&m_font);
	}
	if (pWnd->GetDlgCtrlID() == IDOK)
	{
		//紅色字體
		pDC->SetTextColor(RGB(255, 0, 0));
		return m_brush;
	}
	//其他的背景色保存默認。
	return hbr;
}

運行Graphic程序,發現新加的代碼並沒有起作用,OK按鈕的背景色和文字顏色並沒有被改變。
對於按鈕來說,使用上述方法來改變其背景和文本顏色是無效的,只能尋找其他的解決方法。

⭕⭕2)實際上,如果想要改變按鈕控件的背景色和文本顏色,需要使用CButton類的一個成員函數:DrawItem,該函數的聲明:

virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);

從其聲明形式,可以知道這個函數是一個虛函數。當一個自繪製按鈕(具有BS_OWNERDRAW風格的按鈕)在繪製時,框架將會調用這個虛函數。因此,如果想要實現一個自繪製按鈕控件的繪製,應該重載這個虛函數。該函數的參數是DRAWITEMSTRUCT結構類型,其定義如下所示:

typedef struct tagDRAWITEMSTRUCT {
    UINT        CtlType;
    UINT        CtlID;
    UINT        itemID;
    UINT        itemAction;
    UINT        itemState;
    HWND        hwndItem;
    HDC         hDC;
    RECT        rcItem;
    ULONG_PTR   itemData;
} DRAWITEMSTRUCT;

該結構體中有一個hDC成員,指向將要繪製的按鈕的DC。爲了繪製這個按鈕,可以向該DC中選入自定義的顏色、畫刷等對象。但此重載函數結束前,一定要恢復hDC中原有對象。

因此,如果想要改變OK按鈕的文本顏色:

  1. 需要編寫一個自己的按鈕類,讓這個類派生於CButton類
  2. 重寫Drawltem函數,在此函數中實現按鈕背景色和文本顏色的設置;
  3. 然後,將OK按鈕對象與這個類相關聯

這樣,在繪製OK按鈕時,框架就會調用這個自定義的按鈕類的Drawltem函數來繪製。

🔳🔲◼▪◼🔲🔳 改變按鈕文字顏色

2.1)Graphic程序創建一個派生於CButton的類,新建MFC類。將新增的類命名爲:CTestBtn、基類: CButton。

CTestBtn.h:

#pragma once


// CTestBtn

class CTestBtn : public CButton
{
	DECLARE_DYNAMIC(CTestBtn)

public:
	CTestBtn();
	virtual ~CTestBtn();

protected:
	DECLARE_MESSAGE_MAP()
};

CTestBtn.cpp:

// CTestBtn.cpp: 實現文件
//

#include "pch.h"
#include "Graphic.h"
#include "CTestBtn.h"


// CTestBtn

IMPLEMENT_DYNAMIC(CTestBtn, CButton)

CTestBtn::CTestBtn()
{

}

CTestBtn::~CTestBtn()
{
}

BEGIN_MESSAGE_MAP(CTestBtn, CButton)
END_MESSAGE_MAP()
// CTestBtn 消息處理程序

2.2)爲此類添加DrawItem虛函數的重寫:

void CTestBtn::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{

	// TODO:  添加您的代碼以繪製指定項

	UINT uStyle = DFCS_BUTTONPUSH;

	//這段代碼只對按鈕有效。
	ASSERT(lpDrawItemStruct->CtlType == ODT_BUTTON);

	//如果選擇繪圖,將樣式添加到DrawFrameControl。
	if (lpDrawItemStruct->itemState & ODS_SELECTED)
		uStyle |= DFCS_PUSHED;

	// 繪製按鈕框架
	::DrawFrameControl(lpDrawItemStruct->hDC, &lpDrawItemStruct->rcItem, DFC_BUTTON, uStyle);

	//獲取按鈕的文本。
	CString strText;
	GetWindowText(strText);

	//按鈕文字使用文字的顏色爲綠色。
	COLORREF crOldColor = ::SetTextColor(lpDrawItemStruct->hDC, RGB(0, 255, 0));
	::DrawText(lpDrawItemStruct->hDC,strText,strText.GetLength(),
		&lpDrawItemStruct->rcItem,DT_SINGLELINE | DT_VCENTER | DT_CENTER);
	::SetTextColor(lpDrawItemStruct->hDC, crOldColor);
}

2.3)爲設置對話框上的OK按鈕關聯一個成員變量。將變量名稱設置爲:m_btnTest,類型選擇爲:CTestBtn

在SettingDlg.h文件中包含CTestBtn類的定義文件:

#include "CTestBtn.h"

此外自繪製控件應該具有BS_OWNERDRAW風格,這可以通過設置控件屬性對話框中的Owner Draw選項爲True:

運行Graphic程序,打開設置對話框,這時可以看到OK按鈕的文字變成綠色,即CTestBtn類的設置起作用:

此時可以刪除CSettingDlg類中OnCtlColor中關於OK按鈕的代碼。

🔳🔲◼▪◼🔲🔳 改變按鈕背景顏色
爲了改變OK按鈕的背景色,使用CButtonST類,此類是在網上找到的一個按鈕類,這個類的功能比較豐富,可以用於實際開發中。

CButtonST類的源碼中含有如下文件:

爲了演示CButtonST類的使用,在Graphic程序中的設置對話框資源上再添加一個按鈕控件,將其ID設置:IDC_BTN_ST,Caption設置爲:ButtonST


下面演示使用這個類:
2.1)將類的頭文件和源文件複製到自己的Graphic工程所在目錄下,並將它們添加到自己的Graphic工程中。方法如下:
在這裏插入圖片描述
2.2)在SettingDlg.h文件中包含CButtonST類的頭文件:

#include "BtnST.h"

2.3)爲CButtonST按鈕添加一個關聯的成員變量:

與上面設置OK按鈕背景色和文本顏色的實現方法不同的是,使用CButtonST這個類時,並不需要通過按鈕屬性對話框設置其Owner Draw選項爲True,這個類的內部會自動設置BS_OWNERDRAW分格。

2.4)接下來初始化m_btnST變量,可以放到CSettingDlg類的OnInitDialog函數中進行,所以在CSettingDlg中重寫OnInitDialog函數。

初始化工作包括兩個部分:設置背景色和前景色,後者也就是按鈕活動時文字的顏色。

BOOL CSettingDlg::OnInitDialog()
{
	CDialog::OnInitDialog();

	// TODO:  在此添加額外的初始化
	//造成了m_btnST的重複子類化————屏蔽
	//m_btnST.SubclassDlgItem(IDC_BTN_ST,this);

	//普通狀態時的背景色 #9400D3	
	m_btnST.SetColor(CButtonST::BTNST_COLOR_BK_OUT, RGB(148,0,211));
	//普通狀態時的前景色
	m_btnST.SetColor(CButtonST::BTNST_COLOR_FG_OUT, RGB(255,0,0));
	
	//鼠標放在按鈕內時的背景色
	m_btnST.SetColor(CButtonST::BTNST_COLOR_BK_IN, RGB(205,155,155));
	//鼠標放在按鈕內時的前景色
	m_btnST.SetColor(CButtonST::BTNST_COLOR_FG_IN, RGB(72, 118, 255));

	//按鈕被按下後的背景色
	m_btnST.SetColor(CButtonST::BTNST_COLOR_BK_FOCUS, RGB(255,127, 0));
	
	return TRUE;  // return TRUE unless you set the focus to a control
				  // 異常: OCX 屬性頁應返回 FALSE
}

在這裏插入圖片描述

使用的工具類,可點擊下載:CButtonST

七、位圖顯示

有多種方式可以實現在窗口中顯示位圖:

  • 第一步是創建位圖,這可以先利用CBitmap構造一個位圖對象,然後利用LoadBitmap函數加載一幅位圖資源。

  • 第二步是創建兼容DC。其中CreateCompatibleDC函數將創建一個內存設備上下文,與參數pDC所指定的DC相兼容。內存設備上下文實際上是一個內存塊,表示一個顯示的表面。如果想把圖像複製到實際的DC中,可以先用其兼容的內存設備上下文在內存中準備這些圖像,然後再將這些數據複製到實際DC中。

  • 第三步是將位圖選入兼容DC中。當兼容的內存設備上下文被創建時,它的顯示錶面是標準的一個單色像素寬和一個單色像素高。在應用程序中可以使用內存設備上下文進行繪圖操作之前,必須將一個具有正確高度和寬度的位圖選入設備上下文,這時內存設備上下文的顯示錶面大小就由當前選入的位圖決定了。

  • 第四步是將兼容DC中的位圖貼到當前DC中。有多個函數可以以幾種不同的方式完成這一操作。調用BitBlt函數將兼容DC中的位圖複製到當前DC中。BitBlt函數的功能是把源設備上下文中的位圖複製到目標設備上下文中。該函數的聲明形式如下所示:

    BOOL BitBlt(int x, int y, int nWidth, int nHeight, CDC* pSrcDC, int xSrc,int ySrc, DWORD dwRop);
    

    ◼ x和y
    指定目標矩形區域左上角的x和y座標。
    ◼ nWidth和nHeight
    指定源和目標矩形區域的邏輯寬度和高度。因爲該函數在複製時是按照1:1的比例進行的,所以源矩形區域和目標矩形區域的寬度和高度是相同的。
    ◼ pSrcDC
    指向源設備上下文對象。
    ◼ xSrc 和 ySrc
    指定源矩形區域左上角的x和y座標。
    ◼ dwRop
    指定複製的模式,也就是指定源矩形區域的顏色數據,如何與目標矩形區域的顏色數據組合以得到最後的顏色。

    如果函數調用成功,則BitBIt函數將返回非0值;否則返回0。

按照上述四個步驟,來完成在窗口顯示位圖的功能。
⭕⭕1)首先需要準備一幅位圖。

然後在Graphic工程中,導入位圖資源:
調整位圖:
在這裏插入圖片描述

⭕⭕2)完成位圖的顯示。
首先需要了解一下窗口的繪製過程,包含兩個步驟:

  • 首先擦除窗口背景,
  • 然後在對窗口重新進行繪製。

🔳🔲◼▪◼🔲🔳擦除窗口背景時完成位圖的顯示。


當擦除窗口背景時,程序會發送一個WM_ERASEBKGND消息,因此可以在此響應函數中完成位圖的顯示。另外,根據前面的知識,可以知道應該在視類窗口中進行位圖的繪製。

因此爲Graphic工程的CGraphicView類添加WM_ERASEBKGND消息的響應處理函數:OnEraseBkgnd。

BOOL CGraphicView::OnEraseBkgnd(CDC* pDC)
{
	// TODO: 在此添加消息處理程序代碼和/或調用默認值
	//第一步:
    //首先構建位圖對象: bitmap
	CBitmap bitmap;
	//加載圖像
	bitmap.LoadBitmap(IDB_BITMAP1);

	//第二步:創建兼容的DC
	//創建與當前DC (pDC)兼容的DC: dcCompatible
	CDC dcCompatible;
	dcCompatible.CreateCompatibleDC(pDC);

	//第三步:將位圖選入兼容DC中
	//調用SelectObject函數將位圖選入兼容DC中,從而確定兼容DC顯示錶面的大小
	dcCompatible.SelectObject(&bitmap);

	//第四步:將兼容DC (dcCompatible)中的位圖複製到目的DC (pDC)中
	//因爲要指定複製目的矩形區域的寬度和高度,首先需要得到目的DC客戶區大小,所以就構造一個CRect對象
	CRect rect;
	//然後調用GetClientRect函數得到客戶區大小
	GetClientRect(&rect);
	//源DC就是先前創建的兼容DC,
    //複製模式選擇: SRCCOPY:就是將源位圖複製到目的矩形區域中
	pDC->BitBlt(0, 0, rect.Width(), rect.Height(), &dcCompatible, 0, 0, SRCCOPY);

	//最後,調用視類的基類(即CView)的OnEraseBkgnd函數。
	return CView::OnEraseBkgnd(pDC);
}

運行Graphic程序,但是看到程序窗口在顯示時,位圖只是閃了一下就消失了。主要是因爲在上述OnEraseBkgnd代碼中,在調用BitBIt函數複製位圖之後,又調用了視類的基類(即CView)的OnEraseBkgnd函數,該函數的調用把窗口的背景擦除了,所以位圖只是閃了一個就消失了。

⭕⭕3)對OnEraseBkgnd函數來說,如果其擦除了窗口背景,將返回非0值。因此CGraphicView類的OnEraseBkgnd函數的最後,不應該再調用其基類的OnEraseBkgnd函數,而是應該直接返回TRUE值:

BOOL CGraphicView::OnEraseBkgnd(CDC* pDC)
{
	// TODO: 在此添加消息處理程序代碼和/或調用默認值
	//第一步:
    //首先構建位圖對象: bitmap
	CBitmap bitmap;
	//加載圖像
	bitmap.LoadBitmap(IDB_BITMAP1);

	//第二步:創建兼容的DC
	//創建與當前DC (pDC)兼容的DC: dcCompatible
	CDC dcCompatible;
	dcCompatible.CreateCompatibleDC(pDC);

	//第三步:將位圖選入兼容DC中
	//調用SelectObject函數將位圖選入兼容DC中,從而確定兼容DC顯示錶面的大小
	dcCompatible.SelectObject(&bitmap);

	//第四步:將兼容DC (dcCompatible)中的位圖複製到目的DC (pDC)中
	//因爲要指定複製目的矩形區域的寬度和高度,首先需要得到目的DC客戶區大小,所以就構造一個CRect對象
	CRect rect;
	//然後調用GetClientRect函數得到客戶區大小
	GetClientRect(&rect);
	//源DC就是先前創建的兼容DC,
    //複製模式選擇: SRCCOPY:就是將源位圖複製到目的矩形區域中
	pDC->BitBlt(0, 0, rect.Width(), rect.Height(), &dcCompatible, 0, 0, SRCCOPY);
	return TRUE;
}


⭕⭕4)位圖壓縮和拉伸。
4.1)若要在窗口中完整地顯示一幅位圖,如果位圖比窗口大,就要壓縮位圖;如果小,就可以拉伸位圖。然而BitBlt函數是沒有辦法實現壓縮和拉伸的,因爲它是按照1:1的比例進行復制的。再介紹另一個顯示位圖的函數:StretchBlt,其聲明:

BOOL StretchBlt (int x, int y, int nwidth, int nHeight, CDC* pSrcDc, int xSrc, int ysrc, int nSrcwidth, int nSrcHeight, DWORD dwRop );

該函數與前面介紹的BitBlt函數的功能基本相同,都是從源矩形區域中複製一個位圖到目標矩形,但是與BitBlt函數不同的是:StretchBlt函數可以實現位圖的拉伸或壓縮,以適合目的矩形區域的尺寸

與BitBlt函數相比,StretchBlt函數只是多了兩個參數: nSrcWidth和nSrcHeight,分別用來指示源矩形區域的寬度和高度。

4.2)前面已經說過,兼容DC原始只有1個像素大小。它的大小由選入的位圖的大小所決定,也就是說,如果想要得到源矩形的寬度和高度,就要想辦法得到選入的位圖的寬度和高度。選入的位圖的寬度和高度可以通過調用CBitmap類的GetBitmap函數來得到,該函數的原型聲明如下:

int GetBitmap(BITMAP* pBitMap);

該函數有一個參數,是一個指向BITMAP結構體的指針,該函數將用位圖的信息填充,BITMAP結構體的定義如下:

typedef struct tagBITMAP
  {
    LONG        bmType;
    LONG        bmWidth;
    LONG        bmHeight;
    LONG        bmWidthBytes;
    WORD        bmPlanes;
    WORD        bmBitsPixel;
    LPVOID      bmBits;
  } BITMAP;

其中bmWidth指示位圖的寬度,bmHeight指示位圖的高度。

4.3)因此在CGraphicView類的OnEraseBkgnd函數中,在位圖對象bitmap成功加載位圖資源之後,通過調用CBitmap類的GetBitmap函數來得到選入的位圖的寬度和高度,接下來就可以調用StretchBlt函數複製位圖了。


BOOL CGraphicView::OnEraseBkgnd(CDC* pDC)
{
	// TODO: 在此添加消息處理程序代碼和/或調用默認值
	//第一步:
    //首先構建位圖對象: bitmap
	CBitmap bitmap;
	//加載圖像
	bitmap.LoadBitmap(IDB_BITMAP1);

	BITMAP bmp;
	bitmap.GetBitmap(&bmp);


	//第二步:創建兼容的DC
	//創建與當前DC (pDC)兼容的DC: dcCompatible
	CDC dcCompatible;
	dcCompatible.CreateCompatibleDC(pDC);

	//第三步:將位圖選入兼容DC中
	//調用SelectObject函數將位圖選入兼容DC中,從而確定兼容DC顯示錶面的大小
	dcCompatible.SelectObject(&bitmap);

	//第四步:將兼容DC (dcCompatible)中的位圖複製到目的DC (pDC)中
	//因爲要指定複製目的矩形區域的寬度和高度,首先需要得到目的DC客戶區大小,所以就構造一個CRect對象
	CRect rect;
	//然後調用GetClientRect函數得到客戶區大小
	GetClientRect(&rect);

	//源DC就是先前創建的兼容DC,
    //複製模式選擇: SRCCOPY:就是將源位圖複製到目的矩形區域中
	//pDC->BitBlt(0, 0, rect.Width(), rect.Height(), &dcCompatible, 0, 0, SRCCOPY);
	pDC->StretchBlt(0, 0, rect.Width(), rect.Height(), &dcCompatible, 0, 0, bmp.bmWidth, bmp.bmHeight, SRCCOPY);
	
	return TRUE;
}

運行Graphic程序,這時就可以看到整幅位圖都顯示出來了:

🔳🔲◼▪◼🔲🔳窗口重繪時完成位圖的顯示。

本例是在窗口顯示更新的第一步,即擦除窗口背景這一步實現位圖的顯示。也可以在窗口顯示更新的第二步,即重繪窗口時實現這一功能。窗口重繪時會調用OnDraw函數,因此可以把顯示位圖的代碼放到這個函數中。

void CGraphicView::OnDraw(CDC* pDC)
{
	CGraphicDoc* pDoc = GetDocument();
	ASSERT_VALID(pDoc);
	if (!pDoc)
		return;

	// TODO: 在此處爲本機數據添加繪製代碼
	CFont* pOldFont = pDC->SelectObject(&m_font);
	pDC->TextOutW(0, 0, m_strFontName);
	pDC->SelectObject(pOldFont);

	//第一步:
	//首先構建位圖對象: bitmap
	CBitmap bitmap;
	//加載圖像
	bitmap.LoadBitmap(IDB_BITMAP1);

	BITMAP bmp;
	bitmap.GetBitmap(&bmp);


	//第二步:創建兼容的DC
	//創建與當前DC (pDC)兼容的DC: dcCompatible
	CDC dcCompatible;
	dcCompatible.CreateCompatibleDC(pDC);

	//第三步:將位圖選入兼容DC中
	//調用SelectObject函數將位圖選入兼容DC中,從而確定兼容DC顯示錶面的大小
	dcCompatible.SelectObject(&bitmap);

	//第四步:將兼容DC (dcCompatible)中的位圖複製到目的DC (pDC)中
	//因爲要指定複製目的矩形區域的寬度和高度,首先需要得到目的DC客戶區大小,所以就構造一個CRect對象
	CRect rect;
	//然後調用GetClientRect函數得到客戶區大小
	GetClientRect(&rect);

	//源DC就是先前創建的兼容DC,
	//複製模式選擇: SRCCOPY:就是將源位圖複製到目的矩形區域中
	//pDC->BitBlt(0, 0, rect.Width(), rect.Height(), &dcCompatible, 0, 0, SRCCOPY);
	 pDC->StretchBlt(0, 0, rect.Width(), rect.Height(), &dcCompatible, 0, 0, bmp.bmWidth, bmp.bmHeight, SRCCOPY);

}

總結比較:程序顯示的結果是一樣的,但是產生的效果是不一樣的。

  • 窗口重繪時完成位圖的顯示
    當窗口尺寸發生變化時,程序窗口會有閃爍現象,這是因爲當窗口尺寸發生變化時,會引起窗口重繪操作,它會首先擦除背景,然後在OnDraw函數中再重新貼上位圖。
  • 窗口擦除時完成位圖的顯示
    窗口閃爍比較小。因爲沒有擦除背景,而是直接貼上位圖。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章