티스토리 뷰
가상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(1, 1000, NULL);
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 |