MFC OpenGL繪圖

雖然MFC已經落伍好多年,而且用來做界面非常的不好用。。。但是我既不會C#也不會QT,又需要使用OpenGL,就只能將就用了。。。

一、首先介紹Windows圖像程序設計中幾個重要的概念:

GDI(Graphics Device Interface,圖形設備接口):這是Windows API的一個庫。當Windows應用程序需要顯示點、線、圖像、文字等內容,在顯示器或打印輸入這些內容時,就需要用到GDI。Windows應用程序不能直接操作系統的硬件(比如顯卡),GDI就爲應用程序提供了相關的接口。
其相關的函數接口、數據類型等都在WinGDI.h中聲明(已經由Windows.h引入),在程序開發時,需要鏈接到Gdi32.lib。

DC(Device Contexts,設備上下文):是GDI庫中最基本也是最重要的概念。DC是一個對象,設定了圖形輸出的特性和屬性。
系統中可以有多個DC,每一個DC都必須關聯到一個特定的圖像輸出設備。這些設備可以是真實存在的物理設備(顯示器、打印機、繪圖儀等),也可以使虛擬設備。這些反應在DC的類型上,DC具有4種類型:“顯示”、“打印機”、“內存”、“信息”
其中顯示類型DC是最常用的,它被關聯到了顯示設備上,所有的圖像輸出操作將直接反映在顯示器上。
注:DC也可以只是設備全部輸出範圍的一部分。比如界面上某個窗口的客戶區也可以有DC與之對應,對這樣的DC進行操作只會影響到窗口客戶區。

如果要將圖像輸出到特定的設備只需要創建相應類型的DC即可(注:對不同類型DC的操作是統一的),我們只關注獲取顯示器相關DC的操作:(以下的函數都是GDI庫中的接口函數)
1. 獲取DC - GetDC(HWND hWnd)
調用該函數會返回hWnd參數所指定的窗口的客戶區所對應的DC的句柄。
如果hWnd參數設置爲NULL,那麼函數會返回整個桌面的DC。

2. 另一種獲取DC的方法 - CreateDC
該函數也是用來獲取DC句柄,與GetDC不同的是,CreateDC可以獲取非顯示器輸出DC。只需指定不同的參數即可。
獲取顯示器的DC:HDC hScreenDC = CreateDC("DISPLAY", NULL, NULL, NULL);
若想獲取打印機的DC,一般是將第一個參數改爲"WINSPOOL"。

3. 釋放DC - ReleaseDC(HWND hWnd, HDC hDC)
該函數的作用是釋放DC,使其他應用程序可以使用。

4. 釋放DC的相關係統資源 - DeleteDC(HDC hDC)
這個函數並不常用,一般做圖結束後,我們只需要調用ReleaseDC釋放DC即可。


也許看到這裏,對DC具體是個什麼東西還沒有什麼清楚的概念。下面大概講一下。。。
     DC實際上是包含了一系列的圖形對象,比如位圖(Bitmap)、畫刷(Brushe)、畫筆(Pen)、字體(Font)、邏輯調色板(Logical palette)等等,GDI庫中還定義了一系列接口函數,應用程序通過調用這些接口函數來操作當前DC中的圖形對象,完成期望的繪圖操作,最終影響放映到對應設備的輸出上
     比如先創建一個窗口,然後得到該窗口對應的DC,這時候系統會爲DC創建默認的圖形對象(位圖和路徑除外),此時如果不進行任何操作,那麼顯示的窗口是一片白(也就是說剛開始是一塊白色畫布)。然後我們可以調用GDI庫中的接口函數進行畫圖操作,或者載入位圖(相當於在白色畫布上作畫)。DC中就是提供了對畫布進行作畫的工具
注:DC對應設備的顯示信息都存儲在位圖對象中。

爲了更清楚DC的作用,下面舉個很簡單的例子:在屏幕上畫一條線
首先,獲得整個顯示器的DC:
HDC hdC = GetDC(NULL);
創建新的畫筆對象:
COLORREF cPen = RGB(0,0,0); //指定畫筆顏色爲黑色
HPEN hpen = CreatePen(PS_SOLID, 10, cPen); //創建新的畫筆,返回新畫筆的句柄
將新創建的畫筆指定爲DC的當前畫筆:(對同一種類型的圖形對象,DC中只能有一個當前對象)
HPEN hpenOld = SelectObject(hdc, hpen);
畫線:
LineTO(hdc, 500, 500);
畫圖操作結束,還原畫筆:
SelectObject(hdc, hpenOld);
釋放畫筆資源:
DeleteObject(hpen);
釋放DC:
ReleaseDC(NULL, hdc);


二、下面來討論使用OpenGL來繪圖的相關知識:

使用OpenGL繪圖與使用GDI庫繪圖是不同的,主要體現在:OpenGL採用的是RC(Render Context,渲染上下文)繪圖。
DC和RC的區別和聯繫
1. 在Windows中使用GDI繪圖時必須指定在哪個DC中繪製,同樣地,在使用OpenGL函數時也必須指定一個所謂的RC。正如設備上下文DC要存儲GDI的繪製環境信息如筆、刷和字體等,RC也必須存儲OpenGL所需的渲染信息如像素格式等。
2. Windows下的窗口和DC支持的位圖格式(PIXELFORMAT)屬性,和RC有位圖結構上的一致。只要在創建RC時與一個DC建立聯繫,OpenGL函數就可以通過與RC對應的DC繪製到相應設備上。而實際上,RC只能通過建立了位圖格式的DC來創建
3. 一個DC對應的是一個圖像輸出設備,而一個RC對應的則是一個線程。一個線程只能擁有一個RC,而一個RC也只能屬於一個線程,不能在線程中共有。若一個線程想要在不同的設備上繪圖,只能通過更改與RC對應的DC來實現,而RC在線程總保持不變(當然也可以刪除舊的RC,再利用其它設備的DC創建新的RC)。


下面來講一下RC的創建。正如前面所說的,RC只能通過建立了位圖格式的DC來創建。
1. 首先,我們需要創建一個窗口,然後使用GetDC函數得到這個窗口的DC。我們這裏討論在MFC使用OpenGL,因此窗口類是由工程自動創建的COpenglDemoView類,由於我們做的是繪圖前的窗口初始化工作,因此我們將之後所有的操作都在OnCreate函數中完成。(至於一些必要的成員數據初始化工作,應放在View類的構造函數中
HWND hWnd = this->GetSafeHwnd();
HDC hDC = ::GetDC(hWnd);

2. 然後,我們需要指定DC中的位圖像素格式。這個工作是由GDI庫中名爲PIXELFORMATDESCRIPTOR的類來實現的。
第一步是填充這個數據結構:
PIXELFORMATDESCRIPTORpixelDesc={
sizeof(PIXELFORMATDESCRIPTOR),//nsize:像素格式描述子結構的大小
1, //nVersion:PIXELFORMATDESCRIPTOR結構的版本,一般設爲1
//dwFlags:一組表明像素緩衝特性的標誌位
PFD_DRAW_TO_WINDOW |//使之能在窗口或者其他設備窗口畫圖
PFD_SUPPORT_OPENGL,// |//使之能使用OpenGL函數
//PFD_DOUBLEBUFFER,//指明使用了雙緩衝
PFD_TYPE_RGBA,//PixelType:定義了顯示顏色的方法
24, //cColorBits:指定了一個顏色的位數
0,0,//cRedBits,cRedShift:每個RGBA顏色緩衝區中紅色位平面的數目和偏移數
0,0,//cGreenBits,cGreenShift:每個RGBA顏色緩衝區中綠色位平面的數目和偏移數
0,0,//cBlueBits,cBlutShift:每個RGBA顏色緩衝區中藍色位平面的數目和偏移數
0,0,//cAlphaBits,cAlphaShift:每個RGBA顏色緩衝區中Alpah位平面的數目和偏移數
0, //cAccumBits:累加緩衝區中全部位平面的數目
0,0,0,0,//cAccumRedBits, cAccumGreenBits, cAccumBlueBits,cAccumAlphaBits
32, //cDepthBits:Z(深度)緩衝區的深度
0, //cStencilBits:模板緩衝區的深度
0, //cAuxBuffers:軸向緩衝區的數量(一般1.0版本不支持)
PFD_MAIN_PLANE,//iLayerType:忽略,爲了一致性而包含的
0, //bReserved:表層和底層平面的數量
0,0,0//dwLayerMask, dwVisibleMask, dwDamageMask
};

這個類中的大部分成員變量我們並不關心,最重要的是第三個參數dwFlags,其中指定的PFD_SUPPORT_OPENGL使得我們可以在這個窗口中使用OpenGL函數
而指定的PFD_DOUBLEBUFFER則使得我們可以使用OpenGL中的雙緩存機制(這個機制在製作動畫時非常重要)。但奇怪的是當我選擇了這個標誌位時,生成的窗口沒有圖像(一片空白),而將這個標誌位註釋掉,就能顯示我所繪製的圖了。非常詭異,我也不明白爲什麼。。。

第二步是將這個像素格式選爲當前DC的使用像素格式:
this->m_GLPixelIndex = ChoosePixelFormat(hDC, &pixelDesc);
if(this->m_GLPixelIndex==0){
this->m_GLPixelIndex = 1;
if(DescribePixelFormat(hDC,
       this->m_GLPixelIndex,
       sizeof(PIXELFORMATDESCRIPTOR),
       &pixelDesc) == 0)
{
return false;
}
}

if(SetPixelFormat(hDC, this->m_GLPixelIndex, &pixelDesc) == false){
return false;
}

這段代碼首先調用了ChoosePixelFormat函數來尋找OpenGL所支持的像素格式中,最接近所設置的像素格式,如果沒有找到,就調用DescribePixelFormat函數來選擇索引值爲1的像素格式來填充設置的像素格式。這些操作完成後,保證了所設置的像素格式是OpenGL所支持的。
最後就可以調用SetPixelFormat函數來爲指定當前DC的像素格式

注:代碼中的this->m_GLPixelIndex是我們自己添加的View類的protected型的成員變量,類型爲int,用於保存所設置的像素格式在OpenGL所支持的像素格式列表中的索引值。


3. 設置像素格式完成後,就可以創建RC了。這裏面要用到兩個GDI庫中的函數 wglCreateContext 和 wglMakeCurrent

首先創建RC:
this->m_hGLContext = wglCreateContext(hDC);
然後選擇新創建的RC成爲當前DC對應的RC
wglMakeCurrent(hDC, this->m_hGLContext);

:代碼中的this->m_hGLContext是我們自己添加的View類的protected型的成員變量,類型爲HGLRC,用於保存當前RC的句柄。

這樣,RC就創建成功了。



RC創建成功後,我們就可以在View窗口中執行繪圖操作了。其中,視窗、投影方式等設置放在OnSize函數中,而繪製操作則放在OnPaint函數中。

這裏需要注意的是,MFC中沒有提供類似GLUT中glutIdleFunc()的函數,而OnPaint函數只會在窗口創建或者窗口需要重繪的時候纔會被調用。因此OnPaint函數中的繪圖操作一般都是靜態的,如果想繪製動畫,則需要程序員自己寫定時函數來控制窗口的重繪。



最後列一下參考的資料

如果想快速實現在MFC添加OpenGL窗口的功能,參考:基於MFC的OpenGL繪圖

如果想比較詳細地瞭解其中的原理,參考:OpenGL在MFC下的編程原理 和 OpenGL與MFC編程思想(後一篇還提供了解決重繪時屏幕閃爍問題的方法
如何判斷DOC文件是否打開問題
如何判斷一個WORD文件是否已打開??? 
文件路徑和文件名已知 
例如:c:\a.doc 


------解決方案--------------------
HFILE OpenFile( 
LPCSTR lpFileName, 
LPOFSTRUCT lpReOpenBuff, 
UINT uStyle 
); 
將uStyle設置爲OF_SHARE_EXCLUSIVE來判斷文件是否已經被打開了。 
如果該文件已經被打開了,則該函數調用將失敗。
如何用vb判斷某個excel文件是否打開

因爲excel有一個共同的類名 "xlmain ",用類名查找標題只能找到幾個已打開的excel文件中的一個

------解決方案--------------------
createfile api 來判斷。 



------解決方案--------------------
Dir(App.Path + "\路徑\文件名 ") = " "



Sub 判斷文件是否已經打開()
Dim x As Integer
For x = 1 To Windows.Count
If Windows(x).Caption = "Book2.xls" Then
MsgBox "Book2 文件已經打開了"
Exit Sub
End If
Next

End Sub

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章