<menu id="w8yyk"><menu id="w8yyk"></menu></menu>
  • <dd id="w8yyk"><nav id="w8yyk"></nav></dd>
    <menu id="w8yyk"></menu>
    <menu id="w8yyk"><code id="w8yyk"></code></menu>
    <menu id="w8yyk"></menu>
    <xmp id="w8yyk">
    <xmp id="w8yyk"><nav id="w8yyk"></nav>
  • 網站首頁 > 物聯資訊 > 技術分享

    clistctrl 虛擬列表

    2016-09-28 00:00:00 廣州睿豐德信息科技有限公司 閱讀
    睿豐德科技 專注RFID識別技術和條碼識別技術與管理軟件的集成項目。質量追溯系統、MES系統、金蝶與條碼系統對接、用友與條碼系統對接

    一、什么是虛擬列表控件

    虛擬列表控件是指帶有LVS_OWNERDATA風格的列表控件。。

    二、為什么使用虛擬列表控件

    我們知道,通常使用列表控件CListCtrl,需要調用InsertItem把要顯示的數據插入列表中,之后我們就不必關心數據在哪里了,這是因為控件自己開辟了內存空間來保存這些數據。現在假設我們要顯示一個數據庫,里面的信息量很大,有幾十萬條記錄。通常有兩種方法解決這個問題:1是僅僅在 ListCtrl中插入少量的數據,比如100個,然后通過[上一頁][下一頁]兩個按鈕進行控制,某一時刻顯示的只是從xxx到xxx+100之間的記錄。2是把所有數據全部插入到ListCtrl中,然后讓用戶通過滾動來查看數據。無疑,很多用戶喜歡采用第二種方式,特別是對于已經排序的數據,用戶只需用鍵盤輸入某行的開頭字符,就可以快速定位到某一行。但是,如果這樣做,InsertItem插入數據的過程將是很漫長的,而且用戶會看到 ListCtrl刷新速度也很慢,而且所有數據都位于內存中消耗了大量的內存,當數據多達上萬以后幾乎是不能忍受的。

    為此,mfc特別提供了虛擬列表的支持。一個虛擬列表看起來和普通的ListCtrl一樣,但是不用通過InsertItem來插入數據,它僅僅知道自己應該顯示多少數據。但是它如何知道要顯示什么數據呢?秘密就在于當列表控件需要顯示某個數據的時候,它向父窗口要。假設這個列表控件包含100個元素,第10到20個元素(行)是可見的。當列表控件重畫的時候,它首先請求父窗口給它第10個元素的數據,父窗口收到請求以后,把數據信息填充到列表提供的一個結構中,列表就可以用來顯示了,顯示第10個數據后,列表會繼續請求下一個數據。

    在虛擬的樣式下,ListCtrl可以支持多達DWORD個數據項。(缺省的listctrl控件最多支持int個數據項)。但是,虛擬列表的最大優點不在于此,而是它僅僅需要在內存中保持極少量的數據,從而加快了顯示的速度。所以,在使用列表控件顯示一個很大的數據庫的情況下,采用虛擬列表最好不過了。

    不僅CListCtrl提供虛擬列表的功能, MFC的CListView類也有同樣的功能。

    三、虛擬列表控件的消息


    虛擬列表總共發送三個消息給父窗口:當它需要數據的時候,它發送LVN_GETDISPINFO消息。這是最重要的消息。當用戶試圖查找某個元素的時候,它發送LVN_ODFINDITEM消息;還有一個消息是LVN_ODCACHEHINT,用來緩沖數據,基本上很少用到這個消息。

    虛擬列表控件使用起來非常簡單。它總共只有三個相關的消息,如果你直接使用CListCtrl,應該在對話框中響應這三個消息。如果你使用CListCtrl派生類,可以在派生類中響應這三個消息的反射消息。這三個消息分別是:

      (1)LVN_GETDISPINFO 控件請求某個數據
      (2)LVN_ODFINDITEM  查找某個數據
      (3)LVN_ODCACHEHINT 緩沖某一部分數據

    我們必須響應的消息是(1),多數情況下要響應(2),極少數的情況下需要響應(3)

    四、如何使用虛擬列表控件

    1、首先要創建控件,創建一個虛擬列表和創建一個正常的 CListCtrl差不多。先在資源編輯器里面添加一個list control資源。然后選中"Owner data"屬性,然后給它捆綁一個CListCtrl變量。添加列,添加imagelist等都和使用正常的listctrl一樣。

    2、給虛擬列表添加元素。假設 m_list 是這個列表的控制變量。通常的情況下這樣添加數據:

    m_list.InsertItem(0, _T("Hello world"));

    但是對于虛擬列表,不能這么做。只需告訴列表有多少個數據:

    //總共顯示100行
    m_list.SetItemCount(100);

    3、處理它的通知消息。

    五、如何響應虛擬列表的消息

    1、處理 LVN_GETDISPINFO 通知消息

    當虛擬列表控件需要某個數據的時候,它給父窗口發送一個 LVN_GETDISPINFO通知消息,表示請求某個數據。因此列表的所有者窗口(或者它自己)必須處理這個消息。例如派生類的情況 (CMyListCtrl是一個虛擬列表類對象):

    //這里處理的是反射消息
    BEGIN_MESSAGE_MAP(CMyListCtrl, CListCtrl)
       //{{AFX_MSG_MAP(CMyListCtrl)
       ON_NOTIFY_REFLECT(LVN_GETDISPINFO, OnGetdispinfo)
       //}}AFX_MSG_MAP
    END_MESSAGE_MAP()

    在LVN_GETDISPINFO的處理函數中,必須首先檢查列表請求的是什么數據,可能的值包括:

    (1)LVIF_TEXT   必須填充 pszText
    (2)LVIF_IMAGE  必須填充 iImage
    (3)LVIF_INDENT 必須填充 iIndent
    (4)LVIF_PARAM  必須填充 lParam
    (5)LVIF_STATE  必須填充 state

    根據它的請求,填充所需的數據即可。

    //================= 例子代碼=====================================

    下面的給出一個例子,填充的是列表所需的某個數據項的文字以及圖像信息:

    LV_DISPINFO* pDispInfo = (LV_DISPINFO*)pNMHDR;
    LV_ITEM* pItem= &(pDispInfo)->item;

    int iItemIndx= pItem->iItem;

    if (pItem->mask & LVIF_TEXT) //字符串緩沖區有效
    {
        switch(pItem->iSubItem){
            case 0: //填充數據項的名字
                lstrcpy(pItem->pszText,m_Items[iItemIndx].m_strItemText);
                break;
            case 1: //填充子項1
                lstrcpy(pItem->pszText,m_Items[iItemIndx].m_strSubItem1Text);
                break;
            case 2: //填充子項2
                lstrcpy(pItem->pszText,m_Items[iItemIndx].m_strSubItem2Text);
                break;
        }
    }

    if (pItem->mask & LVIF_IMAGE) //是否請求圖像
            pItem->iImage= m_Items[iItemIndx].m_iImageIndex;

    甚至連某行數據是否選中(當有checkbox的情況下)的信息也需要由用戶自己來維護,例如:
    //是否顯示該行的選擇信息?
    if(IsCheckBoxesVisible()) //自定義函數
    {
        pItem->mask |= LVIF_STATE;
        pItem->stateMask = LVIS_STATEIMAGEMASK;

        if(m_database[itemid].m_checked)
        {
             pItem->state = INDEXTOSTATEIMAGEMASK(2);
        }
        else
        {
             pItem->state = INDEXTOSTATEIMAGEMASK(1);
         }
    }


    2、處理 LVN_ODFINDITEM 消息

    在資源管理器里面,定位到某個文件夾,會顯示很多文件,如果按下鍵盤的‘A’,則資源管理器會自動找到名字以 'A'打頭的文件夾或者文件, 并選擇該文件。繼續按 A,如果還有其它名字以'A'打頭的文件,則下一個文件被選中。如果輸入 "AB",則 'AB'打頭的文件被選中。這就是列表控件的自動查找功能。

    當虛擬列表收到一個LVM_FINDITEM消息,它也會發送這個消息通知父窗口查找目標元素。要搜索的信息通過 LVFINDINFO 結構給出。它是 NMLVFINDITEM 結構的一個成員。當找到要搜索的數據后,應該把該數據的索引(行號)返回,如果沒有找到,則返回-1。

    以對話框為例,響應函數大致如下:

    //================= 例子代碼=====================================
    void CVirtualListDlg::OnOdfinditemList(NMHDR* pNMHDR, LRESULT* pResult)
    {
        // pNMHDR 里面是要查找的元素的信息
        // 要選中的目標元素的行號最后要保存在 pResult 中, 這是關鍵!

        NMLVFINDITEM* pFindInfo = (NMLVFINDITEM*)pNMHDR;

       

        *pResult = -1;

        //是否按照文字查找?
        if( (pFindInfo->lvfi.flags & LVFI_STRING) == 0 )
        {
            return;
        }

        //這是我們要找的字符串
        CString searchstr = pFindInfo->lvfi.psz;

        int startPos = pFindInfo->iStart;//保存起始位置

        //判斷是否最后一行
        if(startPos >= m_list.GetItemCount())
            startPos = 0;

        int currentPos=startPos;
       
        //開始查找
        do
        {       
            if( _tcsnicmp(m_database[currentPos].m_name,
                     searchstr, searchstr.GetLength()) == 0)
            {
                //選中這個元素,停止查找
                *pResult = currentPos;
                break;
            }

            currentPos++;

            //從頭開始
            if(currentPos >= m_list.GetItemCount())
                currentPos = 0;

        }while(currentPos != startPos);       
    }

    顯然,如果數據很多,必須實現一個快速查找的方法。

    關于pFindInfo->lvfi里面的信息的詳細說明可以參考 MSDN。

    3、處理 LVN_ODCACHEHINT 消息。

    假如我們從數據庫或者其它地方讀取數據的速度比較慢,則可以利用這個消息,批量讀取一些數據,然后根據請求,逐個提供給虛擬列表。LVN_ODCACHEHINT消息的用途就是給程序一個緩沖數據的機會。以提高程序的性能。

    //================= 例子代碼=====================================
    使用 ClassWizard 重載 OnChildNotify 函數,檢查是否 LVN_ODCACHEHINT 消息,然后準備緩沖數據:

    NMLVCACHEHINT* pcachehint=NULL;

    NMHDR* phdr = (NMHDR*)lParam;

    if(phdr->code == LVN_ODCACHEHINT)
    {
         pcachehint= (NMLVCACHEHINT*) phdr;
         //自定義函數,準備指定范圍的數據到緩沖區
         PrepCache(pcachehint->iFrom, pcachehint->iTo);
    }
    else ...

    注意,如果消息不是 LVN_ODCACHEHINT,則要傳遞給基類進行處理。

    五、如何修改ListCtrl顯示的數據。

    由于是程序自己維護數據,所以只需修改數據庫中的數據,然后調用CListCtrl::RedrawItems函數進行重畫即可。

    六、數據的選擇狀態和選擇框

    CListCtrl可以顯示checkbox選擇框。有些情況下是很有用的。對于正常的listctrl,用戶可以用鼠標來修改某個元素的選擇狀態,但是對于虛擬列表就不行了。必須自己處理一些消息,然后自己保存數據的選中狀態:

    void CVirtualListDlg::ToggleCheckBox(int item)
    {
        m_database[item].m_checked = !m_database[item].m_checked;
        m_list.RedrawItems(item, item);
    }

    處理 LVN_KEYDOWN消息,添加對空格鍵 的響應,用于切換選擇狀態:

    void CVirtualListDlg::OnKeydownList(NMHDR* pNMHDR, LRESULT* pResult)
    {
        LV_KEYDOWN* pLVKeyDown = (LV_KEYDOWN*)pNMHDR;

        if( pLVKeyDown->wVKey == VK_SPACE )
        {
           int item = m_list.GetSelectionMark();
            if(item != -1)
                ToggleCheckBox(item);
        }

        *pResult = 0;
    }

    然后處理 NM_CLICK 消息:

    void CVirtualListDlg::OnClickList(NMHDR* pNMHDR, LRESULT* pResult)
    {
        NMLISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;

        LVHITTESTINFO hitinfo;
        hitinfo.pt = pNMListView->ptAction;

        int item = m_list.HitTest(&hitinfo);

        if(item != -1)
        {
            //看看鼠標是否單擊在 check box上面了?
            if( (hitinfo.flags & LVHT_ONITEMSTATEICON) != 0)
            {
                ToggleCheckBox(item);
            }
        }
       
        *pResult = 0;
    }

    七、備注:

        1、虛擬列表無法進行排序。

        2、虛表的一個優點是容易保持和數據庫的同步。修改數據庫中的數據,然后重畫list十分容易而且高效。

        3、虛表的另一個優點是可以根據需要產生數據。比如在某一列加上行號。

    http://blog.vckbase.com/iwaswzq/archive/2006/07/07/21113.html
    http://www.codeproject.com/KB/list/virtuallist.aspx

    RFID管理系統集成商 RFID中間件 條碼系統中間層 物聯網軟件集成
    最近免费观看高清韩国日本大全