Notice
Recent Posts
Recent Comments
Link
«   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
Archives
Today
Total
관리 메뉴

codingfarm

7-6. 컨트롤 - 리스트 박스 본문

Windows/윈도우즈 API

7-6. 컨트롤 - 리스트 박스

scarecrow1992 2021. 12. 27. 17:06

https://docs.microsoft.com/en-us/windows/win32/controls/about-list-boxes

 

0. 개요

선택 가능한 여러개의 항목들을 나열해놓고 그 중 하나 이상을 선택하도록 하는 컨트롤

선택가능한 항목은 주로 문자열임

listbox 라는 윈도우 클래스를 사용

 

1. 스타일

스타일 설명
LBS_MULTIPLESEL 여러개의 항목을 선택할 수 있도록 한다. 이 스타일을 적용하지 않으면 디폴트로 하나만 선택할 수 있다.
LBS_NOTIFY 사용자가 목록중 하나를 선택했을 때 부모 윈도우로 통지 메시지를 보내도록 한다.
LBS_SORT 추가된 항목들을 자동 정렬하도록 한다.
LBS_OWNERDRAW 문자열이 아닌 비트맵이나 그림을 넣을 수 있도록 한다.
LBS_STANDARD LBS_NOTIFY | LBS_SORT | WS_BORDER

부모 윈도우로 통지 메시지를 보내기 위해 LBS_NOTIFY는 거의 필수적으로 선택한다.

그리고 LBS_MULTIPLESEL 이나 LBS_SORT 는 필요 할 때 선택한다.

 

리스트 박스로 명령을 보내기 위한 메시지나, 부모 윈도우에 알림을 보내기 위한 notification message는 MSDN을 참고하되, 자주 쓰이는건 아래와 같다.

 

2. Messages to List Boxes

메시지 설명
LBN_DBLCLK 리스트 박스를 더블클릭하였다.
LBN_ERRSPACE 메모리가 부족하다.
LBN_KILLFOCUS 키보드 포커스를 잃었다.
LBN_SELCANCEL 사용자가 선택을 취소하였다.
LBN_SELCHANGE 사용자에 의해 선택이 변경되었다.
LBN_SETFOCUS 키보드 포커스를 얻었다.

 

 

3. Notification Messages from List Boxes

메시지 설명
LB_ADDSTRING 리스트 박스에 항목을 추가한다. lParam으로 추가하고자 하는 문자열의 번지를 넘겨주면 된다.
LB_DELETESTRING 항목을 삭제한다. wParam으로 항목의 번호를 넘겨주며 남은 문자열수를 리턴한다.
LB_GETCURSEL 현재 선택된 항목의 번호(Index)를 조사해준다.
LB_GETTEXT 지정한 항목의 문자열을 읽는다. wParam에 항목 번호, lParam에 문자열 버퍼의 번지를 넘겨주면 버퍼에 문자열을 채워준다.
LB_GETCOUNT 항목의 개수를 조사한다.
LB_SETCURSEL wParam이 지정한 항목을 선택하도록 한다.

 

 

리스트 박스를 만드는 간단한 예제는 아래와 같다.

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#include <windows.h>
#include <tchar.h>
 
#define ID_LISTBOX 100
 
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HINSTANCE g_hInst;
LPCTSTR lpszClass = TEXT("Menu");
 
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance
    , LPSTR lpszCmdParam, int nCmdShow)
{
    HWND hWnd;
    MSG Message;
    WNDCLASS WndClass;
    g_hInst = hInstance;
 
    WndClass.cbClsExtra = 0;
    WndClass.cbWndExtra = 0;
    WndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    //WndClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
    WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
    WndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    WndClass.hInstance = hInstance;
    WndClass.lpfnWndProc = (WNDPROC)WndProc;
    WndClass.lpszClassName = lpszClass;
    WndClass.lpszMenuName = NULL;
    WndClass.style = CS_HREDRAW | CS_VREDRAW;
    RegisterClass(&WndClass);
 
    hWnd = CreateWindow(lpszClass, lpszClass, WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
        NULL, (HMENU)NULL, hInstance, NULL);
    ShowWindow(hWnd, nCmdShow);
    
    while (GetMessage(&Message, 000)) {
        TranslateMessage(&Message);
        DispatchMessage(&Message);
    }
    return Message.wParam;
}
 
 
WCHAR Items[][15= { L"Apple", L"Orange", L"Melon", L"Graph", L"Strawberry" };
WCHAR str[128];
HWND hList;
LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
    int i;
    switch (iMessage) {
    case WM_CREATE:
        hList = CreateWindow(L"listbox"NULL, WS_CHILD | WS_VISIBLE | WS_BORDER |
            LBS_NOTIFY, 1010100200, hWnd, (HMENU)ID_LISTBOX, g_hInst, NULL);
        for (i = 0; i < 5; i++)
            SendMessage(hList, LB_ADDSTRING, 0, (LPARAM)Items[i]);
        return 0;
    case WM_COMMAND:
        switch (LOWORD(wParam)) {
        case ID_LISTBOX:
            switch (HIWORD(wParam)) {
            case LBN_SELCHANGE:
                i = SendMessage(hList, LB_GETCURSEL, 00);
                SendMessage(hList, LB_GETTEXT, i, (LPARAM)str);
                SetWindowText(hWnd, str);
                break;
            }
        }
        return 0;
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }
    return(DefWindowProc(hWnd, iMessage, wParam, lParam));
}
cs

 

 

 

4. 다양한 예제들

여러개의 항목을 선택하고 윈도우 title에 선택된 항목들의 이름을 연이어 띄우는 방법을 알아본다.

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#include <windows.h>
#include <tchar.h>
#include <vector>
 
#define ID_LISTBOX 100
 
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HINSTANCE g_hInst;
LPCTSTR lpszClass = TEXT("Menu");
 
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance
    , LPSTR lpszCmdParam, int nCmdShow)
{
    HWND hWnd;
    MSG Message;
    WNDCLASS WndClass;
    g_hInst = hInstance;
 
    WndClass.cbClsExtra = 0;
    WndClass.cbWndExtra = 0;
    WndClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
    WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
    WndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    WndClass.hInstance = hInstance;
    WndClass.lpfnWndProc = (WNDPROC)WndProc;
    WndClass.lpszClassName = lpszClass;
    WndClass.lpszMenuName = NULL;
    WndClass.style = CS_HREDRAW | CS_VREDRAW;
    RegisterClass(&WndClass);
 
    hWnd = CreateWindow(lpszClass, lpszClass, WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
        NULL, (HMENU)NULL, hInstance, NULL);
    ShowWindow(hWnd, nCmdShow);
 
    while (GetMessage(&Message, 000)) {
        TranslateMessage(&Message);
        DispatchMessage(&Message);
    }
    return Message.wParam;
}
 
WCHAR Items[][15= { L"Apple", L"Orange", L"Melon", L"Graph", L"Strawberry" };
WCHAR str[128];
HWND hList;
LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
    int i;
    switch (iMessage) {
    case WM_CREATE:
        hList = CreateWindow(L"listbox"NULL, WS_CHILD | WS_VISIBLE | WS_BORDER |
            LBS_NOTIFY | LBS_MULTIPLESEL, 1010100200, hWnd, (HMENU)ID_LISTBOX, g_hInst, NULL);
        for (i = 0; i < 5; i++)
            SendMessage(hList, LB_ADDSTRING, 0, (LPARAM)Items[i]);
        return 0;
    case WM_COMMAND:
        switch (LOWORD(wParam)) {
        case ID_LISTBOX:
            switch (HIWORD(wParam)) {
            case LBN_SELCHANGE:
                WCHAR title[255= L"";
                int ItemsSize = sizeof(Items) / sizeof(Items[0]);
                for (int j = 0; j < ItemsSize; j++) {
                    if (SendMessage(hList, LB_GETSEL, j, 0> 0) {
                        SendMessage(hList, LB_GETTEXT, j, (LPARAM)str);
                        wcscat(title, str);
                        wcscat(title, L" + ");
                    }
                }
                SetWindowText(hWnd, title);
                break;
            }
        }
        return 0;
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }
    return(DefWindowProc(hWnd, iMessage, wParam, lParam));
}
cs

항목 선택에 변화가 발생 할 때마다  전달되는 메시지인 LBN_SELCHANGE 내에서 모든 항목에 대해 선형 탐색하며 해당 항목의 선택 여부를 LB_GETSEL 메시지로 알아내는 방식으로 구현

그런데 list가 바뀔 때 마다 선형 탐색을 해야하는 번거로움이 있다.

 

그러므로 아래처럼 개선하여 사용 할 수 있다.

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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#include <windows.h>
#include <tchar.h>
#include <vector>
 
#define ID_LISTBOX 100
 
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HINSTANCE g_hInst;
LPCTSTR lpszClass = TEXT("Menu");
 
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance
    , LPSTR lpszCmdParam, int nCmdShow)
{
    HWND hWnd;
    MSG Message;
    WNDCLASS WndClass;
    g_hInst = hInstance;
 
    WndClass.cbClsExtra = 0;
    WndClass.cbWndExtra = 0;
    WndClass.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
    WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
    WndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    WndClass.hInstance = hInstance;
    WndClass.lpfnWndProc = (WNDPROC)WndProc;
    WndClass.lpszClassName = lpszClass;
    WndClass.lpszMenuName = NULL;
    WndClass.style = CS_HREDRAW | CS_VREDRAW;
    RegisterClass(&WndClass);
 
    hWnd = CreateWindow(lpszClass, lpszClass, WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
        NULL, (HMENU)NULL, hInstance, NULL);
    ShowWindow(hWnd, nCmdShow);
 
    while (GetMessage(&Message, 000)) {
        TranslateMessage(&Message);
        DispatchMessage(&Message);
    }
    return Message.wParam;
}
 
WCHAR Items[][15= { L"Apple", L"Orange", L"Melon", L"Graph", L"Strawberry" };
WCHAR str[128];
HWND hList;
LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
    int i;
    switch (iMessage) {
    case WM_CREATE:
        hList = CreateWindow(L"listbox"NULL, WS_CHILD | WS_VISIBLE | WS_BORDER |
            LBS_NOTIFY | LBS_MULTIPLESEL, 1010100200, hWnd, (HMENU)ID_LISTBOX, g_hInst, NULL);
        for (i = 0; i < 5; i++)
            SendMessage(hList, LB_ADDSTRING, 0, (LPARAM)Items[i]);
        return 0;
    case WM_COMMAND:
        switch (LOWORD(wParam)) {
        case ID_LISTBOX:
            switch (HIWORD(wParam)) {
            case LBN_SELCHANGE:
                WCHAR title[255= L"";
                int listBoxSize = SendMessage(hList, LB_GETCOUNT, 00);
                int cnt;
                int* buf = new int[SendMessage(hList, LB_GETSELCOUNT, 00)];
                cnt = SendMessage(hList, LB_GETSELITEMS, (WPARAM)&cnt, (LPARAM)buf);
 
                wsprintf(title, L"%d : ", cnt);
 
                for (i = 0; i < cnt; i++) {
                    SendMessage(hList, LB_GETTEXT, (WPARAM)buf[i], (LPARAM)str);
                    wcscat(title, str);
                    wcscat(title, L" + ");
                }
                SetWindowText(hWnd, title);
                delete [] buf;
                break;
            }
        }
        return 0;
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }
    return(DefWindowProc(hWnd, iMessage, wParam, lParam));
}
cs

LB_GETSELITEMS 메시지로 선택된 항목들의 index들을 버퍼에 저장한후, LB_GETTEXT 메시지로 항목들의 정확한 이름을 얻어오는 방식이다. 

LB_GETSELITEMS 의 사용 방법이 좀 의문이다.

1
cnt = SendMessage(hList, LB_GETSELITEMS, (WPARAM)&cnt, (LPARAM)buf);
cs

설명을 보면 WPARAM과 반환값을 통해 선택된 항목의 갯수를 알 수 있다 나오는데, 두개중 하나라도 빠지면 제대로된 갯수를 가저올 수 없음을 확인했다.

그리고 LPARAM을 통해 전달되는 index들의 크기는 적당히 int형과 똑같음을 확인했다.

 이방법도 마찬가지로 결국 선형탐색을 통해서 일일이 이름을 확인한다는 점에서 성능이 아쉽다.

 

이런게 싫다면 hash와 notification message를 적절히 융합하면 성능을 적당히 항상 시킬 수 있으리라 생각된다.

 

 

 

 

 

https://stackoverflow.com/questions/42438135/c-winapi-listbox-getting-selected-item-using-lb-getsel-lb-getcursel

 

 

 

'Windows > 윈도우즈 API' 카테고리의 다른 글

7-8. 스크롤 바  (0) 2021.12.29
7-7. 콤보 박스  (0) 2021.12.28
7-4. 컨트롤 - 에디트  (0) 2021.12.27
7-3. 컨트롤 - 라디오 버튼  (0) 2021.12.27
7-2. 컨트롤 - 체크박스 버튼  (0) 2021.12.26
Comments