[C#]使用Dictionary時的注意事項

1.要先加入


 using System.Collections.Generic;  



2.之後再宣告就可以使用嚕。
使用前記得要把變數new出來就是了。


 //宣告 
 protected Dictionary<int, csBehaviorBase> m_dicObjBehaviors; 
 //建立 
 m_dicObjBehaviors = new Dictionary<int, csBehaviorBase>();  

[Unity]設定遊戲手把與取得輸入

參考資料:Input
                    Input Manager

測試手把 :PS Analog gamepad
Script語言:C#
Unity 版本:3.4

Unity提供了3種輸入裝置可以使用,鍵盤、滑鼠、以及遊戲手把。
此篇紀錄在取得遊戲手把輸入時,所遇到的問題以及解決方案。

首先,要先開啟Unity提供的Input Manager(Edit->Project Setting->Input Manager),就可以看到Unity所提供預設的按鈕事件。這邊也可以自訂自己想要用的按鈕,只需要更改size欄位的數值即可,Unity就會利用最後一組按鈕做為參考值,複製一份新的按鈕設定。官方稱這些設定為虛擬軸(Virtual Axes)或是虛擬按鈕(Virtual Button)。


Unity提供預設兩組水平輸入以及垂直輸入,一組是提供給鍵盤訊息使用,另一組是設定給遊戲手把使用。不過這兩個輸入是使用相同的名稱。這邊推測,是要讓寫script的時候,不需要根據裝置而另外寫輸入判斷或是數值取得。如果是這樣設計的話,那算是還蠻方便的功能。


為了測試Unity是否能正確接收到遊戲手把的訊息,就寫了底下的測試程式來測試結果。
 using UnityEngine;  
 using System.Collections;  
   
 public class InputTest : MonoBehaviour {  
     protected float m_JoyX = 0.0f;  
     protected float m_JoyY = 0.0f;  
       
     // Use this for initialization  
     void Start () {  
       
     }  
       
     // Update is called once per frame  
     void Update ()   
     {          
         m_JoyX = Input.GetAxis("Horizontal");  
         m_JoyY = Input.GetAxis("Vertical");  
                 
         Debug.Log("Get Horizontal input from Joystick = " + m_JoyX.ToString());  
         Debug.Log("GetVertical input from Joystick = " + m_JoyY.ToString());  
     }  
 }  
   

測試結果:

測試結果還不錯,用手把或是鍵盤都可以接收到訊息。


不過這種作法有個缺點,就是Update()是每個frame都會更新一次,所以就會一直收到沒按下按鍵時的資料。
因此需要做一些判斷式來確定按鍵有按下時,才取得所需要的按鍵值。
所以更改後的程式碼如下:
   
 using UnityEngine;  
 using System.Collections;  
   
 public class InputTest : MonoBehaviour {  
     protected float m_JoyX = 0.0f;  
     protected float m_JoyY = 0.0f;  
       
     // Use this for initialization  
     void Start () {  
       
     }  
       
     // Update is called once per frame  
     void Update ()   
     {          
         m_JoyX = Input.GetAxis("Horizontal");  
         m_JoyY = Input.GetAxis("Vertical");  
           
         if( Input.GetButton("Horizontal") )  
         {  
             Debug.Log("Get Horizontal input from Joystick = " + m_JoyX.ToString());  
         }else if(Input.GetButton("Vertical")) {  
             Debug.Log("GetVertical input from Joystick = " + m_JoyY.ToString());  
         }  
           
         if( Input.GetKey(KeyCode.JoystickButton11) ){  
             print("Get Joystick Button 0");  
         }  
           
         //Debug.Log("Get Horizontal input from Joystick = " + m_JoyX.ToString());  
     }  
 }  
   

測試結果如下圖:

結果,手把訊息沒進來?!
之後試過很多方法,例如把按鍵名稱改成不同的名稱,也是一樣沒有接收到手把方向鍵按下的訊息。
這邊的測試就繞了很久,後來索性把手把方向鍵是否有輸入這件事情,改成判定軸向值是否有值(也就是不為0)。結果就可正常的在手把方向鍵按下時,才取得所要的數值。

原本不曉得是不是Unity的bug還是限制,畢竟這一塊的說明,官方文件沒有講得很清楚。而提供的按鍵訊息也只有上、下、左、右四個鍵盤按鍵而已。目前推測是,因為手把輸入的Type為Joystick Axis,然後軸並沒有所謂的按鈕觀念,所以這邊就無法用Input.GetButton()來取得是否有按下這個動作。


改判定軸向是否有值後,的確解決上述的問題,但與鍵盤要同時共用的時候,卻又出了問題
就是因為手把跟鍵盤是使用相同的虛擬按鈕名稱,所以取得的軸向資料有時並不是手把輸入的,反而會是鍵盤輸入的。


因此目前為了解決這問題,就是把手把輸入跟鍵盤輸入的虛擬按鈕名稱改成不一樣,這樣就可以確定輸入的資料是確切的來自於鍵盤還是手把。雖然在實作遊戲的時候會很不方便(因為要另外把手把輸入拆出來判斷),但是如果透過繼承跟多型,是可以讓實作遊戲變方便。至於如何繼承跟多型的部分就之後另外開個主題紀錄了(因為目前還在測試中XD)。

最後可以同時使用鍵盤與遊戲手把輸入,但也不會互相影響的方式如下:
或是在Input Manager內使用相同的熱鍵名稱也行。之前測試不過可能是有設定沒弄好。 2011/08/31
 using UnityEngine;  
 using System.Collections;  
   
 public class InputTest : MonoBehaviour {  
     protected float m_JoyX = 0.0f;  
     protected float m_JoyY = 0.0f;  
       
     // Use this for initialization  
     void Start () {  
       
     }  
       
     // Update is called once per frame  
     void Update ()   
     {  
         m_JoyX = Input.GetAxis("Horizontal_Joy");  
         m_JoyY = Input.GetAxis("Vertical_Joy");  
           
         if( Input.GetButton("Horizontal") )  
         {  
             print("Get Horizontal input from keyboard = " + Input.GetAxis("Horizontal").ToString());  
         }else if( Input.GetButton("Vertical") ){  
             print("Get Vertical input from keyboard = " + Input.GetAxis("Vertical").ToString());  
         }else if( Input.GetButton("Horizontal_Joy") ){//m_JoyX != 0.0f ){  
             Debug.Log("GetHorizontal input from Joystick = " + m_JoyX.ToString());  
         }else if( m_JoyY != 0.0f ){  
             Debug.Log("GetVertical input from Joystick = " + m_JoyY.ToString());  
         }  
           
         if( Input.GetKey(KeyCode.JoystickButton11) ){  
             print("Get Joystick Button 0");  
         }  
     }  
 }  

測試結果如下:

輸出結果也就很符合目前的需求嚕。

不過此時Joystick Y軸上為負值,所以有需要用Y軸上為正值時,需把Inverse選項打勾,就可以了。


PS.1.官方有註明鍵盤跟手把的軸向值介於-1到1。滑鼠部分則不一定在此區間。
PS.2.在測試過程中發現,把Dead設為0.00001以下時,在手把方向鍵未按下時,Input.GetAxis()取得到的座標值其實是不為零的。
         因此如果使用別的手把時,發現官方的預設值無法讓未按下方向鍵時取得的值為零的情況下,只要把Dead參數改大就好。反之如果按下按鍵後,收到的值為零,就把Dead參數改小(大於0,小於1)就可以了。
         官方對於Dead的說明為,當收到的按鍵數值低於Dead的值的時候,就會強制設為0。

[CDialog]顯示不固定字串於Dialog上物件的ToolTip

Reference: Handling TTN_NEEDTEXT Notification for Tool Tips

這邊所要處理的是取得到TTN_NEEDTEXT,之後再根據得到的資料,塞給ToolTip來顯示。
建立步驟如下:

1.先在MessageMap()加入ON_NOTIFY()。這邊ID設為0,則為Dialog會收到訊息。
 BEGIN_MESSAGE_MAP(CTestDlg, CDialog)  
     //Show Tips  
     ON_NOTIFY_EX(TTN_NEEDTEXT, 0, &CTestDlg::OnToolTipText)        // the ID of a tool tip is always 0.  
 END_MESSAGE_MAP()  

2.新增OnToolTipText()。由於TOOLTIPTEXT結構用來顯示Tip的szText長度限制為80,所以這邊就要將需要顯示文字的位置指給lpszText。這樣做就沒有顯示長度的限制了。
之後僅需將需要跳出tip的物件的ID加到此Function處理就好。

header
 ....  
 afx_msg BOOL OnToolTipText(UINT ID, NMHDR *pNMHDR, LRESULT *pResult);  
 ....  

cpp
 BOOL CDSEnvItemEdtDlg::OnToolTipText(UINT ID, NMHDR *pNMHDR, LRESULT *pResult)  
 {  
     BOOL IsOK = FALSE;  
     TOOLTIPTEXT *pToolTipText = (TOOLTIPTEXT*) pNMHDR;  
     UINT nID = (UINT)pNMHDR->idFrom;                    //得到相對應的Dialog的ID,有可能是HWND。  
     if ( !(pToolTipText->uFlags & TTF_IDISHWND) ) {        //確認是否為HWND  
         return FALSE; };  
   
     nID = (UINT)::GetDlgCtrlID((HWND)nID);                //取得目前停留的子空見ID。  
   
     switch (nID)  
     {  
         case IDC_EDT_TEXT:  
             pToolTipText->lpszText = this->m_edtPicFilePath.GetBuffer();  
             this->m_edtPicFilePath.ReleaseBuffer();  
             IsOK = TRUE;  
             break;  
         default:  
             break;  
     }    //End of switch (nID)  
   
     return IsOK;  
 }  

[OpenGL]2D Texture Mapping

Reference:Nehe's OpenGL - Lesson 17

目前工作僅需要將BMP圖片顯示在GL Scene裡面,所以就僅擷取出貼圖片材質的部分。並附上原網頁的註解,已方便對照跟查詢。由於是原文貼上,所以有些用語會有有前後不接的狀況,所以想瞭解原始的意義,請至Hehe的網頁觀看。

0.在開始進行前,先建立存放Texture的全域變數。
 ....  
 GLuint m_glTextureList[DS_GL_MAX_TEXTURE];  
 ....  

1.建立讀取BMP檔的函式。由於此函式有用到auxDIBImageLoad,所以要在*.h引用gl\glaux.h。
CDSFileIO為自訂的類別,目的是要檢查檔案是否存在,不存在的話,讀了也沒有意義。原文有提到圖片大小的建議,目前因為要配合TreeList顯示的問題,所以是讀取16*16的大小的圖片,於Windows XP SP2測試是正常。
 //----------- L.6 ----------------  
 //Now immediately after the above code, and before ReSizeGLScene(), we want to add the following section of code.   
 //    The job of this code is to load in a bitmap file. If the file doesn't exist NULL is sent back meaning the texture couldn't be loaded.   
 //    Before I start explaining the code there are a few VERY important things you need to know about the images you plan to use as textures.   
 //    The image height and width MUST be a power of 2.   
 //    The width and height must be at least 64 pixels, and for compatability reasons, shouldn't be more than 256 pixels.   
 //    If the image you want to use is not 64, 128 or 256 pixels on the width or height, resize it in an art program.   
 //    There are ways around this limitation, but for now we'll just stick to standard texture sizes.   
 //First thing we do is create a file handle. A handle is a value used to identify a resource so that our program can access it.   
 //    We set the handle to NULL to start off.  
 //----------- L.6 ----------------  
 AUX_RGBImageRec *COpenGLDrawer::LoadBMP(const char *szFilePath)  
 {  
     if ( CDSFileIO::IsFileExisted(szFilePath) ) {  
         return auxDIBImageLoad(szFilePath);                // Load The Bitmap And Return A Pointer  
     }    //End of if ( CDSFileIO::IsFileExisted(szFilePath) )  
     return NULL;                                        // If Load Failed Return NULL  
 }  

2.建立設定Texture的函式。DS_GL_MAX_TEXTURE為自訂常數,用來定義讀取BMP的Texture buffer大小。所以就看需求定義即可。由於是非動態宣告buffer大小,所以要小心邊界的問題。
 //The follwing code has also changed very little from the code used in previous tutorials.   
 //    If you're not sure what each of the following lines do, go back and review.   
 //Note that TextureImage[ ] is going to hold 2 rgb image records.   
 //    It's very important to double check code that deals with loading or storing our textures.   
 //    One wrong number could result in a memory leak or crash!  
 // Load Bitmaps And Convert To Textures  
 int COpenGLDrawer::LoadGLTextures(std::vector<std::string> vstrFilePaths)                                  
 {  
     int Status = TRUE;  
     AUX_RGBImageRec *TextureImage[DS_GL_MAX_TEXTURE];        // Create Storage Space For The Textures  
           
 //The next line is the most important line to watch. If you were to replace the 2 with any other number, major problems will happen.   
 //    Double check! This number should match the number you used when you set up TextureImages[ ].   
 //The two textures we're going to load are font.bmp (our font), and bumps.bmp.   
 //    The second texture can be replaced with any texture you want.   
 //    I wasn't feeling very creative, so the texture I decided to use may be a little drab.              
 /*    memset(TextureImage,0,sizeof(void *)*2);                // Set The Pointer To NULL    */  
     memset(TextureImage,0,sizeof(void *)*DS_GL_MAX_TEXTURE);    // Set The Pointer To NULL  
   
     for ( unsigned int i = 0 ; i < vstrFilePaths.size() ; i++ )  
     {  
         if ( !(TextureImage[i] = this->LoadBMP(vstrFilePaths[i].c_str())) )        //有一個讀不到就不繼續執行,不然就是要改塞白色。  
         {  
             Status = FALSE;  
         }  
     }    //End of for ( unsigned int i = 0 ; i < vstrFilePaths.size() ; i++ )          
   
     if ( !Status ) {  
         return Status; };  
   
 //Another important line to double check.   
 //    I can't begin to tell you how many emails I've received from people asking "why am I only seeing one texture,   
 //        or why are my textures all white!?!".   
 //    Usually this line is the problem.   
 //    If you were to replace the 2 with a 1, only one texture would be created and the second texture would appear all white.   
 //    If you replaced the 2 with a 3 you're program may crash!   
 //You should only have to call glGenTextures() once. After glGenTextures() you should generate all your textures.   
 //    I've seen people put a glGenTextures() line before each texture they create.   
 //    Usually they causes the new texture to overwrite any textures you've already created.   
 //    It's a good idea to decide how many textures you need to build, call glGenTextures() once, and then build all the textures.   
 //    It's not wise to put glGenTextures() inside a loop unless you have a reason to.          
 //--------- L.6 ------------  
 //Now that we've loaded the image data into TextureImage[0], we will build a texture using this data.   
 //    The first line glGenTextures(1, &texture[0]) tells OpenGL we want to generate one texture name   
 //        (increase the number if you load more than one texture).   
 //    Remember at the very beginning of this tutorial we created room for one texture with the line GLuint texture[1].   
 //    Although you'd think the first texture would be stored at &texture[1] instead of &texture[0], it's not.   
 //    The first actual storage area is 0.   
 //    If we wanted two textures we would use GLuint texture[2] and the second texture would be stored at texture[1].   
 //The second line glBindTexture(GL_TEXTURE_2D, texture[0]) tells OpenGL to bind the named texture texture[0] to a texture target.   
 //    2D textures have both height (on the Y axes) and width (on the X axes).   
 //    The main function of glBindTexture is to assign a texture name to texture data.  
 //    In this case we're telling OpenGL there is memory available at &texture[0].   
 //    When we create the texture, it will be stored in the memory that &texture[0] references.  
 //--------- L.6 ------------  
     glGenTextures((GLsizei)DS_GL_MAX_TEXTURE, &this->m_glTextureList[0]);        //Create n Texture  
   
     for ( unsigned int i = 0 ; i < this->m_vstrTextureFilePath.size() ; i++ )    // Loop Through All The Textures  
     {  
         //Build All The Textures  
         glBindTexture(GL_TEXTURE_2D, this->m_glTextureList[i]);  
   
 //----------- L.6 ---------------  
 //The next two lines tell OpenGL what type of filtering to use when the image is larger (GL_TEXTURE_MAG_FILTER) or   
 //        stretched on the screen than the original texture, or when it's smaller (GL_TEXTURE_MIN_FILTER) on the screen than the actual texture.   
 //    I usually use GL_LINEAR for both. This makes the texture look smooth way in the distance, and when it's up close to the screen.   
 //    Using GL_LINEAR requires alot of work from the processor/video card, so if your system is slow, you might want to use GL_NEAREST.   
 //    A texture that's filtered with GL_NEAREST will appear blocky when it's stretched. You can also try a combination of both.   
 //    Make it filter things up close, but not things in the distance.  
 //----------- L.6 ---------------  
         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);  
         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);  
   
 //----------- L.6 ---------------  
 //Next we create the actual texture. The following line tells OpenGL the texture will be a 2D texture (GL_TEXTURE_2D).   
 //    Zero represents the images level of detail, this is usually left at zero. Three is the number of data components.   
 //    Because the image is made up of red data, green data and blue data, there are three components.   
 //    TextureImage[0]->sizeX is the width of the texture. If you know the width, you can put it here,   
 //        but it's easier to let the computer figure it out for you.   
 //    TextureImage[0]->sizey is the height of the texture. zero is the border.   
 //    It's usually left at zero. GL_RGB tells OpenGL the image data we are using is made up of red, green and blue data in that order.   
 //    GL_UNSIGNED_BYTE means the data that makes up the image is made up of unsigned bytes, and finally...   
 //        TextureImage[0]->data tells OpenGL where to get the texture data from.   
 //    In this case it points to the data stored in the TextureImage[0] record.  
 //----------- L.6 ---------------  
         glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[i]->sizeX, TextureImage[i]->sizeY, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[i]->data);  
     }    //End of for ( int i = 0 ; i < DS_GL_MAX_TEXTURE ; i++ )  
   
 /*        }    */  
   
 //The following lines of code check to see if the bitmap data we loaded to build our textures is using up ram.   
 //    If it is, the ram is freed. Notice we check and free both rgb image records.   
 //    If we used 3 different images to build our textures, we'd check and free 3 rgb image records.  
 //---------- L.6 ---------------  
 //Now we free up any ram that we may have used to store the bitmap data. We check to see if the bitmap data was stored in TextureImage[0].   
 //    If it was we check to see if the data has been stored. If data was stored, we erase it.   
 //    Then we free the image structure making sure any used memory is freed up.  
 //---------- L.6 ---------------  
     for ( int i = 0 ; i < this->m_vstrTextureFilePath.size() ; i++ )  
     {  
         if ( TextureImage[i] )                            // If Texture Exists  
         {  
             if ( TextureImage[i]->data ) {                //If Texture Image Exists  
                 free(TextureImage[i]->data); };            //Free the Texture Image Memory  
   
             free(TextureImage[i]);                        //Free The Image Structure  
         }    //End of if ( TextureImage[i] )  
     }    //End of for ( int i = 0 ; i < DS_GL_MAX_TEXTURE ; i++ )  
   
     return Status;                                // Return The Status  
 }  

3.上述兩個步驟完成後,則在GL做初始化的時候,載入並設定Texture。這邊所需的圖檔路徑皆在物件產生時就先指定好。
 void COpenGLDrawer::glInitialize(void)  
 {  
 ....  
     if ( !this->LoadGLTextures(this->m_vstrTextureFilePath) ) {  
         return;    };  
 ....  
 }  

4.於GL Scene繪製方式如下,關鍵的部分為,要進行Texture Mapping時,要先開啟GL_TEXTURE。繪製完成後,可設定關閉,以釋放記憶體。
 void CDSGLDrawer::glDrawScene(void)  
 {  
 //Show Texture  
 //The next line of code selects which texture we want to use.   
 //    If there was more than one texture you wanted to use in your scene,   
 //        you would select the texture using glBindTexture(GL_TEXTURE_2D, texture[number of texture to use]).   
 //    If you wanted to change textures, you would bind to the new texture.   
 //    One thing to note is that you can NOT bind a texture inside glBegin() and glEnd(), you have to do it before or after glBegin().   
 //    Notice how we use glBindTextures to specify which texture to create and to select a specific texture.      
     glBindTexture(GL_TEXTURE_2D, this->m_glTextureList[0]);                // Select Our Texture  
           
 //To properly map a texture onto a quad, you have to make sure the top right of the texture is mapped to the top right of the quad.   
 //    The top left of the texture is mapped to the top left of the quad,   
 //        the bottom right of the texture is mapped to the bottom right of the quad, and finally,   
 //        the bottom left of the texture is mapped to the bottom left of the quad.   
 //    If the corners of the texture do not match the same corners of the quad, the image may appear upside down, sideways, or not at all.   
 //The first value of glTexCoord2f is the X coordinate.   
 //    0.0f is the left side of the texture. 0.5f is the middle of the texture, and 1.0f is the right side of the texture.   
 //    The second value of glTexCoord2f is the Y coordinate. 0.0f is the bottom of the texture.   
 //    0.5f is the middle of the texture, and 1.0f is the top of the texture.   
 //So now we know the top left coordinate of a texture is 0.0f on X and 1.0f on Y, and the top left vertex of a quad is -1.0f on X,   
 //        and 1.0f on Y.   
 //    Now all you have to do is match the other three texture coordinates up with the remaining three corners of the quad.  
 //Try playing around with the x and y values of glTexCoord2f.   
 //    Changing 1.0f to 0.5f will only draw the left half of a texture from 0.0f (left) to 0.5f (middle of the texture).   
 //    Changing 0.0f to 0.5f will only draw the right half of a texture from 0.5f (middle) to 1.0f (right).          
 //--------------- L.6 -------------------  
 //If everything went OK, and the texture was created, we enable 2D texture mapping.   
 //    If you forget to enable texture mapping your object will usually appear solid white, which is definitely not good.  
 //--------------- L.6 -------------------  
     glEnable(GL_TEXTURE_2D);                            // Enable Texture Mapping  
     glPolygonMode(GL_FRONT, GL_FILL);                    //Set polygon rasterization mode  
     glColor3f(1.0f, 1.0f, 1.0f);                        //用來重設顏色,如果前面這個位置有被繪製圖形,就必須做此動作。反之則無所謂。  
     glBegin(GL_QUADS);  
         glTexCoord2f(0.0f, 0.0f);  
         glVertex2f(this->m_vcdsGrid[0].LB.x, this->m_vcdsGrid[0].LB.y);    // Bottom Left Of The Texture and Quad  
         glTexCoord2f(1.0f, 0.0f);  
         glVertex2f(this->m_vcdsGrid[0].RB.x, this->m_vcdsGrid[0].RB.y);    // Bottom Right Of The Texture and Quad  
         glTexCoord2f(1.0f, 1.0f);  
         glVertex2f(this->m_vcdsGrid[0].RT.x, this->m_vcdsGrid[0].RT.y);    // Top Right Of The Texture and Quad  
         glTexCoord2f(0.0f, 1.0f);  
         glVertex2f(this->m_vcdsGrid[0].LT.x, this->m_vcdsGrid[0].LT.y);    // Top Left Of The Texture and Quad  
     glEnd();  
     glDisable(GL_TEXTURE_2D);                            //Disable texture mapping  
 }  

這樣就可以把讀進來的圖,貼到GL Scene裡面了。

[CTreeCtrl]在Tree Item前顯示載入的BMP圖片

1.使用LoadImage()載入BMP檔。
2.使用CBitMap::Attach()載入HBITMAP。
3.將產生的CBitMap物件透過CImageList::Add()設定給CImageList。
4.使用CTreeCtrl::SetImageList()將CImageList設定給CTreeCtrl。
5.在需要塞入圖片的Item使用SetItemImage()指定圖片即可。
6.由於這邊使用LoadImage()時,是讀新的圖片進來,所以不用到圖片時,則需在使用DeleteObject()來釋放記憶體。

 HBITMAP hBMP= (HBITMAP)LoadImage(NULL,"c:\\temp\\noname.bmp",IMAGE_BITMAP,0,0,LR_LOADFROMFILE);  
 if ( !hBMP ) {  
     CDSMsgShower::ShowMsgByErrorBox(this->m_hWnd, "ERROR", MB_OK, "Fail to load bmp file. Error =%d", ::GetLastError());  
 }  

 CBitmap *pBMP = new CBitmap;  
 pBMP->Attach(hBMP);  
   
 if (!m_cImageList.Create(16,16, ILC_COLOR16|ILC_MASK, 6, 6))  
     AfxMessageBox("Problem in creating ImageList");  
 this->m_cImageList.Add(pBMP, RGB(255, 255, 255));  //Mask部分如沒有其他顏色要過濾掉的話,就設成白色

[Menu]Disable item of title menu

當有將menu設為Dialog的Menu時,僅需用下列步驟就可以操作sub menu的Item的停用或啟用。

 this->GetMenu()->GetSubMenu(nSubMenuIndex)->EnableMenuItem(ID_SUB_MENU_ITEM, MF_GRAYED);

如果是要針對menu本身的Item,僅需用下列的作法:
 this->GetMenu()->EnableMenuItem(1, MF_GRAYED|MF_BYPOSITION);  

因為menu的item並沒有ID,所以在設定的時候要多給MF_BYPOSITION,這樣就可以用Index來指定Item了。

[CTreeCtrl]取得Item的Handle

1.GetRootItem():取得目前TreeCtrl上的根項目。

使用此函式,第一個會取得的為Mountain。












2.GetNextSiblingItem():取得下一個同階層的項目。
  在此函式前呼叫GetRootItem()的話,則在呼叫此函式時會得到River、Houses、Road、Plant。

3.把GetRootItem()取得到的HTREEITEM丟給GetChildItem(),則會得到Fold。之後再使用GetNextSiblingItem(),則會得到Fault-Block。當GetNextSiblingItem()回傳為NULL時,則代表已無可用的child item存在。

4.GetSelectedItem():取得目前左鍵點選之項目。(回應之事件為OnClick時,才算有選中)

5.GetFirstVisibleItem():取得目前CTreeCtrl第一個看的到的項目。

使用此函式第一個會得到Mountain。
(目前測試,在進行item新增後,再使用此函式,有時候會得到Fault-Block。目前原因不明,需再多進行測試。)









6.GetNextVisibleItem():取得下一個看的到的項目。
即Mountain、River、Houses、Plant及其底下項目。唯獨Road只會得到根項目而已。

[OpenGL]選取顯示的物件-2D, Pick

Ref:Nehe's Openg - Lesson 32

目前因為需要把建立在TreeICtrl的物件,丟到OpenGL上做顯示,因此需要可以抓到目前滑鼠指標放置TreeCtrl物件的位置跟對象。
目前已知的作法是透過Frustum Culling可以得知目前滑鼠所選擇的區域。但以目前的能力來講,這難度比較高。
因此這邊選用OpenGL提供的Selection Mode來達到需求。

目前要放置的對象為2D Grid,所以目前要抓的是,滑鼠指標是在那個Cell上。
程式參考Nehe的範例,但由於還有些項目跟數值不清楚,所以還是以Nehe提供的數值跟說明為主。
目前這邊是當作筆記保留,所以不加其他說明,避免誤導社會大眾。

底下為實際運作流程:
1.建立滑鼠事件觸發後的函式:
 void COpenGLDrawer::OnObjectSelection(CPoint point, COLORREF clrPickColor)  
 {  
     GLuint    buffer[512];            // Set Up A Selection Buffer  
     GLint    hits = 0;                // The Number Of Objects That We Selected  
   
 //Now we set up a viewport. viewport[] will hold the current x, y, length and width of the current viewport (OpenGL Window).   
 //glGetIntegerv(GL_VIEWPORT, viewport) gets the current viewport boundries and stores them in viewport[].   
 //    Initially, the boundries are equal the the OpenGL window dimensions.   
 //    glSelectBuffer(512, buffer) tells OpenGL to use buffer for it's selection buffer.  
     // The Size Of The Viewport. [0] Is <x>, [1] Is <y>, [2] Is <length>, [3] Is <width>  
     GLint    viewport[4];  
   
     // This Sets The Array <viewport> To The Size And Location Of The Screen Relative To The Window  
     glGetIntegerv(GL_VIEWPORT, viewport);  
     glSelectBuffer(512, buffer);                        // Tell OpenGL To Use Our Array For Selection      
   
 //All of the code below is very important. The first line puts OpenGL in selection mode.   
 //    In selection mode, nothing is drawn to the screen.   
 //    Instead, information about objects rendered while in selection mode will be stored in the selection buffer.   
 //Next we initialize the name stack by calling glInitNames() and glPushName(0).   
 //    It's important to note that if the program is not in selection mode, a call to glPushName() will be ignored.   
 //    Of course we are in selection mode, but it's something to keep in mind.  
     // Puts OpenGL In Selection Mode. Nothing Will Be Drawn. Object ID's and Extents Are Stored In The Buffer.  
     (void) glRenderMode(GL_SELECT);  
   
     glInitNames();                                // Initializes The Name Stack  
     glPushName(0);                                // Push 0 (At Least One Entry) Onto The Stack  
   
 //After preparing the name stack, we have to to restrict drawing to the area just under our crosshair.   
 //    In order to do this we have to select the projection matrix. After selecting the projection matrix we push it onto the stack.   
 //    We then reset the projection matrix using glLoadIdentity().   
 //We restrict drawing using gluPickMatrix().   
 //    The first parameter is our current mouse position on the x-axis, the second parameter is the current mouse position on the y-axis,   
 //        then the width and height of the picking region. Finally the current viewport[].   
 //    The viewport[] indicates the current viewport boundaries. mouse_x and mouse_y will be the center of the picking region.          
     glMatrixMode(GL_PROJECTION);                        // Selects The Projection Matrix  
     glPushMatrix();                                // Push The Projection Matrix  
     glLoadIdentity();                            // Resets The Matrix  
   
     // This Creates A Matrix That Will Zoom Up To A Small Portion Of The Screen, Where The Mouse Is.  
     /*gluPickMatrix((GLdouble) mouse_x, (GLdouble) (viewport[3]-mouse_y), 1.0f, 1.0f, viewport);*/  
     gluPickMatrix((GLdouble) point.x, (GLdouble) (viewport[3]-point.y), 1.0f, 1.0f, viewport);        //輸入為Windows Coordinate of DrawScene  
   
 //Calling gluPerspective() multiplies the perspective matrix by the pick matrix which restricts the drawing to the area requested by   
 //    gluPickMatrix().   
 //We then switch to the modelview matrix and draw our targets by calling DrawTargets().   
 //    We draw the targets in DrawTargets() and not in Draw() because we only want selection to check for hits with objects (targets) and   
 //        not the sky, ground or crosshair.   
 //After drawing our targets, we switch back to the projection matrix and pop the stored matrix off the stack.   
 //    We then switch back to the modelview matrix.   
 //The last line of code below switches back to render mode so that objects we draw actually appear on the screen.   
 //    hits will hold the number of objects that were rendered in the viewing area requested by gluPickMatrix().  
     // Apply The Perspective Matrix  
 /*    gluPerspective(45.0f, (GLfloat) (viewport[2]-viewport[0])/(GLfloat) (viewport[3]-viewport[1]), 0.1f, 100.0f);    */  
     gluPerspective(45.0f, (GLfloat) (viewport[2]-viewport[0])/(GLfloat) (viewport[3]-viewport[1]), 0.1f, 100.0f);  
     glMatrixMode(GL_MODELVIEW);                        // Select The Modelview Matrix  
     DrawTargets();                                    // Render The Targets To The Selection Buffer  
     glMatrixMode(GL_PROJECTION);                    // Select The Projection Matrix  
     glPopMatrix();                                    // Pop The Projection Matrix  
     glMatrixMode(GL_MODELVIEW);                        // Select The Modelview Matrix  
     hits=glRenderMode(GL_RENDER);                    // Switch To Render Mode, Find Out How Many  
   
 //Now we check to see if there were more than 0 hits recorded.   
 //    If so, we set choose to equal the name of the first object drawn into the picking area.   
 //    depth holds how deep into the screen, the object is.   
 //Each hit takes 4 items in the buffer. The first item is the number of names on the name stack when the hit occured.   
 //    The second item is the minimum z value of all the verticies that intersected the viewing area at the time of the hit.   
 //    The third item is the maximum z value of all the vertices that intersected the viewing area at the time of the hit and the last item   
 //        is the content of the name stack at the time of the hit (name of the object).   
 //    We are only interested in the minimum z value and the object name in this tutorial.  
     if (hits > 0)                                // If There Were More Than 0 Hits  
     {  
         int    choose = buffer[3];                                    // Make Our Selection The First Object  
         int depth = buffer[1];                                    // Store How Far Away It Is   
 //We then loop through all of the hits to make sure none of the objects are closer than the first object hit.   
 //    If we didn't do this, and two objects were overlapping, the first object hit might behind another object,   
 //        and clicking the mouse would take away the first object, even though it was behind another object. When you shoot at something,   
 //        the closest object should be the object that gets hit.   
 //So, we check through all of the hits.   
 //    Remember that each object takes 4 items in the buffer, so to search through each hit we have to multiply the current loop value by 4.   
 //    We add 1 to get the depth of each object hit.   
 //    If the depth is less than the the current selected objects depth,   
 //        we store the name of the closer object in choose and we store the depth of the closer object in depth.   
 //    After we have looped through all of our hits, choose will hold the name of the closest object hit,   
 //        and depth will hold the depth of the closest object hit.  
 /*  
         for (int loop = 1; loop < hits; loop++)                    // Loop Through All The Detected Hits  
         {  
             // If This Object Is Closer To Us Than The One We Have Selected  
             if (buffer[loop*4+1] < GLuint(depth))  
             {  
                 choose = buffer[loop*4+3];                        // Select The Closer Object, sample use the offset 3.  
                 depth = buffer[loop*4+1];                        // Store How Far Away It Is  
             }      
         }    //End of for (int loop = 1; loop < hits; loop++)  
 */       
 //All we have to do is mark the object as being hit. We check to make sure the object has not already been hit.   
 //    If it has not been hit, we mark it as being hit by setting hit to TRUE.   
 //    We increase the players score by 1 point, and we increase the kills counter by 1.  
 /*  
         if (!object[choose].hit)                                // If The Object Hasn't Already Been Hit  
         {  
             object[choose].hit=TRUE;                            // Mark The Object As Being Hit  
             score+=1;                                            // Increase Score  
             kills+=1;                                            // Increase Level Kills  
 */  
         this->m_vcdsGrid4AStar[choose].IsWarkable = !this->m_vcdsGrid4AStar[choose].IsWarkable;  
         this->m_vcdsGrid4AStar[choose].clrSelectedColor = clrPickColor;  
   
 //I use kills to keep track of how many objects have been destroyed on each level.   
 //    I wanted each level to have more objects (making it harder to get through the level).   
 //    So I check to see if the players kills is greater than the current level multiplied by 5.   
 //    On level 1, the player only has to kill 5 objects (1*5).   
 //    On level 2 the player has to kill 10 objects (2*5), progressively getting harder each level.   
 //So, the first line of code checks to see if kills is higher than the level multiplied by 5. If so, we set miss to 0.   
 //    This sets the player morale back to 10 out of 10 (the morale is 10-miss).   
 //    We then set kills to 0 (which starts the counting process over again).   
 //Finally, we increase the value of level by 1 and check to see if we've hit the last level.   
 //    I have set the maximum level to 30 for the following two reasons... Level 30 is insanely difficult.   
 //    I am pretty sure no one will ever have that good of a game. The second reason... At the top of the code, we only set up 30 objects.   
 //    If you want more objects, you have to increase the value accordingly.   
 //It is VERY important to note that you can have a maximum of 64 objects on the screen (0-63).   
 //    If you try to render 65 or more objects, picking becomes confused, and odd things start to happen.   
 //    Everything from objects randomly exploding to your computer crashing. It's a physical limit in OpenGL (just like the 8 lights limit).   
 //If by some chance you are a god, and you finish level 30, the level will no longer increase, but your score will.   
 //    Your morale will also reset to 10 every time you finish the 30th level.  
 /*  
             if (kills>level*5)                                    // New Level Yet?  
             {  
                 miss=0;                                            // Misses Reset Back To Zero  
                 kills=0;                                        // Reset Level Kills  
                 level+=1;                                        // Increase Level  
                 if (level>30)                                    // Higher Than 30?  
                     level=30;                                    // Set Level To 30 (Are You A God?)  
             }  
         }    //End of if (!object[choose].hit)  
 */  
     }    //End of if (hits > 0)  
 }  

2.建立當select mode選用後,用來搜尋滑鼠指標點選的圖形。由於這邊是用線條構成的grid,所以點選時,其實是點不到繪製的物件。因此在這邊搜尋用的圖形,全部改用填滿的grid來作為搜尋的依據。
裡面有個很關鍵的一行,就是在繪製搜尋用的圖像前,要先把要繪製的view port座標移動到目前看到已經畫出來的model相同對應的位置。否則可能會造成選擇的區域不正確
 void COpenGLDrawer::DrawTargets(void)  
 {  
     glLoadIdentity();                                    // Reset The Modelview Matrix  
 /*    glTranslatef(0.0f,0.0f, -10.0f);                        // Move Into The Screen 20 Units        //NOTE:This is the KEY for Picking!!!    */  
     glTranslatef( this->m_fPosX, this->m_fPosY, -20);  
     /*for (int loop=0; loop<level; loop++)                // Loop Through 9 Objects*/  
     for ( unsigned int loop = 0 ; loop < this->m_vcdsGrid.size() ; loop++ )  
     {  
 //The first line of code is the secret to picking individual objects.   
 //    What it does is assigns a name (number) to each object. The first object drawn will be 0.   
 //    The second object will be 1, etc... If the loop was to hit 29, the last object drawn would be given the name 29.   
 //    After assigning a name to the object, we push the modelview matrix onto the stack.   
 //    It's important to note the calls to glLoadName() are ignored if the program is not in selection mode.   
 //We then move to the location on the screen where we want our object to be drawn.   
 //    We use object[loop].x to position on the x-axis, object[loop].y to position on the y-axis and object[loop].distance to position   
 //        the object on the z-axis (depth into the screen).   
 //    We have already translated 10 units into the screen, so the actual distance at which the object will be drawn is going to   
 //        be object[loop].distance-10.0f.      
         glLoadName(loop);                        // Assign Object A Name (ID)  
         glPushMatrix();                            // Push The Modelview Matrix  
         /*glTranslatef(object[loop].x,object[loop].y,object[loop].distance);    // Position The Object (x,y)*/  
           
 //Before we draw the object, we have to check if it's been hit or not.   
 //    We do this by checking to see if object[loop].hit is TRUE.   
 //    If it is, we jump to Explosion(loop) which will draw the explosion animation instead of the actual object.   
 //    If the object was not hit, we spin the object on it's z-axis by object[loop].spin degrees before we call Object().   
 //Object takes 3 parameters. The first one is the width, the second one is the height and the third one is the number of the texture to use.   
 //    To get the width and height, we use the array size[object[loop].texid].w and size[object[loop].texid].h.   
 //    This looks up the width and height from our predefined object size array at the beginning of this program.   
 //    The reason we use object[loop].texid is because it represents the type of object we are drawing.   
 //    A texid of 0 is always the blueface... a texid of 3 is always the coke can, etc.   
 //After drawing an object, we pop the matrix resetting the view, so our next object is drawn at the proper location on the screen.      
 /*        if (object[loop].hit)                        // If Object Has Been Hit  
         {  
             Explosion(loop);                        // Draw An Explosion  
         }  
         else                                // Otherwise  
         {  
             glRotatef(object[loop].spin,0.0f,0.0f,1.0f);        // Rotate The Object  
             Object(size[object[loop].texid].w,size[object[loop].texid].h,object[loop].texid);    // Draw The Object  
         }  
 */  
         //if ( !this->m_vcdsGrid4AStar[loop].IsWarkable )   
         //{  
         //    glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);  
         //    glBegin(GL_QUADS);  
         //    glColor3f(0.48f, 0.44f, 1.0f);            //Light Slate Blue  
         //    glVertex2f(this->m_vcdsGrid4AStar[loop].cdsGridInfo.LB.x, this->m_vcdsGrid4AStar[loop].cdsGridInfo.LB.y);  
         //    glVertex2f(this->m_vcdsGrid4AStar[loop].cdsGridInfo.RB.x, this->m_vcdsGrid4AStar[loop].cdsGridInfo.RB.y);  
         //    glVertex2f(this->m_vcdsGrid4AStar[loop].cdsGridInfo.RT.x, this->m_vcdsGrid4AStar[loop].cdsGridInfo.RT.y);  
         //    glVertex2f(this->m_vcdsGrid4AStar[loop].cdsGridInfo.LT.x, this->m_vcdsGrid4AStar[loop].cdsGridInfo.LT.y);  
   
         //    glEnd();  
         //}    //End of if ( !this->m_vcdsGrid4AStar[loop].IsWarkable )   
   
         glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);                            //Set polygon rasterization mode  
                                                                             //Must fill it and the   
         glBegin(GL_QUADS);                                                    //Start to draw squares  
         glColor3f(0.0f, 0.0f, 0.0f);        //Black  
         for ( unsigned int i = 0 ; i < this->m_vcdsGrid.size() ; i++ )  
         {  
         //3D  
             //glVertex3f(this->m_vcdsGrid[i].LB.x, this->m_vcdsGrid[i].LB.y, this->m_vcdsGrid[i].LB.z);  
             //glVertex3f(this->m_vcdsGrid[i].RB.x, this->m_vcdsGrid[i].RB.y, this->m_vcdsGrid[i].RB.z);  
             //glVertex3f(this->m_vcdsGrid[i].RT.x, this->m_vcdsGrid[i].RT.y, this->m_vcdsGrid[i].RT.z);  
             //glVertex3f(this->m_vcdsGrid[i].LT.x, this->m_vcdsGrid[i].LT.y, this->m_vcdsGrid[i].LT.z);  
   
         //2D          
             //glVertex2f(this->m_vcdsGrid[i].LB.x, this->m_vcdsGrid[i].LB.y);  
             //glVertex2f(this->m_vcdsGrid[i].RB.x, this->m_vcdsGrid[i].RB.y);  
             //glVertex2f(this->m_vcdsGrid[i].RT.x, this->m_vcdsGrid[i].RT.y);  
             //glVertex2f(this->m_vcdsGrid[i].LT.x, this->m_vcdsGrid[i].LT.y);          
             glVertex2f(this->m_vcdsGrid4AStar[loop].cdsGridInfo.LB.x, this->m_vcdsGrid4AStar[loop].cdsGridInfo.LB.y);  
             glVertex2f(this->m_vcdsGrid4AStar[loop].cdsGridInfo.RB.x, this->m_vcdsGrid4AStar[loop].cdsGridInfo.RB.y);  
             glVertex2f(this->m_vcdsGrid4AStar[loop].cdsGridInfo.RT.x, this->m_vcdsGrid4AStar[loop].cdsGridInfo.RT.y);  
             glVertex2f(this->m_vcdsGrid4AStar[loop].cdsGridInfo.LT.x, this->m_vcdsGrid4AStar[loop].cdsGridInfo.LT.y);  
   
         }    //End of for ( unsigned int i = 0 ; i < this->m_vcpGrid.size() ; i++ )  
   
         glEnd();  
   
         glPopMatrix();                            // Pop The Modelview Matrix  
     }    //End of for (int loop=0; loop<level; loop++)  
   
 }  

主要在做select動作,是透過這兩個函式,使用到的關鍵function為glInitNames()glLoadName()glPushName()

至於其他網路上的範例會在用來更新畫面的Draw()裡面放glLoadName(),目前的測試是,沒放也沒關係。所以看起來他們只是要整並更新方式而已。

[MFC]COLORREF給glColor_xx()使用

目前在網路上找到得資料,都沒有可以直接轉換的函式。
目前有找到個算是偷吃步的作法,就是重新把COLORREF內部的R、G、B重新取出。
使用的介面名稱為GetRValue()GetGValue()GetBValue()

不過取出的R、G、B值皆為BYTE型態,因此直接塞給GL是不能使用的。
這邊有測試過glColor3b()glColor3i(),結果都是不可行的。

所以變成只能使用glColor3f()來設定顏色。

但由於glColor3f()只能輸入float,所以要再把取得到的R、G、B再除於255轉換成float。
不過一次要做三個除法,所以當繪製的圖形一多,或許會有效能的問題,這邊還需在想要如何處理會比較適合。

範例如下:
 ....  
     glColor3f(GetRValue(clrSelectedColor*1.0f/255,   
              GetGValue(clrSelectedColor)*1.0f/255,  
              GetBValue(clrSelectedColor)*1.0f/255);  
 ....  

[OpenGL]轉換Windows座標成Viewport座標

Ref:http://nehe.gamedev.net/data/articles/article.asp?article=13

Code部分還蠻簡單的,就直接貼上去做memo。
 CDSVector3d COpenGLDrawer::ConvertWinPos2OGLPos(int x, int y)  
 {  
 //Reference:http://nehe.gamedev.net/data/articles/article.asp?article=13  
     //1. Viewport Origin And Extent   
     //    We need to grab the current viewport.   
     //    The information we need is the starting X and Y position of our GL viewport along with the viewport width and height.   
     //    Once we get this information using glGetIntegerv(GL_VIEWPORT, viewport), viewport will hold the following information:   
     //    viewport[0]=x  
     //    viewport[1]=y  
     //    viewport[2]=width  
     //    viewport[3]=height  
     GLint glnViewPort[4];                                    //Where the Viewport will be stored  
     ::glGetIntegerv(GL_VIEWPORT, glnViewPort);                //Retrieves the Viewport values (X, Y, Width, Height)  
   
   
     //2. The Modelview Matrix   
     //    Once we have the viewport information, we can grab the Modelview information.   
     //    The Modelview Matrix determines how the vertices of OpenGL primitives are transformed to eye coordinates.      
     GLdouble gldModelView[16];                                //Where the 16 doubles of the Modelview matrix are to be stored  
     ::glGetDoublev(GL_MODELVIEW_MATRIX, gldModelView);        //Retrieve the Modelview Matrix  
   
   
     //3. The Projection Matrix   
     //    After that, we need to get the Projection Matrix. The Projection Matrix transforms vertices in eye coordinates to clip coordinates.  
     GLdouble gldProjection[16];                                //Where the 16 doubles of the Projection Matrix are to be strored  
     ::glGetDoublev(GL_PROJECTION_MATRIX, gldProjection);    //Retrieve the Projetion Matrix  
   
   
     //4. The Windows Screen Coordinates   
     //    After we have done all of that, we can grab the Windows screen coordinates. We are interested in the current mouse position.  
     GLfloat glfWinX = 0.0f, glfWinY = 0.0f, glfWinZ = 0.0f;        //Holds Our X, y and Z coordinates  
   
     glfWinX = (float)x;        //Holds the mouse X Coordinate  
     glfWinY = (float)y;        //Holds the mouse Y Corrdinate  
   
     //Now Windows coordinates start with (0, 0) being at the top left whereas OpenGL coords start at the lower left. To convert to OpenGL coordinates we do the following:  
     glfWinY = (float)(glnViewPort[3] - glfWinY);        //Subtract the current Mouse Y Corrdinate from the screen height.  
   
     //Get z corrdinate  
     ::glReadPixels(glfWinX, glfWinY, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &glfWinZ);  
   
     //5. Variables Where The Output OpenGL Coords Will Be Stored   
     //    All that is left to do is calculate our final OpenGL coordinates.  
     GLdouble gldPosX, gldPosY, gldPosZ;        //Hold the Final value  
     ::gluUnProject( glfWinX, glfWinY, glfWinZ, gldModelView, gldProjection, glnViewPort, &gldPosX, &gldPosY, &gldPosZ);  
   
     return CDSVector3d(gldPosX, gldPosY, gldPosZ);  
 }  

這邊用到的CDSVector3d是自訂的座標物件。

[TreeCtrl]CTreeLis於Item前塞入圖示

這邊找到的方法是使用CImageList來實作。要注意的是,這邊宣告的CImageList要為Data member,否則會無法顯示圖示。

這邊使用的Icon為BitMap,建立的圖形長度為16bits。
因此在CImageList::Create()要輸入的cx值為16。

建立好CImageList後,再把把他透過CTreeList::SetItemImage()塞給所要使用的CTreeList即可。

程式碼如下:
header
 ....  
 CImageList m_cImageList;  
 ....  

CPP:
     if ( !m_cImageList.Create(IDB_BMP_ICON, 16, 2, RGB(0, 0, 0)) ) {  
         CDSMsgShower::ShowMsgByErrorBox(this->m_hWnd, "ACCERT", MB_OK, "Fail to create image list."); };  
   
     this->m_treEnvItems.SetImageList(&m_cImageList, TVSIL_NORMAL);  
   
     ....  
     this->m_treEnvItems.SetItemImage(hEnvItemsTree, 0, 1);  

如圖形無法顯示,有可能是輸入的bitmap長度有錯。

[TreeCtrl]Drag and Drop

Ref:Drag and drop

這邊需使用到CImageList來進行,D&D的圖示繪製。
由於CTreeCtrl有提供接收D&D的訊息事件,TVN_BEGINDRAG
新增的方式為,選擇於類別檢視內的CTreeCtrl衍伸類別,之後選擇屬性視窗,點選訊息,之後新增TVN_GEGINDRAG就可以了。

這邊會需要用到幾個data member,所以先宣告好。
Header:
 .....  
     protected:  
         CImageList *m_pDragImage;  
         HTREEITEM m_hDragItem;  
         HTREEITEM m_hDropItem;  
         BOOL m_IsLDragging;  
 .....  

在建立好OnTvnBegindrag()後,就可以在裡面實作按下滑鼠左鍵的行為:
 void CDSTreeCtrl::OnTvnBegindrag(NMHDR *pNMHDR, LRESULT *pResult)  
 {  
     LPNMTREEVIEW pNMTreeView = reinterpret_cast<LPNMTREEVIEW>(pNMHDR);  
     // TODO: 在此加入控制項告知處理常式程式碼  
     *pResult = 0;  
   
     this->m_hDragItem = pNMTreeView->itemNew.hItem;  
     this->m_pDragImage = this->CreateDragImage(this->m_hDragItem);  
   
     if ( !this->m_pDragImage ) {  
         return; };  
   
     this->m_IsLDragging = TRUE;  
     this->m_pDragImage->BeginDrag(0, CPoint(-15, -15));        //0 = current used image. Because of this item just has one image.  
                                                             //Under cursor, (-15, -15).  
     POINT pt = pNMTreeView->ptDrag;  
     ClientToScreen(&pt);  
     this->m_pDragImage->DragEnter(NULL, pt);  
     this->SetCapture();  
 }  

此時,僅有設定拖曳的時候所要顯示的圖示,所以還要處理WM_MOUSEMOVE的事件。這個訊息一樣可以透過上述方式來新增。
 void CDSTreeCtrl::OnMouseMove(UINT nFlags, CPoint point)  
 {  
     // TODO: 在此加入您的訊息處理常式程式碼和 (或) 呼叫預設值  
     HTREEITEM hItem = NULL;  
     UINT uFlag = 0;  
   
     if ( this->m_IsLDragging )  
     {  
         POINT pt = point;  
         ClientToScreen(&pt);  
         CImageList::DragMove(pt);  
   
         if ( NULL != (hItem = this->HitTest(point, &uFlag)) )  
         {  
             CImageList::DragShowNolock(FALSE);  
             this->SelectDropTarget(hItem);  
             this->m_hDropItem = hItem;  
             CImageList::DragShowNolock(TRUE);  
         }    //End of if ( NULL != (hItem = this->HitTest(point, &flags)) )  
     }    //End of if ( this->m_IsLDragging )  
   
     CTreeCtrl::OnMouseMove(nFlags, point);  
 }  

最後就是當滑鼠左鍵釋放時,要把選擇的item移到新的位置去。這邊的作法目前僅適用於同一個Tree的item移動。
如有要移到其他位置,那就要配合SendMessage()來處理了。
 void CDSTreeCtrl::OnLButtonUp(UINT nFlags, CPoint point)  
 {  
     // TODO: 在此加入您的訊息處理常式程式碼和 (或) 呼叫預設值  
     if ( this->m_IsLDragging )  
     {  
         this->m_IsLDragging = FALSE;  
         CImageList::DragLeave(this);  
         CImageList::EndDrag();  
         ::ReleaseCapture();  
   
         delete this->m_pDragImage;    //MUST TO DO.  
   
         this->SelectDropTarget(NULL);    //Remove drop target highlighting  
   
         if ( this->m_hDragItem == this->m_hDropItem ) {  
             return; };  
   
         //If Drag item is an ancestor of drop item then return  
         HTREEITEM hItemParent = this->m_hDropItem;  
         while ( NULL != (hItemParent = this->GetParentItem(hItemParent)) )  
         {  
             if ( hItemParent == this->m_hDragItem ) {  
                 return; };  
         }    //End of while ( NULL != (hItemParent = this->GetParentItem(hItemParent)) )  
   
         this->Expand(this->m_hDropItem, TVE_EXPAND);  
   
         //HTREEITEM hItemNew = NULL;  
         //hItemNew = CopyBranch(this->m_hDragItem, this->m_hDropItem, TVI_LAST);  
             //REF:http://www.vckbase.com/english/code/treeview/copy_item.shtml.htm  
         //this->DeleteItem(this->m_hDragItem);  
         //this->SelectItem(hItemNew);  
   
     }    //End of if ( this->m_IsLDragging )  
   
     CTreeCtrl::OnLButtonUp(nFlags, point);  
 }  

NOTE:由於TreeCtrl收到的座標原點在TreeCtrl內,所以當LButtonUp時,所得到的座標資訊,是無法用在其他的物件內。 故必須重新抓新的滑鼠座標。
處理方式有以下兩種:
1.直接用GetCursorPos()並配合ScreenToClient()來取得被當作Drop目的地的座標。
     pLDragOnGLView->pTree->ClientToScreen(&pLDragOnGLView->ptMousePos);  
     this->m_glWindow.ScreenToClient(&pLDragOnGLView->ptMousePos);  

2.將CTreeCtrl提供的point先用ClientToScreen()轉成螢幕座標,再用被當作Drop目的物件的ScreenToClient()來取得實際的座標位置。
     CPoint pt;  
     ::GetCursorPos(&pt);  
     this->m_glWindow.ScreenToClient(&pt);  
     this->m_glWindow.OnLButtonUp(pLDragOnGLView->unMouseMovementFlags, pLDragOnGLView->ptMousePos);  
   

然後由於在OnTvnBeginDrag()時,有設定SetCapture(),所以LButtonUp的訊息會先進到CTreeCtrl內,因此如有要把拖曳的物件放到別的控制項的話,必須在另外呼叫控制項的LButtonUp。

[TreeList]CTreeCtrl更新選定之Root項目的分支

在取得到目前所要更新的Root Item後,先呼叫CTreeCtrl::GetChildItem()取得到第一個Leaf的HTREEITEM。
之後僅需使用CTreeCtrl::GetNextSiblingItem()來取得下一個相鄰的Leaf。

程式碼如下:
 HTREEITEM hCurSelItem = this->m_TreeCtrl.GetSelectedItem();  
   
 //Update to UI  
 HTREEITEM hLeafItem = NULL;  
   
 hLeafItem = this->m_treRolesSetting.GetChildItem(hCurSelItem);  
 this->m_treRolesSetting.SetItemText(hLeafItem, "1st Leaf");  
   
 hLeafItem = this->m_treRolesSetting.GetNextSiblingItem(hLeafItem);  
 this->m_treRolesSetting.SetItemText(hLeafItem, "2nd Leaf");  

[TreeCtrl]CTreeCtrl取得目前滑鼠右鍵點選項目

這邊需使用到HitTest()來取得滑鼠點選的項目。

步驟如下:
1.使用GetCursorPos()來取得目前滑鼠座標。
2.使用CTreeCtrl::ScreenToClient()轉換滑鼠座標位置到CTreeCtrl的相對位置。
3.使用CTreeCtrl::HitTest()來取得此座標位置是否有Item存在。
4.判斷HitTest()回傳的Flag狀態是否為TVHT_ONITEM
5.如上述步驟都成立,則HitTest()回傳之HTREEITEM則為所要之Item。如此值為NULL,則代表此座標位置並無Tree Item存在。

程式碼如下:
 CPoint pt;  
 UINT unFlag = 0;  
 ::GetCursorPos(&pt);  
 pTree->ScreenToClient(&pt);  
   
 //When this function is called, the pt parameter specifies the coordinates of the point to test.   
 //The function returns the handle of the item at the specified point or NULL if no item occupies the point.   
 //In addition, the pFlags parameter contains a value that indicates the location of the specified point.  
 HTREEITEM htreeHitItem = pTree->HitTest(pt, &unFlag);  
   
 if ( htreeHitItem == NULL || !(unFlag&TVHT_ONITEM) )        //No hit item, and not on item  
 {  
     //Just show Create  
     return false;   
 }else if ( htreeHitItem && (unFlag & TVHT_ONITEM) ) {  
     //Just show edit and delete  
     pTree->Select(htreeHitItem, TVGN_CARET);        //Set this item be selected  
     //pTree->SelectItem(htreeHitItem);              //用這個也可以
 }else{        //Not allow  
     return false;  
 }    //End of if ( htreeHitItem == NULL || !(unFlag&TVHT_ONITEM) )  

如需再指定此Item被選擇,僅需使用CTreeCtrl::Select()或是CTreeCtrl::SelectItem()即可。

[OpenGL]使用OpenGL顯示文字 - 標準英文字

此處參考了Nehe的L13來進行實作。

繪製的部份一樣是在DrawScene裡面處理,但需新增方便用來顯示文字的glPrint()。
建立OpenGL繪圖視窗的方式可以參考[MFC]使用Picture Control來當做DrawScene

建立流程如下:
1.先建立可以存放字集的變數於header檔內。
 class COpenGLDrawer :    public CWnd  
 {  
 ....  
 //================ Data member ==================  
 //------------------ Set Font Head ---------------  
 //BOOK:Draw Font  
         GLuint m_gluiBaseDisplayList;       //For the font set  
                                             //base will hold the number of the first display list we create. Each character requires it's own display list.   
                                             //The character 'A' is 65 in the display list, 'B' is 66, 'C' is 67, etc. So 'A' would be stored in display list base+65.   
 //------------------ Set Font Foot ---------------  
 ....  
 };  

2.接下來的是產生字表的glBuildFont()。
Header:
 class COpenGLDrawer :    public CWnd  
 {  
 ....  
  protected:  
   GLvoid glBuidFont(GLvoid);  
 ....  
 };  

CPP:
 GLvoid COpenGLDrawer::glBuidFont(GLvoid)  
 {  
     HFONT hFont;  
     HFONT holdFont;    //stores the previously used font.   
                        //SelectObject will return the font (or pen, or filler, or whatever GDI objects) that was previously set when it switches to the new font.  
                        //The way that GDI works is such that the use of the return value of SelectObject is not very apparent.   
                        //At first glance it looks as if the code is selecting the new font and returning a pointer and storing it within oldFont.   
     this->m_gluiBaseDisplayList = glGenLists(CHAR_LIST_NUM);                //Storage for 96 characters (NEW)  
     hFont = ::CreateFont(        FONT_HIEGHT,    //Height of Font (NEW)  
                                                  //By putting a minus, we're telling windows to find us a font based on the CHARACTER height.   
                                                  //If we use a positive number we match the font based on the CELL height.   
                                            0,    //Width of Font  
                                            0,    //Angle of Escapement  
                                            0,    //Orientation Angle  
                                    FW_NORMAL,    //Font Weight  
                                                  //You can put a number from 0 - 1000 or you can use one of the predefined values.   
                                                  //FW_DONTCARE is 0, FW_NORMAL is 400, FW_BOLD is 700 and FW_BLACK is 900.   
                                                  //There are alot more predefined values, but those 4 give some good variety. The higher the value, the thicker the font (more bold).   
                                        FALSE,    //Italic  
                                        FALSE,    //Underline  
                                        FALSE,    //Strikeout  
                                 ANSI_CHARSET,    //Character Set Identifier  
                                                  //Character set Identifier describes the type of Character set you wish to use.   
                                                  //There are too many types to explain.   
                                                  //CHINESEBIG5_CHARSET, GREEK_CHARSET, RUSSIAN_CHARSET, DEFAULT_CHARSET, etc.   
                                                  //ANSI is the one I use, although DEFAULT would probably work just as well.  
                                                  //If you're interested in using a font such as Webdings or Wingdings,   
                                                  //        you need to use SYMBOL_CHARSET instead of ANSI_CHARSET.   
                                OUT_TT_PRECIS,    //Output Precision  
                                                  //It tells Windows what type of character set to use if there is more than one type available.   
                                                  //OUT_TT_PRECIS tells Windows that if there is more than one type of font to choose from with the same name,   
                                                  //        select the TRUETYPE version of the font. Truetype fonts always look better, especially when you make them large.   
                                                  //You can also use OUT_TT_ONLY_PRECIS, which ALWAYS trys to use a TRUETYPE Font.   
                          CLIP_DEFAULT_PRECIS,    //Clipping Precision  
                                                  //Clipping Precision is the type of clipping to do on the font if it goes outside the clipping region.   
                                                  //Not much to say about this, just leave it set to default.   
                          ANTIALIASED_QUALITY,    //Output Quality  
                                                  //Output Quality is very important. You can have PROOF, DRAFT, NONANTIALIASED, DEFAULT or ANTIALIASED.   
                                                  //We all know that ANTIALIASED fonts look good :)   
                                                  //Antialiasing a font is the same effect you get when you turn on font smoothing in Windows. It makes everything look less jagged.   
                    FF_DONTCARE|DEFAULT_PITCH,    //Family and Pitch  
                                                  //Next we have the Family and Pitch settings.   
                                                  //For pitch you can have DEFAULT_PITCH, FIXED_PITCH and VARIABLE_PITCH,   
                                                  //    and for family you can have FF_DECORATIVE, FF_MODERN, FF_ROMAN, FF_SCRIPT, FF_SWISS, FF_DONTCARE.   
                                                  //Play around with them to find out what they do. I just set them both to default.   
                            DISPLAY_FONT_TYPE     // Font Name  
                                                  //Can replace the value with the font you would rather use.  
             );  
     holdFont = (HFONT)::SelectObject(this->m_hdc, hFont);          //Selects the Font we want.  
     wglUseFontBitmaps(this->m_hdc, CHAR_LIST_START_POINT, CHAR_LIST_NUM, this->m_gluiBaseDisplayList);        //Build 96 Characters Starting at Character 32.  
     ::DeleteObject(hFont);                                                                                    //Delete the Font  
 }  

3.當然也是需要釋放記憶體用的glKillFont。呼叫時機就看使用情況,目前是在Desctructor時進行。
Header:
 class COpenGLDrawer :    public CWnd  
 {  
 ....  
  protected:  
   GLvoid glKillFont(GLvoid);  
 ....  
 };  

CPP:
GLvoid COpenGLDrawer::glKillFont(GLvoid)
{
glDeleteLists(this->m_gluiBaseDisplayList, CHAR_LIST_NUM); //Delete all 96 Characeters
}

4.用來繪製文字的glPrint(),作者為了方便使用,就包裝成printf()模式。
Header:
 class COpenGLDrawer :    public CWnd  
 {  
 ....  
  protected:  
   GLvoid glPrint(const char* szFormat, ...);  
 ....  
 };  

CPP:
 GLvoid COpenGLDrawer::glPrint(const char* szFormat, ...)  
 {  
     char szText[256];        //Holds Our String  
     va_list argList;            //Pointer to List of arguments  
     if ( szFormat == NULL ) {  
         return; };  
     va_start(argList, szFormat);  
     vsprintf(szText, szFormat, argList);  
     va_end(argList);  
     //We then push the GL_LIST_BIT, this prevents glListBase from affecting any other display lists we may be using in our program.   
     //The command glListBase(base-32) is a little hard to explain. Say we draw the letter 'A', it's represented by the number 65.   
     //Without glListBase(base-32) OpenGL wouldn't know where to find this letter.   
     //It would look for it at display list 65, but if base was equal to 1000, 'A' would actually be stored at display list 1065.   
     //So by setting a base starting point, OpenGL knows where to get the proper display list from. The reason we subtract 32 is because we never made the first 32 display lists.   
     //We skipped them. So we have to let OpenGL know this by subtracting 32 from the base value. I hope that makes sense.   
     glPushAttrib(GL_LIST_BIT);                                                                            //Pushes the Display List Bits  
     glListBase( this->m_gluiBaseDisplayList-CHAR_LIST_START_POINT );        //Sets the base Character to 32  
     //Now that OpenGL knows where the Letters are located, we can tell it to write the text to the screen. glCallLists is a very interesting command.   
     //It's capable of putting more than one display list on the screen at a time.  
     //The line below does the following. First it tells OpenGL we're going to be displaying lists to the screen. strlen(text) finds out how many letters we're going to send to the screen.   
     //Next it needs to know what the largest list number were sending to it is going to be. We're not sending any more than 255 characters.   
     //The lists parameter is treated as an array of unsigned bytes, each in the range 0 through 255. Finally we tell it what to display by passing text (pointer to our string).  
     //In case you're wondering why the letters don't pile on top of eachother. Each display list for each character knows where the right side of the letter is.   
     //After the letter is drawn, OpenGL translates to the right side of the drawn letter.   
     //The next letter or object drawn will be drawn starting at the last location GL translated to, which is to the right of the last letter.  
     //Finally we pop the GL_LIST_BIT setting GL back to how it was before we set our base setting using glListBase(base-32).   
     glCallLists(strlen(szText), GL_UNSIGNED_BYTE, szText);                                //Draw the display list text  
     glPopAttrib();                                                                                                    //Pops the Display List Bits  
 }  

5.使用時機就是在DrawScene()裡面處理就可以了,先畫線或是先顯示文字部分,目前測試是都可以。
 void COpenGLDrawer::glDrawScene(void)  
 {  
 //**************** Draw Grid Head *******************  
     glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);            //Set polygon rasterization mode  
     glBegin(GL_QUADS);                                                                    //Start to draw squares  
     for ( unsigned int i = 0 ; i < this->m_vcdsGrid.size() ; i++ )  
     {  
     //3D  
         //glVertex3f(this->m_vcdsGrid[i].LB.x, this->m_vcdsGrid[i].LB.y, this->m_vcdsGrid[i].LB.z);  
         //glVertex3f(this->m_vcdsGrid[i].RB.x, this->m_vcdsGrid[i].RB.y, this->m_vcdsGrid[i].RB.z);  
         //glVertex3f(this->m_vcdsGrid[i].RT.x, this->m_vcdsGrid[i].RT.y, this->m_vcdsGrid[i].RT.z);  
         //glVertex3f(this->m_vcdsGrid[i].LT.x, this->m_vcdsGrid[i].LT.y, this->m_vcdsGrid[i].LT.z);  
     //2D          
         glVertex2f(this->m_vcdsGrid[i].LB.x, this->m_vcdsGrid[i].LB.y);  
         glVertex2f(this->m_vcdsGrid[i].RB.x, this->m_vcdsGrid[i].RB.y);  
         glVertex2f(this->m_vcdsGrid[i].RT.x, this->m_vcdsGrid[i].RT.y);  
         glVertex2f(this->m_vcdsGrid[i].LT.x, this->m_vcdsGrid[i].LT.y);  
         if ( !this->m_vcdsGrid4AStar[i].IsWarkable ) {  
             this->m_vcdsGrid4DrawBlocking.push_back(m_vcdsGrid4AStar[i]);    };  
     }    //End of for ( unsigned int i = 0 ; i < this->m_vcpGrid.size() ; i++ )  
     glEnd();  
 //**************** Draw Grid Foot *******************  
 //**************** Draw Font Head *******************  
     CString cstrDisplay;  
     SIZE nLen;  
     for ( unsigned int i = 0 ; i < this->m_vcdsGrid4AStar.size() ; i++ )  
     {  
 //Pulsing colors based on text position  
         glColor3f( 1.0f, 1.0f, 1.0f );        //White  
 //Position the text on the screen  
         glRasterPos2f( (this->m_vcdsGrid4AStar[i].cdsGridInfo.LB.x + 0.1f), (this->m_vcdsGrid4AStar[i].cdsGridInfo.LB.y + 0.1f) );        //set to center  
 //Print GL text to the screen  
         this->glPrint("%d", this->m_vcdsGrid4AStar[i].nCostedAP);  
         glRasterPos2f( this->m_vcdsGrid4AStar[i].cdsGridInfo.LT.x + 0.1f, this->m_vcdsGrid4AStar[i].cdsGridInfo.LT.y - 0.2f);  
         this->glPrint("%d", this->m_vcdsGrid4AStar[i].nTotalCostAP);  
         cstrDisplay.Format("%d", this->m_vcdsGrid4AStar[i].nRequiredAP2Target+100);  
         ::GetTextExtentPoint32(this->m_hdc, cstrDisplay, cstrDisplay.GetLength(), &nLen);  
         glRasterPos2f( this->m_vcdsGrid4AStar[i].cdsGridInfo.RB.x - 0.03f*nLen.cx, this->m_vcdsGrid4AStar[i].cdsGridInfo.RB.y + 0.1f);  
         this->glPrint("%d", this->m_vcdsGrid4AStar[i].nRequiredAP2Target+100);  
     }    //End of for ( unsigned int i = 0 ; i < this->m_vcdsGrid.size() ; i++ )  
 //**************** Draw Font Foot *******************  
 }  


此處輸入的是在每個Cell內的左上、左下以及右下塞入各一個數字,結果如下圖:

[WinAPI]計算繪圖文字長度

目前是使用GetTextExtentPoint32()來取得文字長度。

回傳的參數為文字的長度跟高度,單位為Pixel。

測試結果之後再補上。

[Win]服務顯示MsgBox

僅需在使用MsgBox()時,在uType參數要下MB_SERVICE_NOTIFICATION。

同時在hWnd部分,一定要給NULL。

目前並沒有做很多測試,所以限制部分,稍後補上。

目前可以確認的是,僅有Windows XP可以達到此效果。

[MFC]使用Picture Control來當做DrawScene

參考來源:
1.http://blog.sina.com.cn/s/blog_6ac675410100o0t6.html
2.http://www.codeguru.com/cpp/cpp/cpp_mfc/tutorials/article.php/c10975__2/Setting-Up-OpenGL-in-an-MFC-Control.htm
PS.內部參數皆先參考範例內資料。

這邊使用的為Dialog-based的FormView。因此在Dialog上拉出一個Picture Control即可。
ID可以隨便設定,但是Visible的屬性要設成false
根據作者的說法,有些狀況下,如果把Visible的屬性設成True的話,那麼在畫面重新繪製的時候,OpenGL繪製的畫面,會被Windows繪製PictureControl的畫面蓋掉。
目前測試手頭機器的確會有這樣的狀況。

需載入的header為以及。並引用opengl32.lib。

接下來要做的,就是新增用來繪製OpenGL用的Class。名稱也是一樣可以自由設定,這邊是使用COpenGLDrawer。
1.需要預先宣告的是Timer的指標以及相關需用來繪圖的成員。
 class COpenGLDrawer :    public CWnd  
 {  
 //================ Data member ==================  
     public:  
         UINT_PTR m_unpTimer;      //timer  
     private:  
 //Window Information - for draw scene  
         CWnd *m_hWnd;           //Handle of parent window  
         HDC m_hdc;              //Handle of DC for OpenGL using  
         HGLRC    m_hrc;         //Handle of RC for OpenGL using  
         int m_nPixelFormat;     //Pixel format  
         CRect m_rect;           //For getting window rect  
         CRect m_rectOldWindow;  //rect of previous displayed window  
         CRect m_rectOrgRect;    //rect of original displayed window  
 };  
相關成員也要記得先初始化,避免使用到錯誤的數值。

2.建立glCreate()。此函式是用來向系統註冊Class、建立OpenGL使用之視窗屬性以及記錄視窗的初始設定。
Header:
 class COpenGLDrawer :    public CWnd  
 {  
 ....  
 //============== Member Function =================  
     public:  
         COpenGLDrawer(void);  
         ~COpenGLDrawer(void);  
         void glCreate(CRect rect, CWnd *parent);  
 ....  
 };  

CPP:
 void COpenGLDrawer::glCreate(CRect rect, CWnd *parent)  
 {  
     CString cstrClassName = ::AfxRegisterWndClass(CS_HREDRAW|CS_VREDRAW|CS_OWNDC,  
                                                                                 NULL,  
                                                                                 (HBRUSH)::GetStockObject(BLACK_BRUSH),  
                                                                                 NULL);  
     CreateEx(0, cstrClassName, "OpenGL", WS_CHILD|WS_VISIBLE|WS_CLIPSIBLINGS|WS_CLIPCHILDREN, rect, parent, 0);  
     //Set initail variable's value  
     this->m_rectOldWindow = rect;  
     this->m_rectOrgRect = rect;  
     this->m_hWnd = parent;  
 }  

3.由於OpenGL是使用Timer來進行畫面的繪製,因此就不適用於MFC所提供的繪製畫面的方法。因此需要跳過Windows繪圖的函式 - OnPaint()(WM_PAINT)。在OnPaint()內,使用ValidateRect()來讓Picture Control不被更新。
 void COpenGLDrawer::OnPaint()  
 {  
     //CPaintDC dc(this); // device context for painting  
     // TODO: Add your message handler code here  
     // Do not call CWnd::OnPaint() for painting messages  
     ValidateRect(NULL);  //Remove all of region of the client from the updatr region
 }  

4.建立產生OpenGL視窗的函式。裡面呼叫用來初始化OpenGL視窗的相關參數。這邊則改寫WM_CREATE之內容。
 int COpenGLDrawer::OnCreate(LPCREATESTRUCT lpCreateStruct)  
 {  
     if (CWnd::OnCreate(lpCreateStruct) == -1)  
         return -1;  
     // TODO: Add your specialized creation code here  
     this->glInitialize();  
     return 0;  
 }  

5.建立初始化OpenGL之函式。
Header:
 class COpenGLDrawer :    public CWnd  
 {  
 ....  
 //============== Member Function =================  
     public:  
         COpenGLDrawer(void);  
         ~COpenGLDrawer(void);  
         void glCreate(CRect rect, CWnd *parent);  
     protected:  
         void glInitialize(void);  
 };  

CPP:
 void COpenGLDrawer::glInitialize(void)  
 {  
 //Inital Setup  
     static PIXELFORMATDESCRIPTOR pfd =  
     {  
         sizeof(PIXELFORMATDESCRIPTOR),  
         1,  
         PFD_DRAW_TO_WINDOW|PFD_SUPPORT_OPENGL|PFD_DOUBLEBUFFER,  
         PFD_TYPE_RGBA,  
         32,                                //bit depth  
         0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,  
         16,                                //z-buffer depth  
         0, 0, 0, 0, 0, 0, 0  
     };  
 //Get device context only once  
     this->m_hdc = GetDC()->m_hDC;  
 //Pixel format  
     this->m_nPixelFormat = ::ChoosePixelFormat(this->m_hdc, &pfd);  
     ::SetPixelFormat(this->m_hdc, this->m_nPixelFormat, &pfd);  
 //Create the OpenGL Rendering Context  
     this->m_hrc = ::wglCreateContext(this->m_hdc);  
     ::wglMakeCurrent(this->m_hdc, this->m_hrc);  
 //Basisc Setup:  
     //Set color to use when clearing the background  
     ::glClearColor(0.0f, 0.0f, 0.0f, 1.0f);  
     ::glClearDepth(1.0f);  
     //Turn on backface culling  
     ::glFrontFace(GL_CCW);  
     ::glCullFace(GL_BACK);  
     //Turn on depth testing  
     ::glEnable(GL_DEPTH_TEST);  
     ::glDepthFunc(GL_LEQUAL);  
     //Send draw request  
     this->OnDraw(NULL);  
 }  

6.建立OnDraw函式。此函式內處理圖形的移動以及旋轉,也就是鏡頭的控制。
Header:
 class COpenGLDrawer :    public CWnd  
 {  
 ....  
 // View information variables if camera - for mouse move  
         float m_fLastX;  
         float m_fLastY;  
         float m_fPosX;  
         float m_fPosY;  
         float m_fZoom;  
         float m_fRotX;  
         float m_fRotY;  
 //============== Member Function =================  
 ....  
     public:  
         afx_msg void OnDraw(CDC *pDC);  
 ....  
 };  

CPP:
 void COpenGLDrawer::OnDraw(CDC *pDC)  
 {  
   // TODO: Camera controls.  
     glLoadIdentity();  
     glTranslatef( 0.f, 0.f, -this->m_fZoom );  
     glTranslatef( this->m_fPosX, this->m_fPosY, 0.f);  
     glRotatef( this->m_fRotX, 1.f, 0.f, 0.f );  
     glRotatef( this->m_fRotY, 0.f, 1.f, 0.f);  
 }  

7.新增圖像更新用的OnTimer()(WM_TIMER)。
 void COpenGLDrawer::OnTimer(UINT_PTR nIDEvent)  
 {  
     // TODO: Add your message handler code here and/or call default  
     switch (nIDEvent)  
     {  
         case 1:          
         //Clear color and depth buffer bits  
             ::glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);  
         //Draw OpenGL scene  
             this->glDrawScene();  
         //Swap buffers  
             ::SwapBuffers(this->m_hdc);  
             break;  
         default:  
             break;  
     }    //End of switch (nIDEvent)  
     CWnd::OnTimer(nIDEvent);  
 }  

8.當視窗有需要自由變更大小時,則需改寫OnSize()(WM_SIZE)。
 void COpenGLDrawer::OnSize(UINT nType, int cx, int cy)  
 {  
     CWnd::OnSize(nType, cx, cy);  
     // TODO: Add your message handler code here  
     if ( 0>= cx || 0 >= cy || nType == SIZE_MINIMIZED ) {  
         return; };  
 //Resize  
     switch (nType)  
     {  
         case SIZE_MAXIMIZED:        // If widow resize token is "maximize"  
             //get the current window rect  
             GetWindowRect(m_rect);  
             //Move the window accordingly  
             MoveWindow(6, 6, (cx-14), (cy-14));  
             //Get the new window rect  
             GetWindowRect(this->m_rect);  
             //Store our old window as the new rect  
             this->m_rectOldWindow = this->m_rect;  
             break;  
         case SIZE_RESTORED:        //if window resize token is "restore"  
             //If the window is currently maximized  
             if ( this->m_bIsMaximized )  
             {  
                 //Get the current window rect  
                 GetWindowRect(this->m_rect);  
                 //Move the window accordingly ( to our stored old window)  
                 MoveWindow(this->m_rectOldWindow.left, (this->m_rectOldWindow.top-18), (this->m_rectOrgRect.Width()-4), (this->m_rectOrgRect.Height()-4));  
                 //Get the new window rect  
                 GetWindowRect(this->m_rect);  
                 //Store our old window as the new rect  
                 this->m_rectOldWindow = this->m_rect;  
             }    //End of if ( this->m_bIsMaximized )  
             break;  
     }    //End of switch (nType)  
 //Map the OpengGL coordinates  
     glViewport(0, 0, cx, cy);  
 //Projection view  
     glMatrixMode(GL_PROJECTION);  
     glLoadIdentity();  
 //Set our current view perspective  
     gluPerspective(35.0f, (float)cx / (float)cy, 0.01f, 2000.0f);  
 //Mode view  
     glMatrixMode(GL_MODELVIEW);  
 }  

當然也是要改寫Dialog的OnSize()。再由Dialog來呼叫OpenGL class來進行重新繪製。
 void CMainDlg::OnSize(UINT nType, int cx, int cy)  
 {  
     CDialog::OnSize(nType, cx, cy);  
     // TODO: Add your message handler code here  
     switch(nType)  
     {  
         case SIZE_RESTORED:  
             if ( this->m_glWindow.m_bIsMaximized ) {  
                 this->m_glWindow.OnSize(nType, cx, cy);  
                 this->m_glWindow.m_bIsMaximized = false;  
             }    //End of if ( this->m_glWindow.m_bIsMaximized )  
             break;  
         case SIZE_MAXIMIZED:  
             this->m_glWindow.OnSize(nType, cx, cy);  
             this->m_glWindow.m_bIsMaximized = true;  
             break;  
     }    //End of switch(ntype)  
 }  

9.當Timer設定完成後,即可定期的去做任何的更新動作。所以如有需要於畫面上繪製圖形,則需自行新增繪圖函式,並在OnTimer()內呼叫。此處宣告的繪圖函式為glDrawScene()。此處繪製一六面體。
Header:
 class COpenGLDrawer :    public CWnd  
 {  
 ....  
     protected:  
         void glInitialize(void);  
         void glDrawScene(void);  
 };  

CPP:
 void COpenGLDrawer::glDrawScene(void)  
 {  
 //Wireframe Mode  
     glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);  
     glBegin(GL_QUADS);  
 //Top side  
     glVertex3f( 1.0f, 1.0f, 1.0f );  
     glVertex3f( 1.0f, 1.0f, -1.0f );  
     glVertex3f( -1.0f, 1.0f, -1.0f );  
     glVertex3f( -1.0f, 1.0f, 1.0f );  
 //Bottom side  
     glVertex3f( -1.0f, -1.0f, -1.0f );  
     glVertex3f( 1.0f, -1.0f, -1.0f );  
     glVertex3f( 1.0f, -1.0f, 1.0f );  
     glVertex3f( -1.0f, -1.0f, 1.0f );  
 //Front side  
     glVertex3f( 1.0f, 1.0f, 1.0f );  
     glVertex3f( -1.0f, 1.0f, 1.0f );  
     glVertex3f( -1.0f, -1.0f, 1.0f );  
     glVertex3f( 1.0f, -1.0f, 1.0f );  
 //Back side  
     glVertex3f( -1.0f, -1.0f, -1.0f );  
     glVertex3f( -1.0f, 1.0f, -1.0f );  
     glVertex3f( 1.0f, 1.0f, -1.0f );  
     glVertex3f( 1.0f, -1.0f, -1.0f );  
 //Left side  
     glVertex3f( -1.0f, -1.0f, -1.0f );  
     glVertex3f( -1.0f, -1.0f, 1.0f );  
     glVertex3f( -1.0f, 1.0f, 1.0f );  
     glVertex3f( -1.0f, 1.0f, -1.0f );  
 //Right side  
     glVertex3f( 1.0f, 1.0f, 1.0f );  
     glVertex3f( 1.0f, -1.0f, 1.0f );  
     glVertex3f( 1.0f, -1.0f, -1.0f );  
     glVertex3f( 1.0f, 1.0f, -1.0f );  
     glEnd();  
 }  

10.如有需控制物體的選轉、移動,則需改寫OnMouseMove()(WM_MOUSEMOVE)。
 void COpenGLDrawer::OnMouseMove(UINT nFlags, CPoint point)  
 {  
     // TODO: Add your message handler code here and/or call default  
     int diffX = (int)(point.x - this->m_fLastX);  
     int diffY = (int)(point.y - this->m_fLastY);  
     this->m_fLastX = (float)point.x;  
     this->m_fLastY = (float)point.y;  
 //Left mouse button  
     if ( nFlags & MK_LBUTTON )  
     {  
         this->m_fRotX += (float)0.5f * diffY;  
         if ( (this->m_fRotX > 360.0f) || (this->m_fRotX < -360.0f) )    {  
             this->m_fRotX = 0.f;    };  
         this->m_fRotY += (float)0.5f * diffX;  
         if ( (this->m_fRotY > 360.f) || (this->m_fRotY < -360.f) ) {  
             this->m_fRotY = 0.f; };  
 //Right mouse button  
     }else if ( nFlags & MK_RBUTTON ){  
         this->m_fZoom -= (float)0.1f * diffY;  
 //Middle mouse button  
     }else if ( nFlags & MK_MBUTTON ){  
         this->m_fPosX += (float)0.05f * diffX;  
         this->m_fPosY -= (float)0.05f * diffY;  
     }    //End of if ( nFlags & MK_LBUTTON )  
     this->OnDraw(NULL);  
     CWnd::OnMouseMove(nFlags, point);  
 }  

如果希望可以透過滑鼠滾輪來縮放大小,則一樣要改寫OnMouseWheel()(WM_MOUSEWHEEL)。但目前測試結果為WM_MOUSEWHEEL會先被Dialog所接收,所以這邊改寫的OnMouseWheel()並不會被message queue呼叫。
因此,也一併改寫Dialog的OnMouseWheel(),當收到message時,則呼叫COpenGLDrawer的OnMouseWheel()來達到縮放大小之結果。
COpenGLDrawer.cpp
 BOOL COpenGLDrawer::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)  
 {  
     // TODO: Add your message handler code here and/or call default  
     int diffX = (int)(pt.x - this->m_fLastX);  
     int diffY = (int)(pt.y - this->m_fLastY);  
     this->m_fLastX = (float)pt.x;  
     this->m_fLastY = (float)pt.y;  
     if ( zDelta == WHEEL_DELTA )                    //Rolls to forward - Zoom in  
     {  
         this->m_fZoom -= (float)0.05f * diffY;  
     }else if (zDelta == -WHEEL_DELTA) {        //Rolls to back - Zoom out  
         this->m_fZoom += (float)0.05f * diffY;  
     }    //End of if ( zDelta > 0 )   
     this->OnDraw(NULL);  
     return CWnd::OnMouseWheel(nFlags, zDelta, pt);  
 }  

CMainDlg.cpp
 BOOL CMainDlg::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)  
 {  
     // TODO: Add your message handler code here and/or call default  
     this->m_glWindow.OnMouseWheel(nFlags, zDelta, pt);  
     return CDialog::OnMouseWheel(nFlags, zDelta, pt);  
 }  

不使用時,也是要釋放記憶體。
 COpenGLDrawer::~COpenGLDrawer(void)  
 {  
     wglMakeCurrent(NULL, NULL);  
     wglDeleteContext(this->m_hrc);
     DeleteDC(this->m_hdc);
 }  

到上述步驟完成後,OpenGL繪圖用的Class就已經完成了。接下來就要到主要的Dialog來使用此class。

11.建立OpenGL物件,以及設定參數。
Header:
 protected:  
     COpenGLDrawer m_glWindow;  

CPP:
 BOOL CMainDlg::OnInitDialog()  
 {  
     CDialog::OnInitDialog();  
     // Add "About..." menu item to system menu.  
     // IDM_ABOUTBOX must be in the system command range.  
     ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);  
     ASSERT(IDM_ABOUTBOX < 0xF000);  
     CMenu* pSysMenu = GetSystemMenu(FALSE);  
     if (pSysMenu != NULL)  
     {  
         CString strAboutMenu;  
         strAboutMenu.LoadString(IDS_ABOUTBOX);  
         if (!strAboutMenu.IsEmpty())  
         {  
             pSysMenu->AppendMenu(MF_SEPARATOR);  
             pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);  
         }  
     }  
     // Set the icon for this dialog. The framework does this automatically  
     // when the application's main window is not a dialog  
     SetIcon(m_hIcon, TRUE);            // Set big icon  
     SetIcon(m_hIcon, FALSE);        // Set small icon  

     // TODO: Add extra initialization here  
     CRect rect;  
 //Get size and position of the picture control  
     GetDlgItem(IDC_PIC_DRAWDC)->GetWindowRect(rect);  
 //Convert screen coordinates to client coordinate  
     ScreenToClient(rect);  
 //Create OpenGL Control window  
     this->m_glWindow.glCreate(rect, this);  
 //Setup the OpenGL Window's timer to render  
     this->m_glWindow.m_unpTimer = this->m_glWindow.SetTimer(1, 1, 0);  
     return TRUE; // return TRUE unless you set the focus to a control  
 }  

上述完成後,就可以在Picture Control裡繪製圖形了,至於效能部分,就待之後之測試再進行說明。

PS.如有要將FormView當做繪圖區,則只要在進行ScreenToClient()時,塞入FormView的rect即可。

Build docker image from multiple build contexts

Build docker image from multiple build contexts ...