티스토리 뷰

프로그래밍/MFC

가상리스트

에어버스 2019. 5. 20. 14:18

가상ListCtrl, 가상리스트, 가상CListCtrl, 가상 list, 가상 list contol

CListCtrl 을 Report 타입으로 사용할때 1초단마다 ListCtrl 을 갱신하면 깜박임이 발생한다.
CListCtrl 스타일에서 LVS_EX_DOUBLEBUFFER 를 지정하면 CListCtrl 데이터 영역은 깜박임이 발생하지 않지만, 스크롤바가 중간에 있는 경우에는 스크롤바가 깜박이는 문제가 발생하여 가상ListCtrl 을 사용해 본다.

참고추가> 아래 FindItem() 관련 참고 

 

데이터를 배열에 저장하고, 1초마다 배열에서 데이터를 가져와 보여지는 데이터만 갱신한다고 한다.
그러다 보니, GetItemText() 사용 가능하지만, SetItemText() 사용할 수 없다.

1. Owner Data 속성을 True 로 변경 (주의 : Owner Draw 아님)
2. LVN_GETDISPINFO 메시지 추가
3. 메시치 처리 함수에 보여줄 데이터 지정
4. 타이머 등으로 주기적으로 m_ListMax.SetItemCountEx() 호출해서 데이터를 갱신

Ower Data 기본값인 False 면 위 그리처럼 Item과 그림이 보이지만, True 로 하면 아래 그림처럼 사라져 Item 앞에 이미지와 체크박스 기능을 사용하지 못하고 있다. --- 사용 가능함, 참고추가2> 참고.

CListCtrl 속성에서 Ower Data 를 True 로 지정한다.

이벤트 처리기에서

LVN_GETDISPINFO 메시지를 추가한다.

아래 코드 32행 item.iSubItem swich()에서 각 열에 보여줄 데이터가 저장된 배열을 지정한다. 
<코드1>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#define N 3
struct _점수 {
    CString 번호, 이름, 과목;
    int 점수;
} Data[N] = {
    { TEXT("1"), TEXT("AAA"), TEXT("체육"), 60 },
    { TEXT("2"), TEXT("BBB"), TEXT("음악"), 50 },
    { TEXT("3"), TEXT("CCC"), TEXT("과학"), 70 }
}; // 외부변수로 선언
 
BOOL CMFCApplication7Dlg::OnInitDialog()
{
...
        m_ListCtrl.InsertColumn(0, _T("번호"), LVCFMT_LEFT, 80);
    m_ListCtrl.InsertColumn(1, _T("이름"), LVCFMT_LEFT, 80);
    m_ListCtrl.InsertColumn(3, _T("과목"), LVCFMT_LEFT, 80);
    m_ListCtrl.InsertColumn(4, _T("점수"), LVCFMT_LEFT, 80);
    SetTimer(11000NULL);
    return TRUE;
}
 
void CMFCApplication7Dlg::OnLvnGetdispinfoList1(NMHDR* pNMHDR, LRESULT* pResult)
{
    NMLVDISPINFO* pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
    // TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.
    LVITEM& item = pDispInfo->item;
    int nItem = item.iItem;
    CString str;
 
    if (item.mask & LVIF_TEXT)
    {
        switch (item.iSubItem)
        {
        case 0:
            _tcsncpy_s(item.pszText, Data[nItem].번호.GetLength() + 1, Data[nItem].번호, Data[nItem].번호.GetLength() + 1);
            break;
        case 1:
            _tcsncpy_s(item.pszText, Data[nItem].이름.GetLength() + 1, Data[nItem].이름, Data[nItem].이름.GetLength() + 1);
            break;
        case 2:
            _tcsncpy_s(item.pszText, Data[nItem].과목.GetLength() + 1, Data[nItem].과목, Data[nItem].과목.GetLength() + 1);
            break;
        case 3:
            str.Format(TEXT("%d"), Data[nItem].점수++); // 점수를 1씩 증가 시켜 변경된 점수를 보여준다.
            _tcsncpy_s(item.pszText, str.GetLength() + 1, str, str.GetLength() + 1);
            break;
        }
    }
    *pResult = 0;
}
 
 
void CMFCApplication7Dlg::OnTimer(UINT_PTR nIDEvent)
{
    // TODO: 여기에 메시지 처리기 코드를 추가 및/또는 기본값을 호출합니다.
    m_ListCtrl.SetItemCountEx(N, LVSICF_NOSCROLL); // 갱신할 데이터 수 지정
    CDialogEx::OnTimer(nIDEvent);
}
 
 
cs

56행에서 LVICF_NOSCROLL 을 지정하면 스크롤바 위치를 유지한 상태로 데이터 갱신이 된다.

실행결과>

 

참고추가>
일반 CListCtrl 에서 FindItem() 를 호출(12행)하면 해당 item 을 찾아 nItem 값을 반환해준다.
그런데, 가상리스트인 경우 FindItem() 호출하면 무조건 기본값인 0 이 반환된다. (20행)
가상리스트에서 FindItem() 호출하면 LVN_ODFINDITEM 이벤트가 발생하므로 이벤트 등록(3행)하고 이벤트 처리기(16행)에서 item 값을 찾아 반환해줘야 한다. (21~43행) 만약, 데이터를 배열에 저장해두고 가상리스트를 사용하고 있으면 해당 item 이 저장된 배열 index를 반환하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
BEGIN_MESSAGE_MAP(CDlg, CDialogEx)
...
ON_NOTIFY(LVN_ODFINDITEM, IDC_LIST, &CDlg::OnLvnOdfinditemList)
END_MESSAGE_MAP()
 
int CDlg::GetIndex(CString strCompanyCode, int nIndex)
{
    LVFINDINFO lv;
    lv.flags = LVFI_STRING;
    lv.psz = strCompanyCode;
    //return m_ListCtrl.FindItem(&lv, nIndex);
    int a = m_ListCtrl.FindItem(&lv, nIndex);
    return a;
}
 
void CDlg::OnLvnOdfinditemList(NMHDR *pNMHDR, LRESULT *pResult)
{
    LPNMLVFINDITEM pFindInfo = reinterpret_cast<LPNMLVFINDITEM>(pNMHDR);
    // TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.
    //*pResult = 0; // 기본코드
    *pResult = -1// 기본값으로 항목을 못찾을때 반환할 -1 을 저장
 
    if ((pFindInfo->lvfi.flags & LVFI_STRING) == 0)
    {
        return;
    }
    CString str검색어 = pFindInfo->lvfi.psz;
    int iStartPos = pFindInfo->iStart;
    if (iStartPos >= m_ListCtrl.GetItemCount())
        iStartPos = 0;
    int curPos = iStartPos;
    
    for (_map주문정보::iterator it = m_map주문정보.begin(); it != m_map주문정보.end(); it++)
    {
        if (it->second.str종목코드 == str검색어)
        {
            *pResult = it->second.nItem;
            break;
        }
        curPos++;
        if (curPos >= m_ListCtrl.GetItemCount())
            curPos = 0;
    }
}
cs

추가2>
가상 리스트에 이미지와 체크박스를 추가하기 (4~28행)
위에서 LVN_GETDISPINFO 이벤트 처리기에서 문자열 처리하고 나서 뒷부분에 아래 코들 추가한다.
주의> 보통 ClistCtrl 은 체크박스 클릭하면 체크되어 다시 클릭하면 해제 되지만, 가상 리스트에서는 클릭 동작이 안되어 item 클릭했을때나 ContextMenu 에서 처리해야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
void CDlg::OnLvnGetdispinfoList(NMHDR *pNMHDR, LRESULT *pResult)
{
...
        if (item.mask & LVIF_IMAGE) // 이미지와 체크박스 처리
    {
        //////////
        // 이미지 추가하려면 여기서 한다.
        // pItem->iImage = m_database[itemid].m_image;
        //////////
 
        /////////////////////
        // 여기서 체크박스 표시한다. LVS_EX_CHECKBOXES 리스트컨트롤 속성 필요함.
        //To enable check box, we have to enable state mask...
        item.mask |= LVIF_STATE;
        item.stateMask = LVIS_STATEIMAGEMASK;
 
        if (it->second.str체크표시 == _종목Checked문자) //m_database[itemid].m_checked)
        {
            //Turn check box on..
            item.state = INDEXTOSTATEIMAGEMASK(2);
        }
        else
        {
            //Turn check box off
            item.state = INDEXTOSTATEIMAGEMASK(1);
        }
        /////////////////////
    }
 
    *pResult = 0;
}
cs

실행화면>

 

참고> http://blog.naver.com/PostView.nhn?blogId=killamaster&logNo=150036054857

https://docs.microsoft.com/ko-kr/cpp/mfc/virtual-list-controls?view=vs-2019

추가1>
API로 구현할때는 아래처럼 해야 할듯.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
case WM_NOTIFY:
        switch (((LPNMHDR) lParam)->code)
        {
        case LVN_GETDISPINFO:
            {
                NMLVDISPINFO* plvdi = (NMLVDISPINFO*)lParam;    
                switch (plvdi->item.iSubItem)
                {
                case 0:
                    // rgPetInfo is an array of PETINFO structures.
                    plvdi->item.pszText = rgPetInfo[plvdi->item.iItem].szName;
                    break;
 
                case 1:
                    plvdi->item.pszText = rgPetInfo[plvdi->item.iItem].szBreed;
                    break;
 
                case 2:
                    plvdi->item.pszText = rgPetInfo[plvdi->item.iItem].szGender;
                    break;
 
                case 3:
                    plvdi->item.pszText = rgPetInfo[plvdi->item.iItem].szPrice;
                    break;
 
                default:
                    break;
                }
                return TRUE;
            }
      // More notifications...
      }
cs

추가2>
위 <코드1>에서 OnLvnGetdispinfoList1() 가 빈번히 호출되므로 지역변수를 클래스 멤버변수로 사용하면 좋을듯....원래 LVITEM 지역변수를 참조형 변수로 사용했으나 멤버변수로 하면 포인터 변수(아래 코드 5행)로 바꿔야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class CMFCApplication7Dlg : public CDialogEx
{
// 생성입니다.
private:
    LVITEM* m_item;
...
}
 
 
void CMFCApplication7Dlg::OnLvnGetdispinfoList1(NMHDR* pNMHDR, LRESULT* pResult)
{
    NMLVDISPINFO* pDispInfo = reinterpret_cast<NMLVDISPINFO*>(pNMHDR);
    // TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.
    m_item = &(pDispInfo)->item;
    int nItem = m_item->iItem;
    CString str;
 
    if (m_item->mask & LVIF_TEXT)
    {
        switch (m_item->iSubItem)
        {
        case 0:
            _tcsncpy_s(m_item->pszText, Data[nItem].번호.GetLength() + 1, Data[nItem].번호, Data[nItem].번호.GetLength() + 1);
            break;
...
}
cs
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30