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,一個無法控制。


2010年10月18日 星期一

Manifest Style

從.Net 2005之後,AppWizard在產生MFC application時,多了[Common Control Manifest]的選項

使用Manifest產生的對話盒元件看起來比較圓滑舒服,不像之前的3D按鈕那樣的生硬,下圖左是使用Manifest產生的About對話盒,下圖右為傳統的Style。

使用Manifest的project,在stdafx.h中會多出一段碼:

#ifdef _UNICODE
#if defined _M_IX86
#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='x86' publicKeyToken='6595b64144ccf1df' language='*'\"")
#elif defined _M_X64
#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='amd64' publicKeyToken='6595b64144ccf1df' language='*'\"")
#else

#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
#endif

#endif

如果是使用Multi-Byte的project,即使選擇了Manifest,產生的對話盒元件就仍然是傳統型式。

事實上,我把#ifdef _UNICODE這行直接註解掉,Multi-Byte程式還是可以正常執行,而且對話盒就變成Manifest的Style了。所以,這段的UNICODE保護到底有沒有用仍然是個謎?

2010年10月5日 星期二

設定Folder Browser的初始路徑

想不到只是要設定Folder Browser的初始路徑居然要用到回呼(callback)函式

首先必需建立一個Callback Function讓Folder Browser初始化時呼叫,並送出BFFM_SETSELECTION來指定路徑(即我們的初始化路徑),注意BrowseCallbackProc必需為static。

int CALLBACK CMainFrame::BrowseCallbackProc(HWND hwnd,UINT uMsg,LPARAM lParam,LPARAM lpData)
{
  switch(uMsg)
  {
  case BFFM_INITIALIZED:
    ::SendMessage(hwnd,BFFM_SETSELECTION,TRUE,lpData);
    break;
  default:
    break;
  }
  return 0;  
}


指定我們的路徑與Callback Function

BROWSEINFO info = { 0 };
info.lpfn = BrowseCallbackProc;
info.lParam = (LPARAM)(strDefault.GetString());


呼叫SHBrowserForFolder產生Folder Browser

if(PIDLIST_ABSOLUTE pIDL = ::SHBrowseForFolder(&info))
{
  if(::SHGetPathFromIDList(pIDL,(LPSTR)&szPath))
  {

    ::CoTaskMemFree(pIDL);

    AfxGetApp()->OpenDocumentFile(szPath,FALSE);
  }
}


執行結果如下圖

2010年10月3日 星期日

初試Visual Studio 2010平行處理函式 (PPL)

VS2010發表時很強調他的平行處理函式的功能,因此我也嘗試看看是不是真如廣告說的那麼神奇。測試使用一張8MB(32bit)的影像進行500次的平滑化處理。

平滑化處理公式為

I'(x) = (I(x) - dc) * W(x)

首先,必載入平行處理函式的檔頭,由於這是2010才支援的功能,我會先判斷_MSC_VER是否為1600以上的版本,以確認編譯器有支援

#if  _MSC_VER >= 1600
#include "ppl.h"

using namespace Concurrency;
using namespace std;
#endif  //  _MSC_VER

接著將原有的

unsigned long dwPos = 0;
long value = 0;

for(unsigned long i=0 ; i<dwSize ; i++)
{
    value = (((pIData[dwPos] - DC[0]) * pWeight[dwPos]) >> 8);

    if(value < 0)         pOData[dwPos] = 0;
    else if(value > 255)  pOData[dwPos] = 255;
    else                  pOData[dwPos] = (unsigned char)value;

    dwPos++;

    value = (((pIData[dwPos] - DC[1]) * pWeight[dwPos]) >> 8);

    if(value < 0)         pOData[dwPos] = 0;
    else if(value > 255)  pOData[dwPos] = 255;
    else                  pOData[dwPos] = (unsigned char)value;

    dwPos++;

    value = (((pIData[dwPos] - DC[2]) * pWeight[dwPos]) >> 8);

    if(value < 0)         pOData[dwPos] = 0;
    else if(value > 255)  pOData[dwPos] = 255;
    else                  pOData[dwPos] = (unsigned char)value;

    dwPos+=2;
}

改寫為

unsigned long dwPos = 0;
long value = 0;

parallel_for(unsigned long(0),dwSize,[&](unsigned long i)
{
    dwPos = (i << 2);

    value = (((pIData[dwPos] - DC[0]) * pWeight[dwPos]) >> 8);

    if(value < 0)         pOData[dwPos] = 0;
    else if(value > 255)  pOData[dwPos] = 255;
    else                  pOData[dwPos] = (unsigned char)value;

    dwPos++;

    value = (((pIData[dwPos] - DC[1]) * pWeight[dwPos]) >> 8);

    if(value < 0)         pOData[dwPos] = 0;
    else if(value > 255)  pOData[dwPos] = 255;
    else                  pOData[dwPos] = (unsigned char)value;

    dwPos++;

    value = (((pIData[dwPos] - DC[2]) * pWeight[dwPos]) >> 8);

    if(value < 0)         pOData[dwPos] = 0;
    else if(value > 255)  pOData[dwPos] = 255;
    else                  pOData[dwPos] = (unsigned char)value;

    dwPos+=2;
});

這裡我犯了一個明顯的錯誤,在做並行化處理時必注意資料必需保持獨立性,共享資料則必需唯讀或同步化,上述的程式執行後,影像上會有明顯的雜點,主要原因是共享資料dwPosvalue並未保持唯讀的特性,使得資料混亂。

解決方案為將dwPosvalue變為local的變數不共享,程式修改如下:

parallel_for(unsigned long(0),dwSize,unsigned long(1),[&](unsigned long i)
{
    for(int c=0 ; c<3 ; c++)
    {
        unsigned long dwPos = (i << 2) + c;

        long value = (((pIData[dwPos] - DC[c]) * pWeight[dwPos]) >> 8);

        if(value < 0)         pOData[dwPos] = 0;
        else if(value > 255)  pOData[dwPos] = 255;
        else                  pOData[dwPos] = (unsigned char)value;
    }
});
 
先在Core dual 2.2G + XP系統上執行,比較serial / parallel上執行的效能

serial:       0.01639851 sec / frame
paralle:     0.03472216 sec / frame

在雙核主機上serial的效能比parallel

換成 Coro i7 920 2.7G + XP64再次比較
serial:       0.01312059
parallel:    0.01394287

平行處理的效能與使用單處理器相當,也就是說,單純使用平行處理函式的速度並未有提升而且可能降低原有的效能。看來事實並不如Microsoft廣告說的如此簡單,要發揮出真正的效能仍有不少最佳化的動作要做。