[CListCtrl]顯示單個儲存格之Tip

一樣內容也是臨時找的,並沒有整理,一定會雷同。

這邊需自行處理訊息,所以要加入兩個訊息
    ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW, 0, 0xFFFF, OnToolTipText)
    ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA, 0, 0xFFFF, OnToolTipText)

然後也需要改寫CListCtrl::OnToolHitTest(),裡面是用來設定UID,用途目前不明,但缺少的話,tip會閃一下就消失了。
 int CXListCtrl::OnToolHitTest(CPoint point, TOOLINFO * pTI) const   
{  
//See if the point falls onto a list item  
//UINT nFlags = 0;  
LVHITTESTINFO lvhitTestInfo;  
lvhitTestInfo.pt    = point;  
int nItem = ListView_SubItemHitTest(  
this->m_hWnd,  
&lvhitTestInfo);  
int nSubItem = lvhitTestInfo.iSubItem;  
UINT nFlags =  lvhitTestInfo.flags;  
//nFlags is 0 if the SubItemHitTest fails  
//Therefore, 0 & <anything> will equal false  
if (nFlags & m_wHistMask){  
//If it did fall on a list item,  
//and it was also hit one of the  
//item specific sub-areas we wish to show tool tips for  
//Get the client (area occupied by this control  
RECT rcClient;  
GetClientRect( &rcClient );  
//Fill in the TOOLINFO structure  
pTI->hwnd = m_hWnd;  
pTI->uId = (UINT) (nItem * 100 + nSubItem);  
pTI->lpszText = LPSTR_TEXTCALLBACK;  
pTI->rect = rcClient;  
return pTI->uId; //By returning a unique value per listItem,  
//we ensure that when the mouse moves over another list item,  
//the tooltip will change  
}else{  
//Otherwise, we aren't interested, so let the message propagate  
return -1;  
}  
}  

再來就是上面自己定義的Function,tip所要顯示的文字就在這這裡面設定:
 BOOL CXListCtrl::OnToolTipText(UINT id, NMHDR* pNMHDR, LRESULT* pResult)  
{  
//Handle both ANSI and UNICODE versions of the message  
TOOLTIPTEXTA* pTTTA = (TOOLTIPTEXTA*)pNMHDR;  
TOOLTIPTEXTW* pTTTW = (TOOLTIPTEXTW*)pNMHDR;  
//Ignore messages from the built in tooltip, we are processing them internally  
if( (pNMHDR->idFrom == (UINT)m_hWnd) &&  
( ((pNMHDR->code == TTN_NEEDTEXTA) && (pTTTA->uFlags & 
TTF_IDISHWND)) ||  
      ((pNMHDR->code == TTN_NEEDTEXTW) && (pTTTW->uFlags & TTF_IDISHWND)) ) ){  
return FALSE;  
}  
*pResult = 0;  
CString strTipText;  
//Get the mouse position  
const MSG* pMessage;  
pMessage = GetCurrentMessage();  
ASSERT ( pMessage );  
CPoint pt;  
pt = pMessage->pt; //Get the point from the message  
ScreenToClient( &pt ); //Convert the point's coords to be relative to this control  
//See if the point falls onto a list item  
LVHITTESTINFO lvhitTestInfo;  
lvhitTestInfo.pt    = pt;  
int nItem = SubItemHitTest(&lvhitTestInfo);  
int nSubItem = lvhitTestInfo.iSubItem;  
UINT nFlags =  lvhitTestInfo.flags;  
//nFlags is 0 if the SubItemHitTest fails  
//Therefore, 0 & <anything> will equal false  
if( nFlags & m_wHistMask ){  
//If it did fall on a list item,  
//and it was also hit one of the  
//item specific sub-areas we wish to show tool tips for  
//Lookup the list item's text in the ToolTip Map  
CString strKey;  
strKey.Format(_T("%d"), nItem * 100 + nSubItem);  
if( m_ToolTipMap.Lookup(strKey, strTipText ) ){  
//If there was a CString associated with the list item,  
//copy it's text (up to 80 characters worth, limitation of the TOOLTIPTEXT structure)  
//into the TOOLTIPTEXT structure's szText member  
//Deal with UNICODE  
#ifndef _UNICODE  
if (pNMHDR->code == TTN_NEEDTEXTA)  
//lstrcpyn(pTTTA->szText, strTipText, 80);  
lstrcpyn(pTTTA->szText, "AAA", 80);  
else  
//_mbstowcsz(pTTTW->szText, strTipText, 80);  
_mbstowcsz(pTTTW->szText, "Double Click Me", 80);  
#else  
if (pNMHDR->code == TTN_NEEDTEXTA)  
_wcstombsz(pTTTA->szText, strTipText, 80);  
else  
lstrcpyn(pTTTW->szText, strTipText, 80);  
#endif  
return FALSE;  //We found a tool tip,  
//tell the framework this message has been handled  
////////////////////////////////////////////////////////////////////////////////  
// ****** Special note *****  
//  
// Still don't understand why the function must return FALSE for CListCtrl  
// so as not to cause flickering, as opposed to Nate Maynard's derivation  
// from CTreeCtrl.  
// I have experimented with disabling Tooltips for the control  
// and found out that a "ghost" tooltip appears for a fraction of a second...  
//  
// I am completely at a loss...  
// Seems to work, though...  
//  
////////////////////////////////////////////////////////////////////////////////  
}  
}  
return FALSE; //We didn't handle the message,  
}  

Reference:Handling TTN_NEEDTEXT Notification for Tool Tips

[CListCtrl]改變儲存格文字顏色

資料也是在網路上找來的,只是急著測試,就沒有記住來源了。所以一定會有雷同,找時間在把內容整理過嚕@@

需要用到NM_CUSTOMDRAW訊息,這要放在Dialog的message map內:
ON_NOTIFY(NM_CUSTOMDRAW, IDC_LIST, &CXListCtrl::OnNMCustomdrawLst)

因此要改寫CListCtrl::OnNMCustomdrawLst()
 void CXListCtrl::OnNMCustomdrawLst(NMHDR *pNMHDR, LRESULT *pResult)  
{  
LPNMCUSTOMDRAW pNMCD = reinterpret_cast<LPNMCUSTOMDRAW>(pNMHDR);  
NMLVCUSTOMDRAW* pLVCD = reinterpret_cast<NMLVCUSTOMDRAW*>(pNMHDR);  
// TODO: Add your control notification handler code here  
*pResult = 0;  
if ( CDDS_PREPAINT == pLVCD->nmcd.dwDrawStage )  
{  
*pResult = CDRF_NOTIFYITEMDRAW;  
}else if ( CDDS_ITEMPREPAINT == pLVCD->nmcd.dwDrawStage ) {  
//  // This is the prepaint stage for an item. Here's where we set the  
//  // item's text color. Our return value will tell Windows to draw the  
//  // item itself, but it will use the new color we set here.  
//  // We'll cycle the colors through red, green, and light blue.  
*pResult = CDRF_NOTIFYSUBITEMDRAW;        //To Draw sub item request  
} else if ( (CDDS_ITEMPREPAINT | CDDS_SUBITEM) == pLVCD->nmcd.dwDrawStage ) {  
// This is the prepaint stage for a subitem. Here's where we set the  
// item's text and background colors. Our return value will tell   
// Windows to draw the subitem itself, but it will use the new colors  
// we set here.  
// The text color will cycle through red, green, and light blue.  
// The background color will be light blue for column 0, red for  
// column 1, and black for column 2.  
COLORREF crText, crBkgnd;  
//                Sub Item Index                                    Item Index  
if ( (3 == pLVCD->iSubItem && pLVCD->nmcd.dwItemSpec == 1) ||  
(3 == pLVCD->iSubItem && pLVCD->nmcd.dwItemSpec == 3) )  
{  
crText = RGB(255,0,0);  
//crBkgnd = RGB(128,128,255);  
}  
else if ( 1 == pLVCD->iSubItem )  
{  
crText = RGB(0,255,0);  
crBkgnd = RGB(255,0,0);  
}  
else  
{  
crText = RGB(128,128,255);  
crBkgnd = RGB(0,0,0);  
}  
// Store the colors back in the NMLVCUSTOMDRAW struct.  
pLVCD->clrText = crText;  
//pLVCD->clrTextBk = crBkgnd; 
// Tell Windows to paint the control itself.  
//*pResult = CDRF_DODEFAULT;  
*pResult = CDRF_NOTIFYPOSTPAINT|CDRF_NOTIFYSUBITEMDRAW;  
}  
}  

[CListCtrl]在滑鼠指標移過儲存格上方後,顯示底線

目前算只完成一半,因為字跟欄位有個距離,目前還沒辦法動態取得。
ON_WM_MOUSEMOVE()

一樣也是在CListCtrl::OnMouseMove()內處理。
     CRect rect1, rectParent; 
this->GetSubItemRect(nItem, nSubItem, LVIR_BOUNDS, rect1);   
this->GetWindowRect(&rectParent);   
CString cstrDisplayString =    this->GetItemText(nItem, nSubItem);   
int nStrWidth = this->GetStringWidth(cstrDisplayString);   
unsigned int nCurNumOfWorkArea = this->GetNumberOfWorkAreas();  
if ( point.x < rect1.right && point.x > rect1.left && point.y < rect1.bottom && point.y > rect1.top )       
{   
//Clean previous line   
if ( !this->m_PreRect.IsRectEmpty() )   
{   
CDC *pcdc = this->GetDC();   
CPen cp(PS_SOLID, 1, RGB(255,255,255));   
pcdc->SelectObject(cp.m_hObject);   
pcdc->MoveTo(this->m_PreRect.left, this->m_PreRect.bottom);   
pcdc->LineTo(this->m_PreRect.right, this->m_PreRect.bottom);   
this->m_PreRect.SetRectEmpty();   
}   
//Draw line   
CDC *pcdc = this->GetDC();   
CPen cp(PS_SOLID, 1, RGB(255,0,0));   
pcdc->SelectObject(cp.m_hObject);   
pcdc->MoveTo(rect1.left, rect1.bottom);  
pcdc->LineTo((rect1.left + nStrWidth), rect1.bottom);   
this->m_PreRect.SetRect(rect1.left, rect1.top, (rect1.left+nStrWidth), rect1.bottom);   
}else {  
         CDC *pcdc = this->GetDC();   
CPen cp(PS_SOLID, 1, RGB(255,255,255));   
pcdc->SelectObject(cp.m_hObject);   
pcdc->MoveTo(this->m_PreRect.left, this->m_PreRect.bottom);   
pcdc->LineTo(this->m_PreRect.right, this->m_PreRect.bottom);   
this->m_PreRect.SetRectEmpty();   
} 

this->ReleaseDC(pcdc);

[CListCtrl]取得目前滑鼠所在位置的儲存格座標

目前是應用在CListCtrl::OnMouseMove()裡,用來取得目前滑鼠所在位置。

     const MSG* pMsg = this->GetCurrentMessage();  
if ( pMsg == NULL ) {
return; };
CPoint ptMsg = pMsg->pt; //Get point from the message
this->ScreenToClient(&ptMsg); //Convert the point's coords to be relative to this control
LVHITTESTINFO lvItemInfo;
lvItemInfo.pt = ptMsg;
int nItem = this->SubItemHitTest(&lvItemInfo); //Row Index - 0 base
int nSubItem = 3;//lvItemInfo.iSubItem; //Col Index - 0 base

[VS]add/remove operation is impossible because the code element is read only

上google找一下,解決方法很簡單。
目前是在專案關閉的狀況下,把*.ncb刪除後就恢復正常了。

[C]快速判斷字串是否為空字串

寫法很簡單,就像下列寫法:
if(*szStr)
{
  ....
}

因為*szStr == szStr[0],且char[]是以NULL-Terminate來判定字串是否結束,所以只要判定陣列的第一個值是不是為零,就可知道是否為空字串。
如需檢查是否為空字串則在指標前面加上NOT operator即可。

相同的方法也可以應用在其他的陣列結尾檢查,例如:
for ( int i = 0 ; arrVar[i] ; i++ )
{
    ....
}
 


按照Jacob的說法,只要在陣列的結尾塞個0,或是NULL,當迴圈跑到false或是NULL的時候,就會自動結束,這樣也就減少了 n個 if檢查。

[C++]傳遞Class Member function pointer至其他Class

困擾已久的傳遞Class的Member function到其他class的問題,在今天仔細參詳了半天高手的code以後,終於把之前的疑惑解決了。

原本都知道要用第三個class來處理這問題,但由於一直搞不懂第三個要怎麼做,所以就一直寫不出來。

目前參考的方法是使用一個Base class來當做傳遞Function pointer的介面,然後一個Template class來讓內層使用的class來使用function pointer,也避開兩個互相參照的class的交互參照之問題。

處理方式如下:
Class Processer (底下稱CP)有工作要麻煩 Class Thread (底下稱CT)處理,但是為了讓Main thread不要卡住,所以CT是另外開一個Thread來處理CP要求的工作。
然而當CT在處理工作時如遇到了例外,並無法立即讓CP知道目前的狀況,所以就需要讓CP開個管道(Function Pointer)來讓CT可以在遇到問題時,回來告知CP。

原本是打算用PostMessage()來達成這樣的需求,但是CT所在的dll可能會移植到別的平台上,所以如果使用PostMessage()來做的話,那麼在移植上,還需要去針對不同平台處理。

但是此時的需求只是要把CT遇到的狀況告知給CP,並由CP來告知使用者而已,所以不會造成CT的程序block,所以這邊就採用Class的 callback方式處理。(如果會發生blocking的話,那還是只能用Windows Message的方式處理會比較好處理,不然Thread也是可以。)

底下範例使用
Class CallBackBase:
class CCallBackBase
{
    public:
        CCallBackBase(){};
        ~CCallBackBase(){};

        virtual bool Execute(void *Param) const = 0;
};




Template Class:
template < class cTarClass >
class CTestCallBack : public CCallBackBase
{
    public:
        typedef bool (cTarClass::*LPFUNC)(void *Param);

    protected:
        cTarClass *m_pTarClass;
        LPFUNC m_pTarFunc;

    public:
        CTestCallBack() {
            m_pTarClass = NULL;
        };
        ~CTestCallBack() {};

        void SetCallBack(cTarClass *cTarClassPtr, LPFUNC pFuncPtr){
            m_pTarClass = cTarClassPtr;
            m_pTarFunc = pFuncPtr;
        };

        virtual bool Execute(void *Param) const        //Execute的參數要與function pointer一致是因為操作者是使用此介面來使用function pointer
        {
            if (m_pTarClass)
            {
                return (m_pTarClass->*m_pTarFunc)(Param);
            }else{
                ::printf("ERROR: The call back funciton is not set!!");
            }
            return false;
        };
};


Class Thread:
class CProcessThread
{
    protected:    
        CCallBackBase *m_pCallBack;
        HANDLE m_hThread;

    public:
        CProcessThread(){
            m_pCallBack = NULL;
            m_hThread = NULL;
        };

        ~CProcessThread(){
            if ( m_hThread != NULL )    {
                DWORD dwExitCode = 0;
                ::TerminateThread(this->m_hThread, dwExitCode);

                HANDLE hThread = this->m_hThread;
                this->m_hThread = NULL;
                ::CloseHandle(hThread);
            }
        };

        void StartProcessThread(void)
        {
            this->m_hThread = CreateThread( NULL, 0, ProcessThread, (LPVOID)this, 0, NULL);
        };

        void SetCallBack(CCallBackBase *pCallBack){
            m_pCallBack = pCallBack;
        };

    protected:
        static DWORD WINAPI ProcessThread(LPVOID lpParam)
        {
            CProcessThread *pcmxProcessThread = (CProcessThread*) lpParam;

            return pcmxProcessThread->ProcessFunc();
        };

        DWORD ProcessFunc(void)
        {
            //Set callback data to upper class
            m_pCallBack->Execute((void*) "This message is pass from ProcessThread::ProcessFunc() in Thread!!\n");

            if ( m_hThread != NULL )    {
                DWORD dwExitCode = 0;
                ::TerminateThread(this->m_hThread, dwExitCode);

                HANDLE hThread = this->m_hThread;
                this->m_hThread = NULL;
                
                ::CloseHandle(hThread);
            }

            return 0;
        };
};



Class Process:
class CProcess
{
    protected:
        CTestCallBack<CProcess> m_CallBack;
        CProcessThread m_ProcessThread;

    public:
        CProcess() {
            m_CallBack.SetCallBack(this, &CProcess::CallBackShower);
            m_ProcessThread.SetCallBack(&m_CallBack);
        };
        ~CProcess() {};

        void StartProcess(void)
        {
            m_ProcessThread.StartProcessThread();
        };

        bool CallBackShower(void *Param)
        {
            char szMsg[512];
            strcpy(szMsg, (char*)Param);

            ::printf("The Pass message from call back is \nMsg = %s", szMsg);

            return true;
        };

};



Main program:
int _tmain(int argc, char* argv[])
{
    CProcess Process;

    Process.StartProcess();

    system("pause");
    return 0;
}


執行結果:



程式碼亂跑的問題再另外找解@@

[WINAPI]取得磁碟空間

可使用GetDiskFreeSpaceEx()來取得目前可使用的磁碟空間、總磁碟空間以及總剩餘磁碟空間。

使用方式如下:


    char szDiskDir[MAX_PATH]  = "C:\\";

    //A pointer to a variable that receives the total number of free bytes on a disk that are available to the user who is associated with the calling thread.
    //This parameter can be NULL.
    //If per-user quotas are being used, this value may be less than the total number of free bytes on a disk.
    __int64 n64AvailableFreeDiskBytes;       

    //A pointer to a variable that receives the total number of bytes on a disk that are available to the user who is associated with the calling thread.
    //This parameter can be NULL.
    //If per-user quotas are being used, this value may be less than the total number of bytes on a disk.
    //To determine the total number of bytes on a disk or volume, use IOCTL_DISK_GET_LENGTH_INFO.        
    __int64 n64TotalDiskBytes;

    //A pointer to a variable that receives the total number of free bytes on a disk.
    //This parameter can be NULL.
    __int64 n64TotalFreeDiskBytes;

    if ( !::GetDiskFreeSpaceEx(szDiskDir, (PULARGE_INTEGER) &n64AvailableFreeDiskBytes, (PULARGE_INTEGER) &n64TotalDiskBytes,
                                                            (PULARGE_INTEGER) &n64TotalFreeDiskBytes) )
    {
        printf("Fail to get disk fress space in %s (Err = %d).\n", szDiskDir, ::GetLastError());
    }else{
        printf("%s has %lld bytes available free disk.\nTotal disk size is %lld bytes.\nTotal free space is %lld bytes.\n", 
                    szDiskDir, n64AvailableFreeDiskBytes, n64TotalDiskBytes, n64TotalFreeDiskBytes);
    }

[CListCtrl]排序Contorl List內之資料

這邊以檔案清單來說明,需要的欄位有:檔案名稱、副檔名、檔案路徑。

首先先建立用來排序用的資料結構

typedef struct {
    char pszFileName[_MAX_FNAME];
    char pszExtName[_MAX_EXT];
    char pszFilePath[MAX_PATH];
} ITEMDATA, *PITEMDATA; 


再來建立給CListCtrl::SortItems()使用的Call back函式,函式內部就放需要用來sort比對的方法
Head File

friend int CALLBACK SortFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort);

Cpp File

int CALLBACK SortFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
{
    int nRetVal = 0;

    PITEMDATA pData1 = (PITEMDATA)lParam1;
    PITEMDATA pData2 = (PITEMDATA)lParam2;

    switch(lParamSort)
    {
    case 0:    // File Name
        nRetVal = strcmp(pData1->pszFileName, pData2->pszFileName);
        break;

    case 1:    // Extension File Name
        nRetVal = strcmp(pData1->pszExtName, pData2->pszExtName);
        break;

    case 2: // TerFile Path
        nRetVal = strcmp(pData1->pszFilePath, pData2->pszFilePath);
        break;

    default:
        break;
    }

    return nRetVal;
}


再來是建立接收到滑鼠事件後需要執行的函式,在函式內就把先前建立好的Call back函式,SortFunc塞給CListCtrl::SortItems()。
Head File

afx_msg void OnItemClick2Sort(NMHDR* pNMHDR, LRESULT* pResult);

Cpp File

void CFileSearchDlg::OnItemClick2Sort(NMHDR* pNMHDR, LRESULT* pResult)
{
    NMLISTVIEW *pLV = (NMLISTVIEW *) pNMHDR;
    
    m_lstFileItem.SortItems(SortFunc, pLV->iSubItem);

    *pResult = 0;
}



然後在message map鍵入

ON_NOTIFY(LVN_COLUMNCLICK, IDC_LST_FILE, OnItemClick2Sort)

來接收滑鼠點選List的欄位標題時的訊息。

以上動作完成後,就可以利用點擊欄位標題來進行排序了。

[WINDOWS]延遲載入的特定服務的方式

根據MS技術支援的文件內顯示:
1.先至服務的登陸子機碼,HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\ < 服務名稱 >
2.在此子機碼內新增 DependOnService 值(可擴充字串值)
3. 鍵入與此服務之前啟動的服務名稱
4.之後在電腦啟動時,它會使用這個項目,來確認啟動服務或服務列在這個值,再嘗試啟動相依的服務。

測試平台:Win7 x64
測試結果:有效

Ref:延遲載入的特定服務的方式

[MFC]設定RichEdit內顯示文字顏色

設定文字插入位置
//移動游標m_RichEdit.SendMessage(EM_LINEINDEX, m_RichEdit.GetLineCount(), 0);            //移到最底行m_RichEdit.SendMessage(EM_SETSEL, m_RichEdit.GetWindowTextLength(), m_RichEdit.GetWindowTextLength());    //移到最後一個字元後面

改變顯示顏色
    CHARFORMAT chFormat;
    ::ZeroMemory(&chFormat, sizeof(CHARFORMAT));        
    m_RichEdit.GetSelectionCharFormat(chFormat);     //取得目前文字格式  
    chFormat.dwMask |= CFM_COLOR;                    //設定修改顏色屬性
    chFormat.dwEffects &= ~CFE_AUTOCOLOR;        //移除 CFE_AUTOCOLOR
    chFormat.crTextColor = RGB(255, 0, 0);

    m_RichEdit.SetSelectionCharFormat(chFormat);
    m_RichEdit.ReplaceSel(str);        //因為沒有選擇文字,所以會將str塞到最後面

Memo用,可直接使用,需再新增其他功能時,要再修改與測試。

Build docker image from multiple build contexts

Build docker image from multiple build contexts ...