• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            posts - 16,  comments - 81,  trackbacks - 0
             

            用Visual C++操作INI文件

            在我們寫的程序當中,總有一些配置信息需要保存下來,以便完成程序的功能,最簡單的辦法就是將這些信息寫入INI文件中,程序初始化時再讀入.具體應用如下:

            一.將信息寫入.INI文件中.

            1.所用的WINAPI函數原型為:

            BOOL WritePrivateProfileString(
            LPCTSTR lpAppName,
            LPCTSTR lpKeyName,
            LPCTSTR lpString,
            LPCTSTR lpFileName
            );

            其中各參數的意義:

             LPCTSTR lpAppName 是INI文件中的一個字段名.

              LPCTSTR lpKeyName 是lpAppName下的一個鍵名,通俗講就是變量名.

             LPCTSTR lpString 是鍵值,也就是變量的值,不過必須為LPCTSTR型或CString型的.

             LPCTSTR lpFileName 是完整的INI文件名.

            2.具體使用方法:設現有一名學生,需把他的姓名和年齡寫入 c:\stud\student.ini 文件中.

            CString strName,strTemp;
            int nAge;
            strName="張三";
            nAge=12;
            ::WritePrivateProfileString("StudentInfo","Name",strName,"c:\\stud\\student.ini");

            此時c:\stud\student.ini文件中的內容如下:

            [StudentInfo]
            Name=張三

            3.要將學生的年齡保存下來,只需將整型的值變為字符型即可:

            strTemp.Format("%d",nAge);
            ::WritePrivateProfileString("StudentInfo","Age",strTemp,"c:\\stud\\student.ini");

            二.將信息從INI文件中讀入程序中的變量.

            1.所用的WINAPI函數原型為:

            DWORD GetPrivateProfileString(
            LPCTSTR lpAppName, 
            LPCTSTR lpKeyName, 
            LPCTSTR lpDefault, 
            LPTSTR lpReturnedString, 
            DWORD nSize, 
            LPCTSTR lpFileName 
            );

            其中各參數的意義:

            前二個參數與 WritePrivateProfileString中的意義一樣.

              lpDefault : 如果INI文件中沒有前兩個參數指定的字段名或鍵名,則將此值賦給變量.

             lpReturnedString : 接收INI文件中的值的CString對象,即目的緩存器.

             nSize : 目的緩存器的大小.

             lpFileName : 是完整的INI文件名.

            2.具體使用方法:現要將上一步中寫入的學生的信息讀入程序中.

            CString strStudName;
            int nStudAge; 
            GetPrivateProfileString("StudentInfo","Name","默認姓名",strStudName.GetBuffer(MAX_PATH),MAX_PATH,"c:\\stud\\student.ini");

             執行后 strStudName 的值為:"張三",若前兩個參數有誤,其值為:"默認姓名".

            3.讀入整型值要用另一個WINAPI函數:

            UINT GetPrivateProfileInt(
            LPCTSTR lpAppName, 
            LPCTSTR lpKeyName, 
            INT nDefault, 
            LPCTSTR lpFileName 
            );

            這里的參數意義與上相同.使用方法如下:

            nStudAge=GetPrivateProfileInt("StudentInfo","Age",10,"c:\\stud\\student.ini");

            三.循環寫入多個值,設現有一程序,要將最近使用的幾個文件名保存下來,具體程序如下:

            1.寫入:

            CString strTemp,strTempA;
            int i;
            int nCount=6;
            file://共有6個文件名需要保存
            for(i=0;i<nCount;i++) 
            {
              strTemp.Format("%d",i);
              strTempA=文件名;
              //文件名可以從數組,列表框等處取得.
              ::WritePrivateProfileString("UseFileName","FileName"+strTemp,strTempA,"c:\\usefile\\usefile.ini");
            }
            strTemp.Format("%d",nCount);
            ::WritePrivateProfileString("FileCount","Count",strTemp,"c:\\usefile\\usefile.ini");
            //將文件總數寫入,以便讀出.

            2.讀出:

            nCount=::GetPrivateProfileInt("FileCount","Count",0,"c:\\usefile\\usefile.ini");
            for(i=0;i<nCount;i++)
            {
              strTemp.Format("%d",i);
              strTemp="FileName"+strTemp;
              ::GetPrivateProfileString("CurrentIni",strTemp,"default.fil", strTempA.GetBuffer(MAX_PATH),MAX_PATH,"c:\\usefile\\usefile.ini");
              //使用strTempA中的內容.
            }

            補充四點:
             1.INI文件的路徑必須完整,文件名前面的各級目錄必須存在,否則寫入不成功,該函數返回 FALSE 值.
             2.文件名的路徑中必須為 \\ ,因為在VC++中, \\ 才表示一個 \ .
             3.也可將INI文件放在程序所在目錄,此時 lpFileName 參數為: ".\\student.ini".

            //----------------------------------------------------------------------------------
            /*
            類名:CIni
            版本:v2.0
            加入高級操作的功能
            v1.0
            夢小孩于2003年某日
            一般操作完成

            類描述:
            本類可以于.ini文件進行操作
            */

            文件 1:
            #pragma once
            #include "afxTempl.h"

            class CIni
            {
            private:
            CString m_strFileName;
            public:
            CIni(CString strFileName):m_strFileName(strFileName)
            {
            }
            public:
            //一般性操作:
            BOOL SetFileName(LPCTSTR lpFileName);  //設置文件名
            CString GetFileName(void); //獲得文件名
            BOOL SetValue(LPCTSTR lpSection, LPCTSTR lpKey, LPCTSTR lpValue,bool bCreate=true); //設置鍵值,bCreate是指段名及鍵名未存在時,是否創建。
            CString GetValue(LPCTSTR lpSection, LPCTSTR lpKey); //得到鍵值.
            BOOL DelSection(LPCTSTR strSection);  //刪除段名
            BOOL DelKey(LPCTSTR lpSection, LPCTSTR lpKey);  //刪除鍵名

            public:
            //高級操作:
            int GetSections(CStringArray& arrSection);  //枚舉出全部的段名
            int GetKeyValues(CStringArray& arrKey,CStringArray& arrValue,LPCTSTR lpSection);  //枚舉出一段內的全部鍵名及值

            BOOL DelAllSections();

            };

            文件 2:

            #include "StdAfx.h"
            #include "ini.h"

            #define MAX_ALLSECTIONS 2048  //全部的段名
            #define MAX_SECTION 260  //一個段名長度
            #define MAX_ALLKEYS 6000  //全部的鍵名
            #define MAX_KEY 260  //一個鍵名長度

            BOOL CIni::SetFileName(LPCTSTR lpFileName)
            {
              CFile file;
              CFileStatus status;
              if(!file.GetStatus(lpFileName,status))
                return TRUE;
              m_strFileName=lpFileName;
                return FALSE;
            }

            CString CIni::GetFileName(void)
            {
              return m_strFileName;
            }

            BOOL CIni::SetValue(LPCTSTR lpSection, LPCTSTR lpKey, LPCTSTR lpValue,bool bCreate)
            {
            TCHAR lpTemp[MAX_PATH] ={0};

            //以下if語句表示如果設置bCreate為false時,當沒有這個鍵名時則返回TRUE(表示出錯)
            //!*&*none-value*&!* 這是個垃圾字符沒有特別意義,這樣亂寫是防止湊巧相同。
            if (!bCreate)
            {
              GetPrivateProfileString(lpSection,lpKey,"!*&*none-value*&!*",lpTemp,MAX_PATH,m_strFileName);
              if(strcmp(lpTemp,"!*&*none-value*&!*")==0)
              return TRUE;
            }

            if(WritePrivateProfileString(lpSection,lpKey,lpValue,m_strFileName))
              return FALSE;
            else
              return GetLastError();
            }

            CString CIni::GetValue(LPCTSTR lpSection, LPCTSTR lpKey)
            {
              DWORD dValue;
              TCHAR lpValue[MAX_PATH] ={0};

              dValue=GetPrivateProfileString(lpSection,lpKey,"",lpValue,MAX_PATH,m_strFileName);
              return lpValue;
            }

            BOOL CIni::DelSection(LPCTSTR lpSection)
            {
              if(WritePrivateProfileString(lpSection,NULL,NULL,m_strFileName))
                return FALSE;
              else
                return GetLastError();
            }

            BOOL CIni::DelKey(LPCTSTR lpSection, LPCTSTR lpKey)
            {
            if(WritePrivateProfileString(lpSection,lpKey,NULL,m_strFileName))
              return FALSE;
            else
              return GetLastError();
            }

            int CIni::GetSections(CStringArray& arrSection)
            {
            /*
            本函數基礎:
            GetPrivateProfileSectionNames - 從 ini 文件中獲得 Section 的名稱
            如果 ini 中有兩個 Section: [sec1] 和 [sec2],則返回的是 'sec1',0,'sec2',0,0 ,當你不知道  
            ini 中有哪些 section 的時候可以用這個 api 來獲取名稱 
            */
            int i;  
            int iPos=0;  
            int iMaxCount;
            TCHAR chSectionNames[MAX_ALLSECTIONS]={0}; //總的提出來的字符串
            TCHAR chSection[MAX_SECTION]={0}; //存放一個段名。
            GetPrivateProfileSectionNames(chSectionNames,MAX_ALLSECTIONS,m_strFileName);

            //以下循環,截斷到兩個連續的0
            for(i=0;i<MAX_ALLSECTIONS;i++)
            {
              if (chSectionNames[i]==0)
              if (chSectionNames[i]==chSectionNames[i+1])
                break;
            }

            iMaxCount=i+1; //要多一個0號元素。即找出全部字符串的結束部分。
            arrSection.RemoveAll();//清空原數組

            for(i=0;i<iMaxCount;i++)
            {
              chSection[iPos++]=chSectionNames[i];
              if(chSectionNames[i]==0)
              {  
              arrSection.Add(chSection);
              memset(chSection,0,MAX_SECTION);
              iPos=0;
              }
            }

            return (int)arrSection.GetSize();
            }

            int CIni::GetKeyValues(CStringArray& arrKey,CStringArray& arrValue, LPCTSTR lpSection)
            {
            /*
            本函數基礎:
            GetPrivateProfileSection- 從 ini 文件中獲得一個Section的全部鍵名及值名
            如果ini中有一個段,其下有 "段1=值1" "段2=值2",則返回的是 '段1=值1',0,'段2=值2',0,0 ,當你不知道  
            獲得一個段中的所有鍵及值可以用這個。 
            */
            int i;  
            int iPos=0;
            CString strKeyValue;
            int iMaxCount;
            TCHAR chKeyNames[MAX_ALLKEYS]={0}; //總的提出來的字符串
            TCHAR chKey[MAX_KEY]={0}; //提出來的一個鍵名

            GetPrivateProfileSection(lpSection,chKeyNames,MAX_ALLKEYS,m_strFileName);

            for(i=0;i<MAX_ALLKEYS;i++)
            {
              if (chKeyNames[i]==0)
              if (chKeyNames[i]==chKeyNames[i+1])
                break;
            }

            iMaxCount=i+1; //要多一個0號元素。即找出全部字符串的結束部分。
            arrKey.RemoveAll();//清空原數組
            arrValue.RemoveAll();

            for(i=0;i<iMaxCount;i++)
            {
              chKey[iPos++]=chKeyNames[i];
              if(chKeyNames[i]==0)
              {
              strKeyValue=chKey;
              arrKey.Add(strKeyValue.Left(strKeyValue.Find("=")));
              arrValue.Add(strKeyValue.Mid(strKeyValue.Find("=")+1));
              memset(chKey,0,MAX_KEY);
              iPos=0;
              }

            }

            return (int)arrKey.GetSize();
            }

            BOOL CIni::DelAllSections()
            {
            int nSection;
            CStringArray arrSection;
            nSection=GetSections(arrSection);
            for(int i=0;i<nSection;i++)
            {
              if(DelSection(arrSection[i]))
              return GetLastError();
            }
            return FALSE;
            }


            使用方法:
            CIni ini("c:\\a.ini");
            int n;

            /*獲得值
            TRACE("%s",ini.GetValue("段1","鍵1"));
            */

            /*添加值
            ini.SetValue("自定義段","鍵1","值");
            ini.SetValue("自定義段2","鍵1","值",false);
            */

            /*枚舉全部段名
            CStringArray arrSection;
            n=ini.GetSections(arrSection);
            for(int i=0;i<n;i++)
            TRACE("%s\n",arrSection[i]);
            */

            /*枚舉全部鍵名及值
            CStringArray arrKey,arrValue;
            n=ini.GetKeyValues(arrKey,arrValue,"段1");
            for(int i=0;i<n;i++)
            TRACE("鍵:%s\n值:%s\n",arrKey[i],arrValue[i]);
            */

            /*刪除鍵值
            ini.DelKey("段1","鍵1");
            */

            /*刪除段
            ini.DelSection("段1");
            */

            /*刪除全部
            ini.DelAllSections();
            */

            操作配置文件ini
            1.基礎知識
                INI文件(Initialization file ,又稱為初始化文件)是用來保存應用程序設置和選項的一種特殊的ASCII文件,以“.ini”作為文件擴展名,也被稱做配置文件或概要文件(Profile)。除了各個應用程序可以擁有自己私有的初始化文件外,Windows系統還提供有一個系統的初始化文件Win.ini,并由此對當前的Windows系統進行配置,同時也可以在其內記錄系統內其他應用程序在運行時的選項。

              通常為應用程序所私有的初始化文件比較小,這樣可以減少程序在初始化時所讀取的信息量,從而提高程序的啟動速度。而系統初始化文件Win.ini由于除了記錄有關系統的大量信息外,還存儲著許多其他應用軟件的初始化數據,因此其通常比較龐大,訪問的數據量要遠比私有的配置文件大得多。如沒有必要,一般不建議對Win.ini文件進行操作,但如果待存取的信息涉及到Windows系統環境或是其他應用程序時, 就必須對Win.ini進行讀寫訪問,并在訪問的同時發送WM_WININICHANGE消息給所有的頂層窗口,通知其他進程系統初始化文件已被更改。

              配置文件里的信息之所以能為系統和眾多不同類型的應用程序讀取并識別,是由于其內部對數據的存取采用了預先約定的“項-值對(Entry-value pairs)”存儲結構, 并對待存取的數據分門別類地進行存儲。下面是系統目錄下Win.ini文件的部分內容:

            [windows] 
            load= 
            run= 
            NullPort=None 
            [Desktop] 
            WallpaperStyle=2 
            Pattern=(無) 

               在此,配置文件將信息分為若干“節”,節標題放在方括號中,如“[Desktop]”就是Desktop節,在每一個節中包含了一些與之相關的“項”,并通過等號對其進行賦值。一般形式如下:

            [SECTION] 
            ENTRY=VALUE 

              在初始化文件中,VALUE值只能有兩種數據類型:數值和字符串。Windows分別為這兩種數據類型提供了兩套API函數對初始化文件進行數據讀取,在寫入初始化文件時則只支持對字符串的寫入,數值等類型必須先進行數據類型的轉換,然后才能寫入到初始化文件。私有初始化文件的訪問  對私有初始化文件的數據存取是由GetPrivateProfileInt()、GetPrivateProfileString()和WritePrivateProfileString()等三個API函數來完成的。其函數說明如下:

            UINT GetPrivateProfileInt(LPCTSTR lpAppName, // 節名地址
                                      LPCTSTR lpKeyName, // 項名地址
                                      INT nDefault,      // 在項名沒有找到時返回的缺省值
                                      LPCTSTR lpFileName // 初始化文件名地址
            );

            DWORD GetPrivateProfileString(LPCTSTR lpAppName, // 節名地址
                                          LPCTSTR lpKeyName, // 項名地址
                                          LPCTSTR lpDefault, // 缺省字符串
                                          LPTSTR lpReturnedString, // 存放字符串的緩沖區地址
                                          DWORD nSize, // 緩沖區大小
                                          LPCTSTR lpFileName // 初始化文件名地址
            );

            BOOL WritePrivateProfileString(LPCTSTR lpAppName, // 節名地址
                                           LPCTSTR lpKeyName, // 項名地址
                                           LPCTSTR lpString,  // 要寫入的字符串地址
                                           LPCTSTR lpFileName // 初始化文件名地址
            );

                其中,GetPrivateProfileInt()返回的是初始化文件lpFileName中lpAppName節內lpKeyName項的整數值,如果沒有找到該項則返回缺省值nDefault。如果此項目存在,但值不為整數,則返回0。如果某項目的值中含有非數字字符則只返回第一個非數字前的字符,例如對于“Value = 21century”則只返回數值21。初始化文件名lpFileName可以是全路徑也可以只是文件名,如果不指定具體路徑,Windows系統將在系統目錄對文件進行尋找。GetPrivateProfileString()和WritePrivateProfileString()的用法基本與之類似,只是處理對象的數據類型不同。

            2 知識應用
              私有初始化文件主要用來保存同應用程序當前狀態相關的一些信息,當程序退出后,這些信息由于已寫入到初始化文件而得以保留,當程序再次運行時,可以通過對此初始化文件各項數據的讀取而得知此應用程序在上次運行期間的相關信息。下面這段代碼即通過對私有初始化文件的訪問而對程序的運行次數和上一次的運行日期進行記錄:

            CString sPath,sMsg,sTime,sDate;
            char buffer[255];

            // 獲取當前應用程序全路徑
            GetModuleFileName(NULL, buffer, MAX_PATH);
            sPath = CString(buffer);
            sPath = sPath.Left(sPath.ReverseFind('\\'));

            // 得到初始化文件的全路徑
            sPath += "\\Sample04.ini";

            // 得到程序累計運行次數
            UINT Time = GetPrivateProfileInt("PROGRAM", "RUNTIME", 0, sPath);

            // 得到上次運行日期
            GetPrivateProfileString("DATE", "LAST", "2002-11-1", buffer, 1000, sPath);

            // 顯示從初始化文件獲取到的文件信息
            sMsg.Format("本軟件共運行過%d次,上次運行日期為%s", Time, CString(buffer));
            AfxMessageBox(sMsg);

            // 累加運行次數,并保存到初始化文件
            Time++;
            sTime.Format("%d", Time);
            WritePrivateProfileString("PROGRAM", "RUNTIME", sTime, sPath);

            // 獲取當前日期,并保存到初始化文件
            CTime tm = CTime::GetCurrentTime();
            sDate.Format("%d-%d-%d", tm.GetYear(), tm.GetMonth(), tm.GetDay());
            WritePrivateProfileString("DATE", "LAST", sDate, sPath);

            在程序執行后,初始化文件Sample04.ini的內容為:
            [DATE]
            LAST =2002-11-12
            [PROGRAM]
            RUNTIME =1

               系統目錄下的Win.ini是一種特殊的初始化文件,主要為系統提供初始化服務,在系統啟動時將被系統所訪問,并根據其所保存的參數值對系統進行配置。Windows專門提供了三個API函數GetProfileInt()、GetProfileString()和WriteProfileString()對Win.ini進行讀寫訪問,其函數用法同訪問私有初始化文件的那幾個函數非常類似,只是不必再去指定初始化文件名。下面是這三個函數的原型聲明:

            UINT GetProfileInt(LPCTSTR lpAppName, // 節名地址
                               LPCTSTR lpKeyName, // 項名地址
                               INT nDefault // 在項名沒有找到時返回的缺省值
            );

            DWORD GetProfileString(LPCTSTR lpAppName, // 節名地址
                                   LPCTSTR lpKeyName, // 項名地址
                                   LPCTSTR lpDefault, // 缺省字符串地址
                                   LPTSTR lpReturnedString, // 存放字符串的緩存的地址
                                   DWORD nSize // 緩存的大小
            );

            BOOL WriteProfileString(LPCTSTR lpAppName, // 節名地址
                                    LPCTSTR lpKeyName, // 項名地址
                                    LPCTSTR lpString   // 要寫入字符串的地址
            );

             

              只要對前面對私有初始化文件進行訪問的代碼稍加改動即可將程序的配置信息添加到Win.ini中,改動后的代碼如下:

            CString sPath,sMsg,sTime,sDate;

            // 得到程序累計運行次數
            UINT Time = GetProfileInt("PROGRAM", "RUNTIME", 0);

            // 得到上次運行日期
            GetProfileString("DATE", "LAST", "2002-11-1", buffer, 1000);

            // 顯示從初始化文件獲取到的文件信息
            sMsg.Format("本軟件共運行過%d次,上次運行日期為%s", Time, CString(buffer));
            AfxMessageBox(sMsg);

            // 累加運行次數,并保存到初始化文件
            Time++;
            sTime.Format("%d", Time);
            WriteProfileString("PROGRAM", "RUNTIME", sTime);

            // 獲取當前日期,并保存到初始化文件
            CTime tm = CTime::GetCurrentTime();
            sDate.Format("%d-%d-%d", tm.GetYear(), tm.GetMonth(), tm.GetDay());
            WriteProfileString("DATE", "LAST", sDate);

               由于Win.ini文件是系統初始化文件,在程序沒有運行前文件內不含“DATE”和“PROGRAM”等自定義的節以及其下各項,因此在程序第一次執行后,將由WriteProfileString()函數向Win.ini文件末尾創建相關節、項,并完成數據的寫入。

            posted @ 2010-09-27 18:14 叫我老王吧 閱讀(5110) | 評論 (0)編輯 收藏
            Doxygen+Graphviz 幫助
            2010-02-02 10:33
            @class 聲明一個類
            @fn 聲明一個函數
            @brief 簡要說明
            /**
            @file RenderMgr.h
            @brief 渲染管理類
            @date 3/16/2006
            @author lethean
            */
            @param 聲明一個參數
            @note 注解
            @par paragraph,自定義一個段落標題,類似html中<p>
            @code 代碼示例,需要用@endcode封閉,代碼段中不需要加前置*號

            /**
            @brief 讀取bmp文件
            @remarks 測試一下
            @param [in] dc 傳入一個dc用于臨時操作
            @param [in] filename bmp文件名
            @note 屏幕dc只能在一處被使用,請確保傳入屏幕dc后其他地方不會操作該dc
            @par 代碼示例:
            @code
               //使用
               CDC* dc = AfxGetMainWindow()->GetDC();
               Bitmap bmp( dc , "test.bmp");
               AfxGetMainWindow()->ReleaseDC( dc );
            @endcode
            */
            對于單行的前置注釋可以用///,或者/** blahblah */ 樣式
            ///我是一個變量
            int index;
            對于單行的后置注釋,需要加一個"<"符號
            int count; ///< 我也是
            /** 協議標識 */
            enum ProtoId { 
            PROTOID_LOGIN, /**< 登陸協議*/
            PROTOID_BROWSE, /**< 瀏覽協議*/
            };
            @defgroup是用來定義新模塊的
            集中在某個頭文件中定義所有的模塊,然后在屬于這個模塊的類定義處使用@ingroup
            EnginePrerequisite.h中
            /** 
            @defgroup Render Render渲染模塊
            @defgroup Resource Resource管理模塊
            @defgroup Sound Sound聲音模塊
            @defgroup Anim Anim動畫模塊
            @defgroup Math Math數學庫模塊
            @defgroup Script Script腳本模塊
            */
            RenderMgr.h中
            /**
            @class RenderMgr
            @ingroup Render
            */

            JavaDoc類型:

            /**

            * ... 程序注釋 ...

            */

            Qt類型:

            /*!

            * ... 程序注釋...

            */    

            單行型式:

            /// 這是一個簡潔型的注釋

            //! 這也是一個簡潔型的注釋

            Doxygen的這幾種基本注釋類型就可以建立足以建立文檔了,如果需要更多復雜功能,Doxygen還可以提供高級格式如組(group)和列表。

            Doxygen的常用注釋參數:

            @file

            檔案的批注說明。

            @author

            作者的信息

            @brief

            用于class function的批注中,后面為class function的簡易說明。

            @param

            格式為

            @param arg_name 參數說明

            主要用于函式說明中,后面接參數的名字,然后再接關于該參數的說明。

            @return

            后面接函數傳回值的說明。用于function的批注中。說明該函數的傳回值。

            @retval

            格式為

            @retval value 傳回值說明

            主要用于函式說明中,說明特定傳回值的意義。所以后面要先接一個傳回值。然后在放該傳回值的說明。

            Doxygen的使用,如下:

            E:\>xstring目錄包含下列文件:

            Xstring.cpp

            Xstring.h

            其中xstring.h里對xstring類的StrToInt方法的注釋如下:

                   /**

                   *

            0. 序言
            為代碼寫注釋一直是大多數程序員有些困擾的事情。當前程序員都能接受為了程序的可維護性、可讀性編碼的同時寫注釋的說法,但對哪些地方應該寫注釋,注釋如何寫,寫多少等這些問題,很多程序員仍然沒有答案。更頭痛的是寫文檔,以及維護文檔的問題,開發人員通常可以忍受編寫或者改動代碼時編寫或者修改對應的注釋,但之后需要修正相應的文檔卻比較困難。如果能從注釋直接轉化成文檔,對開發人員無疑是一種福音。而doxygen就能把遵守某種格式的注釋自動轉化為對應的文檔。

            Doxygen是基于GPL的開源項目,是一個非常優秀的文檔系統,當前支持在大多數unix(包括linux),windows家族,Mac系統上運行,完全支持C++, C, Java, IDL(Corba和Microsoft 家族)語言,部分支持PHP和C#語言,輸出格式包括HTML、latex、RTF、ps、PDF、壓縮的HTML和unix manpage。有很多開源項目(包括前兩篇文章介紹的log4cpp和CppUnit)都使用了doxygen文檔系統。而國內的開發人員卻使用的不多,這里從開發人員使用的角度介紹這個工具,使開發人員用最少的代價盡快掌握這種技術,并結合這個工具探討如何撰寫注釋的問題。以下以linux下的C++語言為例進行介紹,以下討論基于doxygen1.3.3。

            1. doxygen使用步驟
            由于只是工具的使用,這里不介紹它的原理,直接從使用步驟開始。Doxygen的使用步驟非常簡單。主要可以分為:
            1)第一次使用需要安裝doxygen的程序
            2)生成doxygen配置文件
            3)編碼時,按照某種格式編寫注釋
            4)生成對應文檔
            doxygen的安裝非常簡單, linux下可以直接下載安裝包運行即可,下載源代碼編譯安裝也是比較通用的編譯安裝命令。請參考其安裝文檔完成安裝。

            Doxygen在生成文檔時可以定義項目屬性以及文檔生成過程中的很多選項,使用下面命令能夠產生一個缺省的配置文件:
            doxygen -g [配置文件名]
            可以根據項目的具體需求修改配置文件中對應的項,具體的修改過程在下面介紹。修改過的配置文件可以作為以后項目的模板。

            讓doxygen自動產生文檔,平常的注釋風格可不行,需要遵循doxygen自己的格式。具體如何寫doxygen認識的注釋在第3節詳細介紹。

            OK,代碼編完了,注釋也按照格式寫好了,最后的文檔是如何的哪?非常簡單,運行下面的命令,相應的文檔就會產生在指定的目錄中。
               doxygen [配置文件名]

            需要注意的是doxygen并不處理所有的注釋,doxygen重點關注與程序結構有關的注釋,比如:文件、類、結構、函數、變量、宏等注釋,而忽略函數內變量、代碼等的注釋。

            2. doxygen配置文件
            doxygen配置文件的格式是也是通常的unix下配置文件的格式:注釋'#'開始;tag = value [,value2…];對于多值的情況可以使用 tag += value [,value2…]。

            對doxygen的配置文件的修改分為兩類:一種就是輸出選項,控制如何解釋源代碼、如何輸出;一種就是項目相關的信息,比如項目名稱、源代碼目錄、輸出文檔目錄等。對于第一種設置好后,通常所有項目可以共用一份配置,而后一種是每個項目必須設置的。下面選擇重要的,有可能需要修改的選項進行解釋說明,其他選項在配置文件都有詳細解釋。

            TAG 缺省值 含義
            PROJECT_NAME   項目名稱
            PROJECT_NUMBER   可以理解為版本信息
            OUTPUT_DIRECTORY   輸出文件到的目錄,相對目錄(doxygen運行目錄)或者絕對目錄
            INPUT   代碼文件或者代碼所在目錄,使用空格分割
            FILE_PATTERNS *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx *.hpp *.h++ *.idl *.odl 指定INPUT的目錄中特定文件,如:*.cpp *.c *.h 
            RECURSIVE NO 是否遞歸INPUT中目錄的子目錄
            EXCLUDE   在INPUT目錄中需要忽略的子目錄
            EXCLUDE_PATTERNS   明確指定的在INPUT目錄中需要忽略的文件,如:FromOut*.cpp
              
            OUTPUT_LANGUAGE English 生成文檔的語言,當前支持2、30種語言,國內用戶可以設置為Chinese
            USE_WINDOWS_ENCODING YES(win版本)
            NO(unix版本) 編碼格式,默認即可。
            EXTRACT_ALL NO 為NO,只解釋有doxygen格式注釋的代碼;為YES,解析所有代碼,即使沒有注釋。類的私有成員和所有的靜態項由EXTRACT_PRIVATE和 EXTRACT_STATIC控制
            EXTRACT_PRIVATE NO 是否解析類的私有成員
            EXTRACT_STATIC NO 是否解析靜態項
            EXTRACT_LOCAL_CLASSES YES 是否解析源文件(cpp文件)中定義的類
            SOURCE_BROWSER NO 如果為YES,源代碼文件會被包含在文檔中
            INLINE_SOURCES NO 如果為YES,函數和類的實現代碼被包含在文檔中
            ALPHABETICAL_INDEX NO 生成一個字母序的列表,有很多類、結構等項時建議設為YES
            GENERATE_HTML YES 是否生成HTML格式文檔
            GENERATE_HTMLHELP NO 是否生成壓縮HTML格式文檔(.chm)
            GENERATE_LATEX YES 是否乘車latex格式的文檔
            GENERATE_RTF NO 是否生成RTF格式的文檔
            GENERATE_MAN NO 是否生成man格式文檔
            GENERATE_XML NO 是否生成XML格式文檔
              

            3. doxygen注釋
            3.1 注釋風格
            下面是工作量最大部分,安裝doxygen格式寫注釋。通常代碼可以附上一個注釋塊來對代碼進行解釋,一個注釋塊由一行或者多行組成。通常一個注釋塊包括一個簡要說明(brief)和一個詳細說明(detailed),這兩部分都是可選的。可以有多種方式標識出doxygen可識別的注釋塊。
            1)JavaDoc類型的多行注釋。
            /**
            * ….text….
            */
            2)QT樣式的多行注釋。
            /*!
            ….text….
            */
            3) /// …text….
            4) //! …text….
            簡要說明有多種方式標識,這里推薦使用@brief命令強制說明,例如:
            /**
            * @brief [some brief description ]
            *      [ brief description more. ]

            * [some more detailed description…]
            */
            以上這些注釋格式用來對緊跟其后的代碼進行注釋。doxygen也允許把注釋放到代碼后面,具體格式是放一個'<'到注釋開始部分。例如:
            int var1 ; /**< ….text…. */
            int var2; ///< ….text….

            注釋和代碼完全分離,放在其他地方也是允許的,但需要使用特殊的命令加上名稱或者聲明進行標識,比如:class、struct、union、enum、fn、var、def、file、namespace、package、interface(這些也就是doxygen關注的注釋類型)。這里不推薦使用,建議注釋盡量放在代碼前后。具體使用方式參見doxygen手冊。

            3.2 doxygen常用注釋格式
            通常的選擇上面的一、兩種注釋風格,遇到頭文件中各種類型定義,關鍵變量、宏的定義,在其前或者后使用 @brief 定義其簡要說明,空一行后繼續寫其詳細的注釋即可。

            對函數的注釋,是比較常常需要注釋的部分。除了定義其簡要說明以及詳細注釋,還可以使用param命令對其各個參數進行注釋,使用return命令對返回值進行注釋。常見的格式如下:
            /**
            *@brief func's brief comment.
            *
            * Some detailed comment.
            *@param a [param a 's comment.]
            *@param b [param b 's comment.]
            *@exception std::out_of_range [exception's comment.]
            *@return [return's comment.]
            */
            int func1(int a, int b);

            進行設計時,通常有模塊的概念,一個模塊可能有多個類或者函數組成,完成某個特定功能的代碼的集合。如何對這個概念進行注釋?doxygen提供了group的概念,生成的模塊的注釋會單獨放在一個模塊的頁面中。使用下面的格式定義一個group。
            /** [group_name] [brief group description ]
            * detailed group description ]
            * @{
            */
            code
            /** @} */
            group中的代碼可以有自己的注釋。單純定義一個模塊,去除{ 和}命令即可。任何其他代碼項(比如類、函數、甚至文件)如果要加入到某個模塊,可以在其doxygen注釋中使用ingroup命令即可。Group之間使用ingroup命令,可以組成樹狀關系。
            /** @file util.cpp 
            * @ingroup [group_name]
            * @brief file's brief info.
            */
            把多個代碼項一起添加到某個模塊中可以使用addtogroup命令,格式和defgroup相似。

            對于某幾個功能類似的代碼項(比如類、函數、變量)等,如果希望一起添加注釋,而又不想提升到模塊的概念,可以通過下面的方式:
            //@{
            /** Comments for all below code. */
            code…
            //@}
            對這種組進行命名可以使用name命令。此時中間代碼可以有自己的注釋。如:
            /** @name group_name
            * description for group.
            */
            //@{
            code…
            //@}

            3.3 doxygen常用注釋命令
            doxygen通過注釋命令識別注釋中需要特殊處理的注釋,比如函數的參數、返回值進行突出顯示。上面也提到了一些注釋命令(如:brief、param、return、以及group相關的命令),下面對其他一些常用的注釋命令進行解釋說明。
            @exception <exception-object> {exception description} 對一個異常對象進行注釋。
            @warning {warning message } 一些需要注意的事情
            @todo { things to be done } 對將要做的事情進行注釋
            @see {comment with reference to other items } 一段包含其他部分引用的注釋,中間包含對其他代碼項的名稱,自動產生對其的引用鏈接。
            @relates <name> 通常用做把非成員函數的注釋文檔包含在類的說明文檔中。
            @since {text} 通常用來說明從什么版本、時間寫此部分代碼。
            @deprecated
            @pre { description of the precondition } 用來說明代碼項的前提條件。
            @post { description of the postcondition } 用來說明代碼項之后的使用條件。
            @code 在注釋中開始說明一段代碼,直到@endcode命令。
            @endcode 注釋中代碼段的結束。

            到此為止,常用的doxygen的注釋格式討論完畢,我們能夠按照一定的格式撰寫doxygen認識的注釋,并能夠使用doxygen方便快捷的生成對應的文檔,不過注釋中應該寫些什么,如何撰寫有效的注釋可能是困擾開發人員的一個更深層次的問題。

            4. 注釋的書寫
            注釋應該怎么寫,寫多還是寫少。過多的注釋甚至會干擾對代碼的閱讀。寫注釋的一個總的原則就是注釋應該盡量用來表明作者的意圖,至少也應該是對一部分代碼的總結,而不應該是對代碼的重復或者解釋。對代碼的重復或者解釋的代碼,看代碼可能更容易理解。反映作者意圖的注釋解釋代碼的目的,從解決問題的層次上進行注釋,而代碼總結性注釋則是從問題的解答的層次上進行注釋。

            推薦的寫注釋的過程是首先使用注釋勾勒出代碼的主要框架,然后根據注釋撰寫相應的代碼。對各種主要的數據結構、輸出的函數、多個函數公用的變量進行詳細地注釋。對代碼中控制結構,單一目的的語句集進行注釋。下面是一些寫注釋時需要注意的要點:
               避免對單獨語句進行注釋;
               通過注釋解釋為什么這么做、或者要做什么,使代碼的讀者可以只閱讀注釋理解代碼;
               對讀者可能會有疑問的地方進行注釋;
               對數據定義進行注釋,而不是對其使用過程進行注釋;
               對于難于理解的代碼,進行改寫,而不要試圖通過注釋加以說明;
               對關鍵的控制結構進行注釋;
               對數據和函數的邊界、使用前提等進行注釋;

                  雖然使用各種IDE或者Source Insight 可以方便地在windows下閱讀和分析C/C++代碼,但是一步步Go to Definetion 實在令人痛苦。Doxygen能夠生成函數調用關系圖,所有的函數調用關系可以一目了然,另外他還能統計文檔中所有的類,成員變量,成員函數等。總的來說,Doxygen不但能從局部把握代碼,還能從全局審視代碼,后者是一般IDE和Source Insight 不能做到的。所以,使用doxygen閱讀分析代碼可以達到事半功倍的效果。

                      doxygen的安裝很簡單,到官方網站下載doxygen的windows安裝程序即可。除了安裝doxygen外,還需要安裝graphviz,因為doxygen需要使用graphviz的dot.exe生成調用圖。graphviz在它的官方網站上也可以下載到安裝程序。不過我下載的安裝程序在安裝時老是報cab文件錯誤。最后我下載了graphviz的release文件,即安裝程序下面的zip壓縮文件,下載完成后解壓縮即可。

                     安裝完doxygen后即可進行適當的配置,然后運行doxyfile生成文檔。基本的配置前人早有說明,不再贅述。需要注意的是:

                    1.要勾選Dot選項卡下面的HAVE_DOT、CALL_GRAPH 、CALLER_GRAPH 選項。并在DOT_PATH下面填入dot.exe的路徑,也就是graphviz安裝目錄下的bin文件夾。

                    2.勾選Wizard->Project選項卡下面的Scan recursively!

                    3.勾選Build選項卡下面的EXTRACT_ALL、EXTRACT_PRIVATE、EXTRACT_STATIC、EXTRACT_LOCAL_CLASSES、EXTRACT_LOCAL_METHODS選項

                    4.如果程序里面有中文,將Project選項卡下的DOXYFILE_ENCODING和Input選項卡下面的INPUT_ENCODING改為GBK

                   5.可以勾掉LATEX輸出,這樣節省編譯時間

            5. 參考資料
            1. doxygen homepage
            http://www.stack.nl/~dimitri/doxygen/

            2. doxygen manual 
            http://www.stack.nl/~dimitri/doxygen/manual.html

            3. Code Complete: A Practical Handbook of Software Construction. Redmond, Wa.: Microsoft Press, 880 pages, 1993. ISBN: 1-55615-484-4. 

            4. 簡介doxygen
            http://www.stack.nl/~dimitri/doxygen/doxygen_intro_cn.html

            5. 10 Minutes to document your code
            http://www.codeproject.com/tips/doxysetup.asp

            6. 使用doxygen
            http://www.csdn.net/Develop/article/16%5C16383.shtm

            posted @ 2010-09-08 15:45 叫我老王吧 閱讀(4237) | 評論 (0)編輯 收藏

            DOXYGEN簡明實用教程

            Posted on 2009-03-04 21:31 活著就是幸福 閱讀(507) 評論(0) 編輯 收藏 所屬分類: Project Management

            代碼寫多了難免需要做文檔,給自己還是給別人看都需要如此,這次XBOX360制作,前期沒怎么寫注釋,回頭改Bug都要猜半天自己寫的代碼是什么意思。更別提別人寫的東西,100行代碼也沒有一句注釋,幸好不是我維護,否則要瘋掉了。

            花了一天功夫嘗試了一下Doxygen的使用,還好不難,但是有些磕磕絆絆,它自己的文檔也說不清楚,網上搜出來的教程也只是給出樣子,遇到的問題還是靠自己嘗試了幾十次才搞定。

            不管如何,常用的東西都可以弄出來了。貼在下面:

             -----------------------------------------------------------------------------------

            1.所有注釋都可以使用///開始(C++風格)。

            2.類體前必須加上///描述,否則會產生警告【Compound 類名 is not documented】
              描述中最好不要帶有此類的類名,否則會產生兩個鏈接(但指向同一個文件)影響美觀。

            3.public和protected會自動生成,但是private要在Expert的Build選項里勾中EXTRACT_PRIVATE,static成員也是如此。

            4.函數注釋方式
                /// Constructor【函數描述】
                /// @param [in] pos       The position of Camera in world coordinate         【參數描述1】
                /// @param [in] lookat    The point Camera looks at in world coordinate    【參數描述2】
                BaseCamera( const D3DXVECTOR3& pos, const D3DXVECTOR3& lookat );

            5.變量注釋方式
                D3DXVECTOR3 m_Position;    /*!< Camera position point in world coordinate */   或
                D3DXVECTOR3 m_Position;    ///< Camera position point in world coordinate
            兩種方式產生的結果不同。前者會單獨產生一塊Member Data Documentation注釋,后者會在Pubilc/Protected/Private Attributes變量描述后緊跟注釋。

            6.@參數和\參數相同

            7.產生描述順序和注釋順序相同,一般風格為

                /// 函數描述
                /// @param     參數描述
                /// @return     返回值描述
                /// @retval     返回值1     返回值1描述
                /// @retval     返回值2     返回值2描述
                /// @remarks     注意事項
                /// @note    注意事項,功能同@remarks,顯示字樣不同
                /// @par    自定義圖塊,后面可跟示例代碼之類
                /// @code(必須使用@endcode結束)
                /// 示例代碼(無需縮進)    
                /// @endcode
                /// @see     其他參考項【產生指向參考的鏈接】
                函數代碼聲明

            8.特殊符號
                /// -        產生一個黑色圓點

            9.定義在類體里面的enum
                /// Camera types
                enum CAMERA_TYPE
                {
                    CAMERA_FIRST_VIEW,/*!< Camera that looks from the first view */
                    CAMERA_MODEL_VIEW,///< Camera that looks from the third view
                };
                兩種風格相同。

            以下開始的項都是全局非類內定義,在文件最開始(我嘗試是在include之前) 必須加上【/// \file 文件名】,否則不會生成注釋【沒有File Member頁】。

            10. 定義在文件里面的宏
                 #define CAMERA_TYPE_NUMBER     ///< The number of camera types.       或
                 #define CAMERA_TYPE_NUMBER     /*!< The number of camera types. */
            風格說明見5。

            11. 非類內enum定義同10.        兩種風格相同。見9。
            12. 非類內typedef定義同10.     風格說明見5。

             

            posted @ 2010-09-08 15:15 叫我老王吧 閱讀(1423) | 評論 (2)編輯 收藏
             1GtkTreeIter iter;//定義一個迭代器
             2    GtkTreeModel *pmodel;
             3    char* pitem;
             4    pmodel=gtk_tree_view_get_model(GTK_TREE_VIEW1));//得到TREEVIEW的model
             5    gtk_tree_model_get_iter_first(pmodel,&iter);//迭代器定位到第一行
             6    int columns=gtk_tree_model_get_n_columns(pmodel);//獲取行數
             7    
             8    //獲取列名
             9    for(int i=0;i<columns;i++)
            10    {
            11        GtkTreeViewColumn *column = gtk_tree_view_get_column(GTK_TREE_VIEW(control),i);//遍歷header
            12        string coltitle(__(gtk_tree_view_column_get_title(column),1));
            13        cout<<coltitle;//輸出列名
            14    }
                        
            15        
            16    do//開始遍歷內容
            17    {
            18        for(int i=0;i<columns;i++)
            19            {
            20                gtk_tree_model_get (GTK_TREE_MODEL(pmodel),&iter,
            21                i,        &pitem,
            22                -1);//將指定單元格的值傳給pitem
            23                try
            24                {
            25                    cout<<pitem;//輸出單元格的值
            26                }
            catch{}
            27            }

            28    }
            while(gtk_tree_model_iter_next(pmodel,&iter));
            唉,網上找了半天,沒找到思路,只有自己慢慢啃了,寫完發一下,給需要的朋友一些借鑒

            posted @ 2010-06-25 11:57 叫我老王吧 閱讀(3945) | 評論 (0)編輯 收藏
             
            這一部分講述如何繪制一些簡單的圖元,包括直線、填充與筆畫操作、虛線、線端(Cap)與線的交合等圖形的繪制方法。
            直線段
            直線段是非常基礎的矢量圖形對象。畫一條直線段,需要調用兩個函數:cairo_move_to() 函數,用于設置線段起點;cairo_line_to() 用于設定線段終點。
            #include
            #include
            double coordx[100];
            double coordy[100];
            int count = 0;
            static gboolean
            on_expose_event(GtkWidget *widget,
                            GdkEventExpose *event,
                            gpointer data)
            {
                    cairo_t *cr;
                    
                    cr = gdk_cairo_create(widget->window);
                    
                    cairo_set_source_rgb(cr, 0, 0, 0);
                    cairo_set_line_width (cr, 0.5);
                    
                    int i, j;
                    for ( i = 0; i 1; i++ ) {
                            for ( j  = 0; j -1; j++ ) {
                                    cairo_move_to(cr, coordx, coordy);
                                    cairo_line_to(cr, coordx[j], coordy[j]);
                            }
                    }
                    
                    count = 0;
                    cairo_stroke(cr);
                    cairo_destroy(cr);
                    
                    return FALSE;
            }
            gboolean clicked(GtkWidget *widget, GdkEventButton *event,
                             gpointer user_data)
            {
                    if (event->button == 1) {
                            coordx[count] = event->x;
                            coordy[count++] = event->y;
                    }
                    
                    if (event->button == 3) {
                            gtk_widget_queue_draw(widget);
                    }
                    
                    return TRUE;
            }
            int
            main (int argc, char *argv[])
            {
                    
                    GtkWidget *window;
                    
                    gtk_init(&argc, &argv);
                    
                    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
                    
                    gtk_widget_add_events (window, GDK_BUTTON_PRESS_MASK);
                    
                    g_signal_connect(window, "expose-event",
                                     G_CALLBACK(on_expose_event), NULL);
                    g_signal_connect(window, "destroy",
                                     G_CALLBACK(gtk_main_quit), NULL);
                    g_signal_connect(window, "button-press-event",
                                     G_CALLBACK(clicked), NULL);
                    
                    gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
                    gtk_window_set_title(GTK_WINDOW(window), "lines");
                    gtk_window_set_default_size(GTK_WINDOW(window), 400, 300);
                    gtk_widget_set_app_paintable(window, TRUE);
                    
                    gtk_widget_show_all(window);
                    
                    gtk_main();
                    
                    return 0;
            }
            該示例會創建一個支持鼠標交互繪制直線段的 GTK+ 窗口。在窗口中使用鼠標左鍵隨便點幾下,每一次點擊時,光標位置的坐標都會被記入長度為 100 的數組;然后點擊鼠標右鍵,所有由鼠標左鍵點擊所得到的點會被彼此連接形成直線段;在窗口中再次點擊鼠標右鍵時,會對窗口繪圖區域進行清除。
            下面對該示例程序代碼進行分析:
                    cairo_set_source_rgb(cr, 0, 0, 0);
                    cairo_set_line_width (cr, 0.5);
            設置顏色為黑色,線寬為 0.5pt 為參數,繪制直線段。
                    int i, j;
                    for ( i = 0; i 1; i++ ) {
                            for ( j  = 0; j -1; j++ ) {
                                    cairo_move_to(cr, coordx, coordy);
                                    cairo_line_to(cr, coordx[j], coordy[j]);
                            }
                    }
            用 cairo_move_to() 和 cairo_line_to() 函數在 cr 中定義繪圖路徑 (path),連接 coordx[] 和 coordy[] 所記錄的每個點。
                    cairo_stroke(cr);
            cairo_stroke() 函數會將 cr 中的路徑繪制出來。
                    g_signal_connect(window, "button-press-event",
                                     G_CALLBACK(clicked), NULL);
            設定 button-press-event 事件的回調函數為 clicked ()。
                    if (event->button == 1) {
                            coordx[count] = event->x;
                            coordy[count++] = event->y;
                    }
            在 clicked () 函數中,當鼠標左鍵點擊事件發生時,講光標所在位置的 x 和 y 坐標分別記入數組 coordx 和 coordy。
                    if (event->button == 3) {
                            gtk_widget_queue_draw(widget);
                    }
            在 clicked () 函數中,當鼠標右鍵單擊時,調用 gtk_widget_queue_draw () 函數重繪窗口區域。

            描繪 (Stroke) 與填充 (Fill)
            描繪 (Stroke) 可以繪制形狀的輪廓,填充 (Fill) 則用于向形狀內部灌注顏色。
            #include
            #include
            #include
            static gboolean
            on_expose_event (GtkWidget * widget,
                             GdkEventExpose * event, gpointer data)
            {
                    cairo_t *cr;
                    cr = gdk_cairo_create (widget->window);
                    int width, height;
                    gtk_window_get_size (GTK_WINDOW (widget), &width, &height);
                    cairo_set_line_width (cr, 9);
                    cairo_set_source_rgb (cr, 0.69, 0.19, 0);
                    cairo_arc (cr, width / 2, height / 2,
                               (width ) / 2 - 10, 0,
                               2 * M_PI);
                    cairo_stroke_preserve (cr);
                    cairo_set_source_rgb (cr, 0.3, 0.4, 0.6);
                    cairo_fill (cr);
                    cairo_destroy (cr);
                    return FALSE;
            }
            int
            main (int argc, char *argv[])
            {
                    GtkWidget *window;
                    gtk_init (&argc, &argv);
                    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
                    g_signal_connect (G_OBJECT (window), "expose-event",
                                      G_CALLBACK (on_expose_event), NULL);
                    g_signal_connect (G_OBJECT (window), "destroy",
                                      G_CALLBACK (gtk_main_quit), NULL);
                    gtk_window_set_position (GTK_WINDOW (window),
                                             GTK_WIN_POS_CENTER);
                    gtk_window_set_default_size (GTK_WINDOW (window), 200, 150);
                    gtk_widget_set_app_paintable (window, TRUE);
                    gtk_widget_show_all (window);
                    gtk_main ();
                    return 0;
            }
            這個示例繪制一個內部填充灰色的圓。
            下面對代碼進行解析:
            #include
            之所以引入這個頭文件,是因為程序中使用了圓周率常量 M_PI。
                    int width, height;
                    gtk_window_get_size (GTK_WINDOW (widget), &width, &height);
            獲取窗口的寬度與高度尺寸。程序中將使用這些值作為繪制圓形的參考尺寸,以實現窗口尺寸變化時,所繪制的圓的尺寸也會相應變化。
                    cairo_set_source_rgb (cr, 0.69, 0.19, 0);
                    cairo_arc (cr, width / 2, height / 2,
                               (width ) / 2 - 10, 0,
                               2 * M_PI);
                    cairo_stroke_preserve (cr);
            描繪圓的輪廓。這里要注意一下 cairo_stroke_preserve () 函數與 cairo_stroke () 函數的區別(最好的辦法是用后者替換一下前者,看看程序執行效果)。cairo_stroke_preserve () 函數會將它繪制的路徑依然保存在 cairo 環境中,而 cairo_stroke () 所繪制的路徑,在繪制完成后,就從 cairo的環境中清除了。
                    cairo_set_source_rgb (cr, 0.3, 0.4, 0.6);
                    cairo_fill (cr);
            對使用 cairo_stroke_preserve () 函數繪制的路徑進行藍色填充。

            虛線 (Dash)
            每條線都可以用不同的虛線筆 (dash pen) 來畫。虛線模式是通過 cairo_set_dash () 函數來設定。模式類型通過一個數組來定義,數組中的值均為正數,它們用于設置虛線的虛部分與實部分。數組的長度與偏移量可以在程序中設定。如果數組的長度 為 0,虛線模式就是被禁止了,那所繪制的線是實線。如果數組長度為 1,則對應著虛實均勻分布的虛線模式。偏移量是用來設置在虛線的始端在一個虛線周期(包含一個實部單元和一個虛部單元)內的起始位置。
            #include
            #include
            static gboolean
            on_expose_event (GtkWidget * widget,
                             GdkEventExpose * event, gpointer data)
            {
                    cairo_t *cr;
                    cr = gdk_cairo_create (widget->window);
                    cairo_set_source_rgba (cr, 0, 0, 0, 1);
                    static const double dashed1[] = { 4.0, 1.0 };
                    static int len1 = sizeof (dashed1) / sizeof (dashed1[0]);
                    static const double dashed2[] = { 4.0, 10.0, 4.0 };
                    static int len2 = sizeof (dashed2) / sizeof (dashed2[0]);
                    static const double dashed3[] = { 1.0 };
                    cairo_set_line_width (cr, 1.5);
                    cairo_set_dash (cr, dashed1, len1, 0);
                    cairo_move_to (cr, 40, 60);
                    cairo_line_to (cr, 360, 60);
                    cairo_stroke (cr);
                    cairo_set_dash (cr, dashed2, len2, 10);
                    cairo_move_to (cr, 40, 120);
                    cairo_line_to (cr, 360, 120);
                    cairo_stroke (cr);
                    cairo_set_dash (cr, dashed3, 1, 0);
                    cairo_move_to (cr, 40, 180);
                    cairo_line_to (cr, 360, 180);
                    cairo_stroke (cr);
                    cairo_destroy (cr);
                    return FALSE;
            }
            int
            main (int argc, char *argv[])
            {
                    GtkWidget *window;
                    GtkWidget *darea;
                    gtk_init (&argc, &argv);
                    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
                    darea = gtk_drawing_area_new ();
                    gtk_container_add (GTK_CONTAINER (window), darea);
                    g_signal_connect (darea, "expose-event",
                                      G_CALLBACK (on_expose_event), NULL);
                    g_signal_connect (window, "destroy",
                                      G_CALLBACK (gtk_main_quit), NULL);
                    gtk_window_set_position (GTK_WINDOW (window),
                                             GTK_WIN_POS_CENTER);
                    gtk_window_set_default_size (GTK_WINDOW (window), 400, 300);
                    gtk_widget_show_all (window);
                    gtk_main ();
                    return 0;
            }
            該示例演示了三種虛線模式的設置及繪制。
            下面分析一下關鍵代碼。
                    static const double dashed1[] = { 4.0, 1.0 };
            設定第一條虛線的模式,它的實部是 4 個像素,虛部是 1 個像素。
                    static int len1 = sizeof (dashed1) / sizeof (dashed1[0]);
            計算數組 dashed1 的長度。
                    cairo_set_dash (cr, dashed1, len1, 0);
            設置虛線模式。
                    darea = gtk_drawing_area_new ();
                    gtk_container_add (GTK_CONTAINER (window), darea);
            這次,我們是在 drawing_area 部件上繪圖,不再是窗口區域了。

            線帽 (Line caps)
            線帽是針對直線段的端點形狀而言的,分為三種:

            • CAIRO_LINE_CAP_SQUARE
            • CAIRO_LINE_CAP_ROUND
            • CAIRO_LINE_CAP_BUTT

            對應形狀如下圖所示:

            同一條直線段,CAIRO_LINE_CAP_SQUARE 線帽與 CAIRO_LINE_CAP_BUTT 線帽會導致直線段長度有所差別,前者會比后者長一個線寬尺寸。
            #include
            #include
            static gboolean
            on_expose_event (GtkWidget * widget,
                             GdkEventExpose * event, gpointer data)
            {
                    cairo_t *cr;
                    cr = gdk_cairo_create (widget->window);
                    cairo_set_source_rgba (cr, 0, 0, 0, 1);
                    cairo_set_line_width (cr, 10);
                    cairo_set_line_cap (cr, CAIRO_LINE_CAP_BUTT);
                    cairo_move_to (cr, 40, 60);
                    cairo_line_to (cr, 360, 60);
                    cairo_stroke (cr);
                    cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
                    cairo_move_to (cr, 40, 150);
                    cairo_line_to (cr, 360, 150);
                    cairo_stroke (cr);
                    cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
                    cairo_move_to (cr, 40, 240);
                    cairo_line_to (cr, 360, 240);
                    cairo_stroke (cr);
                    cairo_set_line_width (cr, 1.5);
                    cairo_move_to (cr, 40, 40);
                    cairo_line_to (cr, 40, 260);
                    cairo_stroke (cr);
                    cairo_move_to (cr, 360, 40);
                    cairo_line_to (cr, 360, 260);
                    cairo_stroke (cr);
                    cairo_move_to (cr, 365, 40);
                    cairo_line_to (cr, 365, 260);
                    cairo_stroke (cr);
                    cairo_destroy (cr);
                    return FALSE;
            }
            該示例繪制三條具有不同線帽的直線段,同時也展示了不同線帽對線的長度的影響。
            下面對關鍵代碼進行簡單分析:
                    cairo_set_line_width (cr, 10);
            設置線的寬度為 10px。
                    cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
                    cairo_move_to (cr, 40, 150);
                    cairo_line_to (cr, 360, 150);
                    cairo_stroke (cr);
            畫了一條線帽為 CAIRO_LINE_CAP_ROUND 的直線段。
                    cairo_move_to (cr, 40, 40);
                    cairo_line_to (cr, 40, 260);
                    cairo_stroke (cr);
            這是三條豎線之一,用于表現線帽對線的長度的影響。

            線的交合 (Line joins)
            線的交合存在以下三種風格:

            • CAIRO_LINE_JOIN_MITER
            • CAIRO_LINE_JOIN_BEVEL
            • CAIRO_LINE_JOIN_ROUND

            對應形狀如下圖所示。

            #include
            #include
            static gboolean
            on_expose_event (GtkWidget * widget,
                             GdkEventExpose * event, gpointer data)
            {
                    cairo_t *cr;
                    cr = gdk_cairo_create (widget->window);
                    cairo_set_source_rgb (cr, 0.1, 0, 0);
                    cairo_rectangle (cr, 30, 30, 100, 100);
                    cairo_set_line_width (cr, 14);
                    cairo_set_line_join (cr, CAIRO_LINE_JOIN_MITER);
                    cairo_stroke (cr);
                    cairo_rectangle (cr, 160, 30, 100, 100);
                    cairo_set_line_width (cr, 14);
                    cairo_set_line_join (cr, CAIRO_LINE_JOIN_BEVEL);
                    cairo_stroke (cr);
                    cairo_rectangle (cr, 100, 160, 100, 100);
                    cairo_set_line_width (cr, 14);
                    cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND);
                    cairo_stroke (cr);
                    cairo_destroy (cr);
                    return FALSE;
            }
            int
            main (int argc, char *argv[])
            {
                    GtkWidget *window;
                    GtkWidget *darea;
                    gtk_init (&argc, &argv);
                    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
                    darea = gtk_drawing_area_new ();
                    gtk_container_add (GTK_CONTAINER (window), darea);
                    g_signal_connect (darea, "expose-event",
                                      G_CALLBACK (on_expose_event), NULL);
                    g_signal_connect (window, "destroy",
                                      G_CALLBACK (gtk_main_quit), NULL);
                    gtk_window_set_position (GTK_WINDOW (window),
                                             GTK_WIN_POS_CENTER);
                    gtk_window_set_default_size (GTK_WINDOW (window), 300, 280);
                    gtk_widget_show_all (window);
                    gtk_main ();
                    return 0;
            }

            該示例采用不同的交合類型繪制了三個矩形。
            下面對關鍵代碼進行簡單分析:
                    cairo_rectangle (cr, 30, 30, 100, 100);
                    cairo_set_line_width (cr, 14);
                    cairo_set_line_join (cr, CAIRO_LINE_JOIN_MITER);
                    cairo_stroke (cr);
            繪制了一個線寬為 14px,交合類型為 CAIRO_LINE_JOIN_MITER 的矩形。



            本文來自ChinaUnix博客,如果查看原文請點:
            posted @ 2010-04-08 14:15 叫我老王吧 閱讀(1468) | 評論 (0)編輯 收藏
            僅列出標題
            共2頁: 1 2 
            <2011年2月>
            303112345
            6789101112
            13141516171819
            20212223242526
            272812345
            6789101112

            常用鏈接

            留言簿(4)

            隨筆分類

            隨筆檔案

            搜索

            •  

            最新評論

            閱讀排行榜

            評論排行榜

            精品久久久久久无码专区| 久久人妻少妇嫩草AV无码蜜桃| 久久久免费精品re6| 久久夜色精品国产亚洲| 久久久久久国产精品免费免费| 久久中文字幕人妻熟av女| 国产亚洲婷婷香蕉久久精品| 久久久久久亚洲精品不卡 | 欧美777精品久久久久网| 亚洲精品99久久久久中文字幕| 国内精品久久久久久久久电影网| 91久久精品无码一区二区毛片| 久久亚洲AV无码精品色午夜| 日本福利片国产午夜久久| 精产国品久久一二三产区区别| 欧美日韩中文字幕久久伊人| 少妇熟女久久综合网色欲| 国产叼嘿久久精品久久| 国产精品女同久久久久电影院 | 久久久久久午夜成人影院| 亚洲精品NV久久久久久久久久| 婷婷久久综合九色综合98| 少妇精品久久久一区二区三区| 久久婷婷人人澡人人| 91久久成人免费| 久久国产色AV免费观看| 亚洲精品无码久久久久AV麻豆| 久久国产成人午夜aⅴ影院 | 国内精品久久久久久久久电影网| 亚洲综合日韩久久成人AV| 亚洲精品国精品久久99热| 99热成人精品免费久久| 丰满少妇人妻久久久久久| 日产精品久久久一区二区| 久久国产欧美日韩精品免费| 国产香蕉97碰碰久久人人| 狠狠狠色丁香婷婷综合久久俺| 精品精品国产自在久久高清| 久久精品中文騷妇女内射| 97久久精品午夜一区二区| 国产精品一区二区久久国产|