顯示具有 Visual Studio 標籤的文章。 顯示所有文章
顯示具有 Visual Studio 標籤的文章。 顯示所有文章

2015年10月22日 星期四

設定OpenCV 3.0 於 VS2012 環境中

1. 為方便稍後的設定與未來專案的移植性,在安裝完OpenCV 3.0後首先要設定OpenCV的環境目錄,使用管理員的權限開啟Command視窗輸入

   1: setx -m OPENCV_DIR e:\sdk\opencv\build

2. 開啟VS2012,匯入我設定好的property setting (OpenCV300.zip),OpenCV300.props的內容如下:



   1: <?xml version="1.0" encoding="utf-8"?>
   2: <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
   3:   <ImportGroup Label="PropertySheets" />
   4:   <PropertyGroup Label="UserMacros" />
   5:   <PropertyGroup>
   6:     <IncludePath>$(OPENCV_DIR)\include;$(IncludePath)</IncludePath>
   7:     <LibraryPath Condition="'$(Platform)'=='Win32'">$(OPENCV_DIR)\x86\vc11\staticlib;$(LibraryPath)</LibraryPath>
   8:     <LibraryPath Condition="'$(Platform)'=='X64'">$(OPENCV_DIR)\x64\vc11\staticlib;$(LibraryPath)</LibraryPath>
   9:   </PropertyGroup>
  10:   <ItemDefinitionGroup>
  11:     <Link Condition="'$(Configuration)'=='Debug'">
  12:       <AdditionalDependencies>ippicvmt.lib;IlmImfd.lib;libjasperd.lib;libjpegd.lib;libpngd.lib;libtiffd.lib;libwebpd.lib;opencv_calib3d300d.lib;opencv_core300d.lib;opencv_features2d300d.lib;opencv_flann300d.lib;opencv_hal300d.lib;opencv_highgui300d.lib;opencv_imgcodecs300d.lib;opencv_imgproc300d.lib;opencv_ml300d.lib;opencv_objdetect300d.lib;opencv_photo300d.lib;opencv_shape300d.lib;opencv_stitching300d.lib;opencv_superres300d.lib;opencv_ts300d.lib;opencv_video300d.lib;opencv_videoio300d.lib;opencv_videostab300d.lib;zlibd.lib;%(AdditionalDependencies)</AdditionalDependencies>
  13:     </Link>
  14:     <Link Condition="'$(Configuration)'=='Release'">
  15:       <AdditionalDependencies>ippicvmt.lib;IlmImf.lib;libjasper.lib;libjpeg.lib;libpng.lib;libtiff.lib;libwebp.lib;opencv_calib3d300.lib;opencv_core300.lib;opencv_features2d300.lib;opencv_flann300.lib;opencv_hal300.lib;opencv_highgui300.lib;opencv_imgcodecs300.lib;opencv_imgproc300.lib;opencv_ml300.lib;opencv_objdetect300.lib;opencv_photo300.lib;opencv_shape300.lib;opencv_stitching300.lib;opencv_superres300.lib;opencv_ts300.lib;opencv_video300.lib;opencv_videoio300.lib;opencv_videostab300.lib;zlib.lib;%(AdditionalDependencies)</AdditionalDependencies>
  16:     </Link>
  17:   </ItemDefinitionGroup>
  18:   <ItemGroup />
  19: </Project>

若編譯的環境為VS2013則將上述的設定檔中的vc11改為vc12即可。


由於使用的是static lib所以原專案的runtime library也必需改為/MT或/MTd


OpenCV_Runtime

2013年8月9日 星期五

VS2012 extension - Image Watch

我在開發中的影像程式大部份都是在計算影像的某些特徵值,因此最終輸出的結果不是影像而是一些數值。不過在開發的過程中難免都會遇到問題,當輸出的結果與預期有落差時,確認是計算的過程出現的問題還是載入影像本身的發生問題是除錯過程中必。MS提供VS2012一個方便的擴充套件Image Watch,方便讓我們直接在Debug的過程中觀察記憶體中的影像資料。

我們可以透過VS2012的擴充工具直接搜尋「Image Watch」來安裝。

ImageWatch

ImageWath預設只支援OpenCV的cvMat與Intel的IPLImage。我很少利用這些工具來開發,因此必需新增.nativs。

ImageWath會在VS2012開啟後,從[User]\Documents\Visual Studio 2012\Visualizers目錄下自動搜尋.nativs檔。我們可以把自行定義的影像格式寫成檔案放在Visualizers目錄中,讓ImageWath可以解讀。下面是我常用的影像格式參考Image Watch Help後所完成的.nativs檔案内容:

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
   3:   <UIVisualizer ServiceId="{A452AFEA-3DF6-46BB-9177-C0B08F318025}" Id="1"
   4:                 MenuName="Add to Image Watch"/>
   5:   <Type Name="ImgWGenericImage&lt;*&gt;">
   6:     <UIVisualizer ServiceId="{A452AFEA-3DF6-46BB-9177-C0B08F318025}" Id="1" />
   7:   </Type>
   8:   <Type Name="ImgWGenericImage&lt;*&gt;">
   9:     <Expand>
  10:       <Synthetic Name="[type]" Condition='strcmp("unsigned char", "$T1") == 0'>
  11:         <DisplayString>UINT8</DisplayString>
  12:       </Synthetic>
  13:       <Synthetic Name="[type]" Condition='strcmp("float", "$T1") == 0'>
  14:         <DisplayString>FLOAT32</DisplayString>
  15:       </Synthetic>
  16:       <Item Name="[channels]">nchannels</Item>
  17:       <Item Name="[width]">ncols</Item>
  18:       <Item Name="[height]">nrows</Item>
  19:       <Item Name="[data]">data</Item>
  20:       <Item Name="[stride]">ncols*nchannels*sizeof($T1)</Item>
  21:     </Expand>
  22:   </Type>
  23: </AutoVisualizer>

在我們的程式碼中要有對應的C++定義:



   1: #ifdef _DEBUG
   2: template <typename T>
   3: struct ImgWGenericImage
   4: {
   5:     unsigned int ncols;
   6:     unsigned int nrows;
   7:     unsigned int nchannels;
   8:     T* data;
   9: };
  10: #endif // _DEBUG

當我們在Debug模式下單步執行時,透過選單[View]→[Other Windows]→[Image Watch]叫出Image Watch視窗後,可以在Image Watch的視窗中直接看到影像的結果:


result


還可輸出成檔案


dumpresult


Image Watch有提供一些簡單的指令方便我們觀察影像。詳細的用法就直接參考Help吧!


Reference
1. Image Watch Help
2. Image Watch Extension

2013年8月7日 星期三

[W8App] VS2012 Unit Test - Prepare environment (2)

我的Unit Test主要是測試與驗証Portable Library的功能,其中又與影像相關的程式有關。在測試時必需從檔案中讀取大量的數據進行分析處理,處理的結果再回存回硬碟比對結果。

但是,即使只是Unit Test專案,也必需遵守W8App的檔案權限限制。因此在測試之前必需先設定Unit Test專案的權限。

我通常會指定Unit Test專案有存取Documents Library、Pictures Library、Videos Library這三個檔案庫的能力。這樣就能使Unit Test能「讀」這三個檔案庫中的檔案。

Capabilities

如果Unit Test專案還需要有輸出結果到硬碟中,則必需讓專案還有「寫」的能力。必需指定File Type Associations支援的副檔名,以下範例為指定Unit Test可以輸出jpg檔案。

Declarations

由於我的影像檔案庫非常的大,使用了好幾顆硬碟,可依下面步驟把不同硬碟的路徑都指定到Documents Library (或Pictures / Videos)中。

1. 開啟檔案管理員,對著Documents點滑鼠右鍵。選擇「Properties」

Documents_Properties_1

2. 點選「Add …」選擇指定的目錄

Documents_Properties_2

2013年7月29日 星期一

[W8App] VS2012 Unit Test – Prepare environment (1)

VS2012已能建立Windows Store Unit Test Library,但是C++ WinRT卻無法測試async函式。Win8 app和傳統的Desktop app最大的一個不同點是非同步(async)的架構,Async在某些類別的API幾乎是標準設計了。因此若有開發Windows Store Unit Test計畫的話,建議在安裝完VS2012之後先安裝C++ WinRT Async UnitTestLibrary。除了從網路上下載外,也可直接從VS2012的選單[TOOLS] –> [Extensions and Updates]中找到這個擴充套件。

WinRT Async

安裝完成後,VS2012會多一個專案選項[Unit Test Library with Async Helper (Windows Store apps)]。若測試的函式有包含非同步架構的話,就必需選用這個專案範本。

Add WinRT Async

* C++ WinRT Async UnitTestLibrary是MS開發團隊在開發著名的範例程式Hilo的過程中所開發出來的Unit Test套件,原始程式碼在C++ Unit Test Library for Windows Store apps with Async Helper有與趣可自行參考。

2013年5月13日 星期一

[秘技] 實作Converter (繼承自IValueConverter)

其實只是要秀一下我發現的一個秘技。為何Data Binding時需要Converter,如何實作Converter,如何Binding的方式教科書已經說的太多,只是我實作程式碼時常想,同樣的工作同樣的程式碼為何要一再重覆的coding。教科書沒教我們的Implement Converter技法其實很簡單。

當你把Cursor設定在IValueConverter上時有沒有覺得有一個怪怪的小藍色底線看了很不舒服很想把他消除:

valueconvert_1

把滑鼠移動過去會變成一個可以按的小按鈕

valueconvert_2

選擇Implement與Explicitly implement有什麼不同我還不曉得,不過按了之後的結果是一樣的,Convert與ConvertBack都implement好了(雖然都是throw NotImplementedException):

valueconvert_4

剩下的工作就是自已實作要如何convert而已。

從滑鼠移到小藍點到變成按鈕要花點時間,如果不想等,可以直接對IValueConvert按右鍵,也有Implement Interface的選項可選:

valueconvert_3

這個秘技似乎只有新建的class可以用,如果已經有Implement過Convert與ConvertBack後,Implement Interface的選單會自然的消失。

2012年4月8日 星期日

知識更新 建立選單和快捷鍵

MFC要為我們的選單加入快捷鍵其實是很簡單的,Visual Studio從古代的Visual C++就提供使用者在Resource檔中編寫Accelerator Table讓我們把相關連的選單和快捷鍵連結在一起,在VS 2010的使用者界面長的像下圖所示。
我們可以直接在rc檔中編輯選單所使用的快捷鍵,或是在properties中選擇。

這個功能實際上測試起來應該會發現"新"建立的快捷鍵是沒有反應。如果你的執行程式曾經成功執行過一次,MFC會自動把accelerator table寫到註冊檔中,之後即使我們修改了快捷鍵也都不會自動更新註冊的結果。如果快捷表有修改過,那就把註冊檔中的快捷表刪除吧。
微軟說,這可是新功能不是Bug喔(註1)

如果不想更改註冊檔,可從選單的[View] / [Toolbars and Docking Windows] / [Customize....]使用"新"界面進行設定。


註1. Visual Studio 2010 Unable to Commit Accelerator Resource Changes

2011年11月20日 星期日

Keep Menu Expand - MFC

在VS 2008 Feature Pack後MFC的menu有了expand的style,VS 2010進一步整合feature pack的GUI行為在建立專案時就可以選擇新的menu style。
不過新功能有一個很不好用的地方,不管你應用程式中的選單數量是多還是少,都會預設的收折(collapse)。
選單收折的新功能是由下面這段程式碼所引發

int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    ...

    CList&lt; UINT, UINT&gt; lstBasicCommands;

    lstBasicCommands.AddTail(ID_FILE_NEW);
    lstBasicCommands.AddTail(ID_FILE_OPEN);
    lstBasicCommands.AddTail(ID_FILE_SAVE);
    lstBasicCommands.AddTail(ID_FILE_PRINT);
    lstBasicCommands.AddTail(ID_APP_EXIT);
    lstBasicCommands.AddTail(ID_EDIT_CUT);
    lstBasicCommands.AddTail(ID_EDIT_PASTE);
    lstBasicCommands.AddTail(ID_EDIT_UNDO);
    lstBasicCommands.AddTail(ID_APP_ABOUT);
    lstBasicCommands.AddTail(ID_VIEW_STATUS_BAR);
    lstBasicCommands.AddTail(ID_VIEW_TOOLBAR);

    CMFCToolBar::SetBasicCommands(lstBasicCommands);
}

CMFCToolBar::SetBasicCommands是採正面列表的方式,指定基本選單。這些基本選單在使用者點選選單時,會保持可視狀態而不會被隱藏,而不在清單中的選單則自動收藏起來。因此如果你的選單是不想被收折起來,只要把選單的ID加入到lstBasicCommands中即可。 如果應用程式不希望有收折的功能,則只要把這段程式刪除即可。

2011年10月31日 星期一

註冊自已的dialog base類別

有時候我們會設計一些跨行程的應用程式,當我們需要取得另一行程的視窗Handle對其做操作時通常會利用FindWindow來找到我們想要的視窗。

FindWindow可利用title name或是windows class二種方式來找到指定的視窗。為避免命名上的衝突或是多語系的支援,通常我們會重新註冊我們的dialog類別為特殊的名稱。註冊的方式分為2部份。

Step 1:首先找到你的RC檔,在dialog的rc區加入以下的程式碼

IDD_MYCLASSDLG_DIALOG DIALOGEX 0, 0, 200, 200
STYLE DS_SETFONT | DS_FIXEDSYS | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME
EXSTYLE WS_EX_APPWINDOW
CAPTION "MyClass Dialog"
CLASS "MyClassDlg"
FONT 8, "MS Shell Dlg", 0, 0, 0x1
BEGIN
...


也可透過Visual Studio的GUI來修改,先將RC設定為非MFC Mode


如此一來,Dialog的屬性Class Name變為可修改狀態,再輸入指定的Class Name即可



Step 2:設定好RC屬性後,在App的InitInstance中呼叫main dialog之前註冊MyClassDlg即可完成註冊

WNDCLASS wndcls = { 0 };
::GetClassInfo(AfxGetInstanceHandle(),MAKEINTRESOURCE(32770),&amp;wndcls);

wndcls.lpszClassName = _T("MyClassDlg");

AfxRegisterClass(&amp;wndcls);

CMyClassDlg dlg;
m_pMainWnd = &amp;dlg;
INT_PTR nResponse = dlg.DoModal();
...



2011年3月23日 星期三

warning C4996: _Is_checked? No I don't.

如果我們在VS2010用非STL的container來使用某些STL algorithm時很容易得到傳說中的warning C4996。

warning C4996: 'std::_Merge1': Function call with parameters that may be unsafe - this call relies on the caller to check that the passed values are correct. To disable this warning, use -D_SCL_SECURE_NO_WARNINGS. See documentation on how to use Visual C++ 'Checked Iterators'

除了關掉4996的warning訊息外。可對cl編譯器指定/D_SCL_SECURE_NO_WARNINGS,煩人的警告訊息OUT

2011年3月16日 星期三

知識更新 MFC Modeless Dialog 生成與結束

Windows的dialog有二種模式,model與modeless。所謂的model模式指的是當dialog產生時,該應用程式的訊息會被鎖住,直到dialog結束。而modeless dialog則較像是程式中的floating panel一樣可和應用程式一同運作,能做出較複雜的功能。

如果要產生一個modeless dialog

我所會的舊知識(這是MS VC 1.5時代學到的)

1. 建立一個新的CModeless::Create(CWnd* pParent)函式。
2. 呼叫端中必需記錄該dialog object。
3. 呼叫端必需有closedialog函式。
4. 要產生modeless dialog時必需透過new、Create、與ShowWindow三個步驟。
5. 當對話盒要關閉時,需使用呼叫端的closedialog函式而非自行解構。
6. 當程式要結束時,必需檢查dialog object是否已解構,否則必需呼叫closedialog函式以避免memory leakage的發生。

今天的新知識(可參考VC Sample : Modeless)

1. 建立一個新的CModeless::Create(CWnd* pParent)函式。
2. 呼叫端中不必有記錄該dialog object。
3. 呼叫端不必有closedialog函式。
4. 要產生modeless dialog時必需透過new與Create ShowWindow 個步驟。
5. 當對話盒要關閉時,透過WM_NCDESTROY訊息自行解構。
6. 當程式要結束時,必需檢查dialog object是否已解構,否則必需呼叫closedialog函式以避免memory leakage的發生。

以前在呼叫Create之後必需再呼叫ShowWindow(SW_SHOW)對話盒才可視,但可在resouce中將對話盒Visible屬性設為True即可在Create後直接出現,省去了呼叫ShowWindow的動作。

以前要在呼叫端記錄dialog object是為了在程式結束時能順利解構,但透過接收WM_NCDESTROY訊息可正確得知視窗需解構的時間點,是否要儲存dialog object就不再是必需。

VC的Modeless sample在收到WM_NCDESTROY後馬上就執行delete this,對自己進行解構。
void CModeless::OnNcDestroy()
{
    delete this;
}

但CDialog::OnNcDestroy會釋放dialog中的controller元件的資源,若未先呼叫可能會有memory leakage的疑慮。我加入了這段
void CModeless::OnNcDestroy()
{
    CDialog::OnNcDestroy();
    delete this;
}

2010年11月11日 星期四

CMyCommandLineInfo

在遠古的DOS時代,我們常會在可執行檔後面加上一大堆的command來指定特殊的功能,進到了視窗模式後command已經很少有人在使用了。今天就來談談要始何在透過MFC實現這種原始人才會想用的特殊技藝。

不論產生的是MDISDI或是dialog baseapp,都可以在自動產生的CWinAppInitInstance中找到這段code

CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);

if (!ProcessShellCommand(cmdInfo))
    return FALSE;

ParseCommandLine會將執行的指令進行剪貼轉換為CCommandLineInfo物件的格式,而ProcessShellCommand則依傳入的cmdInfo所擁有的屬性進行相對應的處理。

ParseCommandLine內的作用方式如下,如果我們想做的指令不在預設的列表中時,我們必需改寫ParseParam來對應我們想要的功能。

void CWinApp::ParseCommandLine(CCommandLineInfo& rCmdInfo)
{
    for (int i = 1; i < __argc; i++)
    {
        LPCTSTR pszParam = __targv[i];
        BOOL bFlag = FALSE;
        BOOL bLast = ((i + 1) == __argc);
        if (pszParam[0] == '-' || pszParam[0] == '/')
        {
            // remove flag specifier
            bFlag = TRUE;
            ++pszParam;
        }
        rCmdInfo.ParseParam(pszParam, bFlag, bLast);
    }
}

極簡單的範例:

我在開發一些演算法時常需要反覆測試一些已知的影像比較程式修改前修改後的差異,我懶得每次開啟程式時都要去按File選單來選擇我上一次測試的影像,這時我就會在我的debug條件中加入”/default”command

繼承自CcommandLineInfo產生CMyCommandLineLifo類別,並覆載(override) ParseParam函式。小心,UnicodeMulti-Byte有一點點的不同,若想讓這類別二者通吃必需多覆載針對UnicodeParseParam

完成後記得把InitInstanceCCommandLineInfo cmdInfo改為CMyCommandLineInfo cmdInfo

class CMyCommandLineInfo :  public CCommandLineInfo
{
public:
    virtual void ParseParam(const TCHAR* pszParam, BOOL bFlag, BOOL bLast);
#ifdef _UNICODE
    virtual void ParseParam(const char* pszParam, BOOL bFlag, BOOL bLast);
#endif
};

void CMyCommandLineInfo::ParseParam(const TCHAR* pszParam, BOOL bFlag, BOOL bLast)
{
    if (bFlag)
    {
        const CString strParam(pszParam);
        if(strParam.CompareNoCase(_T("default")) == 0)
        {
            m_nShellCommand = CCommandLineInfo::FileOpen;
            m_strFileName   = AfxGetApp()->GetProfileString(_T("Recent File List"),_T("File1"));

            ParseLast(bLast);

            return;
        }
    }

    CCommandLineInfo::ParseParam(pszParam, bFlag, bLast);
}

#ifdef _UNICODE
void CMyCommandLineInfo::ParseParam(const char* pszParam, BOOL bFlag, BOOL bLast)
{
    const CString strParam(pszParam);

    ParseParam(strParam, bFlag, bLast);
}
#endif

只為了擴充單一功能而必需繼承產生一個新類別,每次遇到這種例子我就有點懷念Object-C

2010年11月4日 星期四

Button Array

我想建一個如下圖所示的對話盒界面。


當我在Resource Edit中double click [Method 1]的check box,Visual Studio 會自動幫我們加入相對應的click message函式

afx_msg void OnBnClickedCheckMethod1();

與其Message Map

ON_BN_CLICKED(IDC_CHECK_METHOD_1, &CSetCompareMethodDlg:: OnBnClickedCheckMethod1)

我程式中的功能會將此時的相對應Method元件設為enable或disable

void CSetCompareMethodDlg::OnBnClickedCheckMethod1()
{
    if(CButton* pCheck = (CButton*)GetDlgItem(IDC_CHECK_METHOD_1))
    {
        m_pObj[0]->Enable((pCheck->GetCheck() == 0) ? false : true);
    }
}

以我的例子而言,我要做8個非常類似的函式,其中只有ID與index不同。這並不會造成我們什麼困擾只是程式的一點怪味而已。

那,該如何讓這個動作變得優雅呢。

關鍵在於能否讓UI的動作包含ID。我們可以用ON_COMMAND_EX取代ON_BN_CLICKED來改寫。

1. 在class宣告中加入

afx_msg BOOL OnBnClickedCheckMethodEX(UINT uid);

2. 把

ON_BN_CLICKED(IDC_CHECK_METHOD_1, &CSetCompareMethodDlg:: OnBnClickedCheckMethod1)

改寫為

ON_COMMAND_EX(IDC_CHECK_METHOD_1, &CSetCompareMethodDlg::OnBnClickedCheckMethodEX)

同樣的動作把IDC_CHECK_METHOD_2 ~IDC_CHECK_METHOD_8都用ON_COMMAND_EX取代

3. 實作OnBnClickedCheckMethodEX

BOOL CSetCompareMethodDlg:: OnBnClickedCheckMethodEX(UINT uid)
{
    if(CButton* pCheck = (CButton*)GetDlgItem(uid))
    {
        // m_pObj[index]->Enable((pCheck->GetCheck() == 0) ? false : true);
    }

    return TRUE;
}

4. 現在的問題是如何把uid和index對應起來,有2種方法可行。如果你無法更動Resource.h的話。

BOOL CSetCompareMethodDlg:: OnBnClickedCheckMethodEX(UINT uid)
{
    const UINT nCheckID[8] = {
        IDC_CHECK_METHOD_1, IDC_CHECK_METHOD_2, IDC_CHECK_METHOD_3, IDC_CHECK_METHOD_4,
        IDC_CHECK_METHOD_4, IDC_CHECK_METHOD_6, IDC_CHECK_METHOD_7, IDC_CHECK_METHOD_8
    };

    for(int index=0 ; index<8 ; index++)
    {
        if(uid == nCheckID[index])
        {
            if(CButton* pCheck = (CButton*)GetDlgItem(uid))
            {
                pObj[index]->Enable((pCheck->GetCheck() == 0) ? false : true);
            }

            return TRUE;
        }
    }
    return FALSE;
}

如果你可以更動Resource.h的話,我會先到Resource.h中把ID做排序

#define IDC_CHECK_METHOD_1 1004
#define IDC_CHECK_METHOD_2 1005
#define IDC_CHECK_METHOD_3 1006
#define IDC_CHECK_METHOD_4 1007
#define IDC_CHECK_METHOD_5 1008
#define IDC_CHECK_METHOD_6 1009
#define IDC_CHECK_METHOD_7 1010
#define IDC_CHECK_METHOD_8 1011

然後函式能寫的更簡捷

BOOL CSetCompareMethodDlg::OnBnClickedCheckMethodEX(UINT uid)
{
    int index = uid - IDC_CHECK_METHOD_1;

    if(CButton* pCheck = (CButton*)GetDlgItem(uid))
    {
        pObj[index]->Enable((pCheck->GetCheck() == 0) ? false : true);

        return TRUE;
    }

    return FALSE;
}

當然要改ID你必需要很小心很小心。

2010年10月22日 星期五

MFC PropertyGrid Control

我的工作經常要做些測試用的程式來調整參數或是驗証功能,這其中最大的UI使用就是拉出一堆Edit Box來做為輸入的界面,有時候為了美觀(or自虐)會把這些參數設定做成像Visual Studioproperty page的功能,可能我是個老傢伙了,我的作法還停留在太古時代,先繼承自ClistBox產生我自己的property grid class,然後再用subclass的方法,把原先resource上的list換成我寫好的property grid class以產生出我要的property grid style class。嗯,這還不是最麻煩的,最麻煩的是property必需要能edit吧,所以我還要偵測mouse的點擊動作,當點在edit區時create一個edit box把他蓋在原本的位置上方讓,還記得我第一次作出這樣功能時是VC 4.2時代吧,想不到這個class用了10年還在用真是一點都不長進。

我這元件只能單純的輸入文字而已,想說10多年了,來加個新功能把combo box也弄進來試試,結果

我在拉元件時不小心撇見了Visual Studio 2010Toolbox中的一角

難道說時代已經進步到這東西己是MFC內建的元件了嗎,癈話不多說直接拉元件就上了

例用Add Member Variable Wizard把這元件加進來方便操作。

Dialog::OnInitDialog中把我要的property加入

BOOL CSetCompareMethodDlg::OnInitDialog()
{
    CDialogEx::OnInitDialog();

    CMFCPropertyGridProperty* pProp = new CMFCPropertyGridProperty(_T("Different Threshold"),_T("1"));

    pProp->AllowEdit(TRUE);

    m_wndPropClass.AddProperty(pProp);
    m_wndPropClass.AdjustLayout();

    return TRUE;  // return TRUE unless you set the focus to a control
}

結果,好像有點怪怪的


首先,我有設定border,但似乎沒畫出來,其次就是property namewidth似乎是太太太太小了。

研究了這2class的相關成員發現似乎沒有辦法指定我要的欄位大小(很神奇,可以get不能set這不像是M$style,只能用AdjustLayout讓他自己算),border style也嘗試了幾種組合也都無法顯現。在計無可施下,突然有一個聲音,告訴我這是M$bug, bug, bug, bug…,可能是DDX的交握出了問題,我把他改成用老方法subclass試試

BOOL CSetCompareMethodDlg::OnInitDialog()
{
    CDialogEx::OnInitDialog();

    CWnd* pWnd = GetDlgItem(IDC_PROPERTYGRID_METHOD);
    CRect rectPropList;
    pWnd->GetClientRect(&rectPropList);
    pWnd->MapWindowPoints(this, &rectPropList);

    m_wndPropClass.Create(WS_CHILD | WS_VISIBLE | WS_TABSTOP | WS_BORDER, rectPropList, this, (UINT)-1);

    m_wndPropClass.EnableHeaderCtrl(FALSE);
    m_wndPropClass.SetVSDotNetLook(FALSE);
    m_wndPropClass.MarkModifiedProperties(TRUE);
    m_wndPropClass.SetAlphabeticMode(FALSE);
    m_wndPropClass.SetShowDragContext(FALSE);
   
    CMFCPropertyGridProperty* pProp = new CMFCPropertyGridProperty(_T("Different Threshold"),_T("1"));

    pProp->AllowEdit(TRUE);

    m_wndPropClass.AddProperty(pProp);
    m_wndPropClass.AdjustLayout();

    return TRUE;  // return TRUE unless you set the focus to a control
}

別忘了把原本的DDX斷掉

void CSetCompareMethodDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialogEx::DoDataExchange(pDX);
    //  DDX_Control(pDX, IDC_PROPERTYGRID_METHOD, m_wndPropClass);
}

正確的style出現啦

如果想要做出combo的效果,就把想加入的CMFCPropertyGridProperty objectAddOption的方式一項一項的加進去,也可以用AddSubItem做成樹狀結構。相關可使用的調整還很多,有興趣可參考相關連結。

CMFCPropertyGridProperty的生成方式很像Composite的做法,特別是要做成樹狀的結構時,非常的容易操作。以我的懶性而言,要做出樹狀應該是不可能吧。

create的方式建立的CMFCPropertyGridProperty必需設resouceinvisible否則會產生2個property,一個無法控制。