Win32 - API - Control
[TOC]
컨트롤
컨트롤이란
- 컨트롤이란 사용자와 인터렉션을 하는 버튼, 텍스트 입력란, 리스트 등을 말한다.
- 컨트롤은 각각 아이디가 부여된다.
- VS 리소스 관리 툴을 통해 중복되지 않게 자동으로 입력된다.
- 컨트롤도 하나의 작은 윈도우이다.
- 화면상의 일정한 영역을 차지한다.
- 자신의 고유 메시지를 처리할 수 있다.
- 윈도우이기는 하지만 홀로 사용될 수는 없고 자식 윈도우로 사용해야 한다.
- 윈도우를 생성하려면 CreateWindow()함수를 통해야하는데 컨트롤은 운영체제가 제공해주기 때문에 윈도우 클래스를 만들어 사용할 필요없이 미리 정의된 클래스를 사용하면 된다.
- RegisterClassExW() 함수를 통해 윈도우 클래스 WNDCLASSEXW를 따로 만들어 등록시킬 필요없이 CreateWindow() 함수의 첫번째 인수로 미리 정의된 클래스(문자열)를 주면 해당 컨트롤을 만들 수 있다.
- 컨트롤은 자신에게 이벤트가 발생했을 때 부모 윈도우로 통지 메시지(Notification Message)를 보낸다.
- 버튼은 클릭 시 WM_COMMAND 메시지를 보낸다.
- HIWORD(wParam) : 통지 코드
- LOWORD(wParam) : 컨트롤의 ID
- lParam : 메시지를 보낸 차일드 윈도우의 메시지 핸들
- WM_COMMAND 메시지의 LOWORD(wParam)으로 컨트롤 ID, 메뉴 ID, 액셀레이터 ID 등이 전달되므로 서로 중복되지 않는 2바이트 정수 값(0~65535)이어야 한다.
- 버튼은 클릭 시 WM_COMMAND 메시지를 보낸다.
미리 정의된 컨트롤 윈도우 클래스
| 윈도우 클래스 | 컨트롤 |
|---|---|
| button | 버튼, 체크, 라디오 |
| static | 텍스트 |
| scrollbar | 스크롤 바 |
| edit | 에디트 |
| listbox | 리스트 박스 |
| combobox | 콤보 박스 |
컨트롤 핸들 얻기
/*
HWND hDlg // 컨트롤의 부모 윈도우 핸들러
int nIDDlgItem // 컨트롤의 아이디
*/
HWND GetDlgItem(HWND hDlg, int nIDDlgItem);
컨트롤 핸들로 컨트롤 조작하기
- 컨트롤은 윈도우이기 때문에 GetDlgItem을 통해 윈도우 핸들을 얻을 수 있다.
- 얻은 윈도우 핸들로 SetWindowText, ShowWindow, MoveWindow 등 모든 윈도우 조작 함수를 사용할 수 있다.
- 윈도우 핸들로 컨트롤 ID를 구하는 함수는 GetDlgCtrlID()이다.
컨트롤 기본 속성
- 리소스 뷰에서 지정
- Caption
- 컨트롤에 표시되는 문자열
- ID
- 컨트롤의 아이디
- Visible
- 컨트롤의 출력 여부를 결정
- Caption
- 코드에서 지정하려면 CreateWindowEx()에 값을 전달한다.
- 아래 코드 확인
부모 윈도우와 자식 컨트롤 간에 메시지 교환
- 자식은 부모 윈도우의 프로시저에 통지 메시지를 보내고 부모는 자식 컨트롤에 메시지를 보낸다.
- 자식 -> 부모 : 통지 메시지
- WM_COMMAND 메시지의 HIWORD(wParam)을 통해 통지 메시지를 보낸다.
- 부모 -> 자식 : 메시지
- SendMessage()를 통해 메시지를 보내고 반환값이 컨트롤의 응답값이다.
- 자식 -> 부모 : 통지 메시지
- Check, Radio 클릭시 상태변화는 부모, 자식 윈도우간 메시지 교환으로 이루어지는데 운영체제가 기본으로 제공하는 기능이다.
- 클릭시 조건에 따라 상태 변환 등을 하려면 수동으로 메시지 교환을 통해서 상태 변경을 해야한다.
(Manual)Check 컨트롤과 부모 윈도우간 메시지 교환
#include "Windows.h"
#include "resource.h"
INT_PTR CALLBACK DialogProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), nullptr, DialogProc);
return 0;
}
INT_PTR CALLBACK DialogProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
switch (message)
{
case WM_COMMAND:
switch (LOWORD(wParam))
{
case IDC_CHECK1: {
// check 컨트롤의 클릭 통지 메시지
// Button Messages : https://docs.microsoft.com/en-us/windows/desktop/controls/button-messages
if (HIWORD(wParam) == BN_CLICKED) {
// Check의 HWND 얻기
HWND hCheck = GetDlgItem(hWnd, IDC_CHECK1);
// check 컨트롤에 BM_GETCHECK 메시지 보내고 결과 받아서 현재 상태 알기
LRESULT result = SendMessage(hCheck, BM_GETCHECK, 0, 0);
if (result == BST_UNCHECKED) {
// check 컨트롤이 비선택 상태라면 BM_SETCHECK 메시지를 보내 선택 상태로 만들기
SendMessage(hCheck, BM_SETCHECK, BST_CHECKED, 0);
}
else {
// check 컨트롤이 선택 상태라면 BM_SETCHECK 메시지를 비보내 선택 상태로 만들기
SendMessage(hCheck, BM_SETCHECK, BST_UNCHECKED, 0);
}
return true;
}
}
case IDOK:
case IDCANCEL:
EndDialog(hWnd, 0);
return true;
default:
break;
}
default:
break;
}
return false;
}
컨트롤 종류
Static, Button 컨트롤
- static
- 문자열을 출력하는 컨트롤
- TextOut으로 출력한 문자열과 다르게 스스로 메시지를 처리하는 윈도우이기 때문에 지워지는 것을 대비해 WM_PAINT에서 계속 그려주는 코드를 작성할 필요가 없다.
- 고정된 문자열을 출력하므로 리소스 뷰에서 자동으로 ID를 할당해주지 않는다.
#include "Windows.h"
HINSTANCE g_hInst;
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
g_hInst = hInstance;
// 윈도우 클래스 등록
WNDCLASSEX wcex = { 0, };
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
//wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_TEST));
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
//wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_TEST);
wcex.lpszClassName = "MyButton";
//wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
RegisterClassEx(&wcex);
// 윈도우 클래스를 기반으로 윈도우 생성
HWND hWnd = CreateWindowEx(0, "MyButton", "Title", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, nullptr, nullptr, hInstance, nullptr);
if (hWnd == nullptr) {
return false;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
// 메시지 루프
MSG msg;
while (GetMessage(&msg, nullptr, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
switch (message)
{
case WM_CREATE:
{
// Button 컨트롤 생성
// 생성한 컨트롤 핸들을 반환한다.
HWND hButton = CreateWindowEx(0,
// 만들고자 하는 윈도우 클래스
"button",
// 윈도우의 캡션, 버튼일 경우 버튼 위에 나타난다.
"label",
// WS_CHILD : 컨트롤은 반드시 자식 윈도우
// WS_VISIBLE : ShowWindow 함수를 호출하지 않아도 화면에 나타냄
// BS_PUSHBUTTON : 버튼의 스타일 값
WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
// 위치와 크기
20,
20,
50,
30,
// 부모 윈도우
hWnd,
// 윈도우에서 사용할 메뉴의 핸들이지만 컨트롤의 경우에는 메뉴를 가지지 않으므로
// 이 인수는 컨트롤의 ID를 지정하는 용도로 사용한다.
// 자식 윈도우끼리만 중복되지 않으면 된다.
(HMENU)10,
// 인스턴스 핸들
g_hInst,
// MDI에서 사용하는 구조체
nullptr);
// Button 컨트롤 생성 end
// Static 컨트롤 생성
HWND hStatkc = CreateWindowEx(0, "static", "static controll text", WS_CHILD | WS_VISIBLE, 20, 50, 200, 50, hWnd, (HMENU)20, g_hInst, nullptr);
// Static 컨트롤 생성 end
}
break;
case WM_COMMAND: // 사용자로부터의 명령
{
// 컨트롤 ID, 메뉴 ID, 액셀레이터 ID 등이 모두 LOWORD(wParam)로 전달되므로
// 2바이트, 즉 0~65535 사이의 값중에서 중복되지 않는 값이어야 한다.
switch (LOWORD(wParam))
{
case 10:
// 버튼 컨트롤 클릭시 전달되는 정보
// HIWORD(wParam) : 통지 코드, 버튼의 경우 항상 BN_CLICKED(클릭됨)
// LOWORD(wParam) : 컨트롤의 ID
// lParam : 메시지를 보낸 차일드 윈도우의 메시지 핸들
if (HIWORD(wParam) == BN_CLICKED) {
MessageBox(hWnd, "button button", "button caption", MB_OK);
}
break;
default:
break;
}
}
break;
case WM_CLOSE: // 윈도우가 닫힌다.
case WM_DESTROY: // 윈도우가 제거됐다.
PostQuitMessage(0); // 프로그램을 종료한다.
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam); // DefWindowProc() 반환값을 리턴한다.
break;
}
// false(0)을 리턴하면 메시지를 제대로 처리하지 않았음을 나타낸다.
// 윈도우 생성중(CreateWindow()함수 내부)에 false를 리턴하면 윈도우가 생성되지 않기 때문에
// 처리하는 메시지가 없어도 DefWindowProc를 통해 0이 아닌 값을 리턴하도록 해야한다.
// 윈도우 생성 후에는 메시지가 제대로 처리되지 않았음을 나타낼뿐 프로그램이 종료되지는 않는다.
return 0;
}
Radio, Check 컨트롤
- Radio 컨트롤
- Radio 그룹
- Radio 컨트롤의 그룹 값을 true로 설정한 라디오 컨트롤부터 그룹값이 true인 다음 라디오 컨트롤 이전까지의 라디오 컨트롤을 그룹으로 묶는다.
- 순서는 탭 순서.
- Radio 컨트롤의 그룹 값을 true로 설정한 라디오 컨트롤부터 그룹값이 true인 다음 라디오 컨트롤 이전까지의 라디오 컨트롤을 그룹으로 묶는다.
- Radio 그룹
- Check 컨트롤
- 상태
- 두가지 상태를 가지는 BS_CHECKBOX
- 선택, 비선택
- 세가지 상태를 가지는 BS_3STATE
- 선택, 비선택, Grayed(Indeterminate)
- 두가지 상태를 가지는 BS_CHECKBOX
- 상태
#include "Windows.h"
#include "resource.h"
#include <stdio.h> // sprintf_s
const UINT nMax_Char = 100; // 최대 문자열 길이
const UINT nCheckCount = 2; // 체크 버튼 개수
const UINT nRadioCount = 3; // 라디오 버튼 개수
const UINT arrCheckIds[nCheckCount] = { IDC_CHECK1, IDC_CHECK2 }; // 체크 버튼 아이디
const UINT arrRadioIds[nRadioCount] = { IDC_RADIO1, IDC_RADIO2, IDC_RADIO3 }; // 라디오 버튼 아이디
const UINT arrEditIds[nCheckCount] = { IDC_EDIT1, IDC_EDIT2 }; // 에디트 아이디(상태 문자열 출력용)
INT_PTR CALLBACK DialogProc(HWND, UINT, WPARAM, LPARAM);
UINT GetIdIndex(const UINT *ids, UINT cIds, UINT id); // 아이디로 순서를 반환
void PrintCheckState(HWND hWnd, UINT nCheckId); // 체크 버튼의 상태를 출력
void PrintRadioState(HWND hWnd); // 라디오 버튼의 상태를 출력
void ClearCheck(HWND hWnd); // 모든 체크 버튼의 선택 해제
void ClearRadio(HWND hWnd); // 모든 라디오 버튼의 선택 해제
INT_PTR APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), nullptr, DialogProc);
return 0;
}
INT_PTR CALLBACK DialogProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
switch (message)
{
case WM_INITDIALOG:
{
// init check state
for (size_t i = 0; i < nCheckCount; i++)
{
PrintCheckState(hWnd, i);
}
// init radio state
PrintRadioState(hWnd);
}
break;
case WM_PAINT:
{
PAINTSTRUCT ps = {};
HDC hdc = BeginPaint(hWnd, &ps);
EndPaint(hWnd, &ps);
}
break;
case WM_COMMAND:
{
switch (LOWORD(wParam))
{
case IDC_CHECK1:
case IDC_CHECK2:
{
UINT id = LOWORD(wParam);
UINT index = GetIdIndex(arrCheckIds, nCheckCount, id);
PrintCheckState(hWnd, index);
return true;
}
break;
case IDC_RADIO1:
case IDC_RADIO2:
case IDC_RADIO3:
{
PrintRadioState(hWnd);
return true;
}
break;
case IDC_BUTTON1:
ClearCheck(hWnd);
break;
case IDC_BUTTON2:
ClearRadio(hWnd);
break;
case IDOK:
case IDCANCEL:
EndDialog(hWnd, 0);
return true;
break;
default:
break;
}
}
break;
default:
break;
}
return 0;
}
void PrintCheckState(HWND hWnd, UINT index) {
UINT nCheckId = arrCheckIds[index];
UINT nEditId = arrEditIds[index];
UINT checked = IsDlgButtonChecked(hWnd, nCheckId); // 버튼 컨트롤러 체크 상태 가져옴
const char *szCheckState = nullptr;
switch (checked)
{
case BST_CHECKED:
szCheckState = "Checked";
break;
case BST_UNCHECKED:
szCheckState = "Unchecked";
break;
case BST_INDETERMINATE:
szCheckState = "Indeterminate";
break;
default:
break;
}
// 컨트롤 텍스트 변경
SetDlgItemText(hWnd,
nEditId, // 컨트롤 ID
szCheckState // 변경할 텍스트
);
}
void PrintRadioState(HWND hWnd) {
for (size_t i = 0; i < nRadioCount; i++)
{
UINT nRadioId = arrRadioIds[i];
UINT checked = IsDlgButtonChecked(hWnd, nRadioId); // 버튼 컨트롤러 체크 상태 가져옴
if (checked == BST_CHECKED) {
char str[nMax_Char];
sprintf_s(str, nMax_Char, "Radio%d checked", i + 1);
SetDlgItemText(hWnd, IDC_EDIT3, str);
return;
}
}
SetDlgItemText(hWnd, IDC_EDIT3, "No checked radio");
}
UINT GetIdIndex(const UINT *ids, UINT cIds, UINT id) {
for (size_t i = 0; i < cIds; i++)
{
if (ids[i] == id) {
return i;
}
}
}
void ClearCheck(HWND hWnd) {
for (size_t i = 0; i < nCheckCount; i++)
{
// 버튼 컨트롤러 체크 상태 변경
CheckDlgButton(hWnd,
arrCheckIds[i], // 컨트롤 ID
BST_UNCHECKED // 상태
);
PrintCheckState(hWnd, i);
}
}
void ClearRadio(HWND hWnd) {
// 방법 1.
//for (size_t i = 0; i < nRadioCount; i++)
//{
// CheckDlgButton(hWnd, arrRadioIds[i], BST_UNCHECKED); // 버튼 컨트롤러 체크 상태 변경
//}
// 방법 2.
CheckRadioButton(hWnd,
arrRadioIds[0], // 첫 번째 라디오 버튼 ID
arrRadioIds[nRadioCount - 1], // 마지막 라디오 버튼 ID
0 // 선택할 라디오 버튼 ID
);
PrintRadioState(hWnd);
}
Edit 컨트롤
- 사용자로부터 데이터 입력
- 데이터 입력, 수정, 삭제를 위한 컨트롤
- 다양한 속성 제공
Edit 컨트롤 데이터 읽고 쓰기
GetDlgItemText
- Edit 컨트롤에서 문자열 가져오기
GetDlgItemInt
- Edit 컨트롤에서 숫자 가져오기
- float형은 문자열로 받아서 atof 함수를 거쳐 숫자로 변환해야 한다.
SetDlgItemText
- Edit 컨트롤에 문자 입력
SetDlgItemInt
- Edit 컨트롤에 숫자 입력
ComboBox 컨트롤
- 여러 항목을 리스트 형태로 출력 및 선택
- Simple, Drop Down, Drop List 형태 제공
- 속성에서 선택 가능
- 항목이 펼쳐지는 영역을 리소스 뷰에서 설정 가능
ComboBox 관련 매크로 함수
- 사용하기 위해서는 Windowsx.h 포함해야함
- ComboBox_AddString()
- 항목 추가
- ComboBox_DeleteString()
- 항목 제거
- ComboBox_InsertString()
- 중간에 추가
- ComboBox_GetCurSel()
- 사용자가 선택한 인덱스 값
ComboBox 코드
#include "Windows.h"
#include "Windowsx.h"
#include "resource.h"
#include <stdio.h>
const UINT nMax_Char = 100;
HWND g_hCombo;
INT_PTR CALLBACK DialogProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
void ShowCurrentComboIndex(HWND hWnd);
INT_PTR APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), nullptr, DialogProc);
return 0;
}
INT_PTR CALLBACK DialogProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
switch (message)
{
case WM_INITDIALOG:
{
g_hCombo = GetDlgItem(hWnd, IDC_COMBO1);
ComboBox_AddString(g_hCombo, "a1");
ShowCurrentComboIndex(hWnd);
}
break;
case WM_COMMAND:
{
switch (LOWORD(wParam))
{
// Combo Box : https://docs.microsoft.com/en-us/windows/desktop/controls/combo-boxes
case IDC_COMBO1:
{
switch (HIWORD(wParam)) // ComboBox의 통지 메시지
{
case CBN_SELCHANGE: // 사용자가 콤보 리스트에서 아이템 선택
{
ShowCurrentComboIndex(hWnd);
}
break;
//case CBN_EDITUPDATE: // CBN_EDITCHANGE와 다르게 변경된 텍스트가 화면에 출력되기 전에 전달됨
case CBN_EDITCHANGE: // 콤보 박스의 텍스트 수정(선택 인덱스가 -1 됨)
{
ShowCurrentComboIndex(hWnd);
}
break;
default:
break;
}
}
break;
case IDC_BUTTON1: // add button
{
char str[nMax_Char] = {};
GetDlgItemText(hWnd, IDC_EDIT1, str, nMax_Char);
ComboBox_AddString(g_hCombo, str); // 0번 인덱스에 추가
ShowCurrentComboIndex(hWnd);
}
break;
case IDC_BUTTON2: // insert button
{
char str[nMax_Char] = {};
GetDlgItemText(hWnd, IDC_EDIT1, str, nMax_Char);
ComboBox_InsertString(
g_hCombo,
0, // 삽입할 인덱스, 아이템 개수보다 높은 인덱스이면 무시됨
str
);
ShowCurrentComboIndex(hWnd);
}
break;
case IDC_BUTTON3: // delete button
{
ComboBox_DeleteString(
g_hCombo,
0 // 삭제할 인덱스, 아이템 개수보다 높은 인덱스이면 무시됨
);
ShowCurrentComboIndex(hWnd);
}
break;
case IDOK:
case IDCANCEL:
EndDialog(hWnd, 0);
break;
default:
break;
}
}
break;
default:
break;
}
return 0;
}
void ShowCurrentComboIndex(HWND hWnd) {
int selectedIndex = ComboBox_GetCurSel(g_hCombo);
char str[nMax_Char] = {};
sprintf_s(str, nMax_Char, "selected index : %d", selectedIndex);
SetDlgItemText(hWnd, IDC_EDIT2, str);
}
ListBox 컨트롤
- 리스트 출력 영역 안에서 항목 선택
- 지정된 영역보다 많은 항목은 스크롤바를 통해 선택
- Windowsx.h 포함 필요
- 정렬 속성이 true이면 자동 정렬한다.
ListBox 컨트롤 관련 매크로 함수
- ListBox_AddString()
- ListBox_GetCurSel()
ListBox 코드
// ComboBox와 비슷하기 때문에 생략
List 컨트롤
- 리스트 박스를 확장한 개념의 컨트롤
- 다양한 뷰 스타일 제공
- 아이콘 스타일
- 스몰 아이콘 스타일
- 리스트 스타일
- 리포트 스타일
- 컬럼이 추가된 리스트
- 기능, 코드 향상
- #include
- Commctrl.h
- Windowsx.h
- 용어
- 컬럼(Column)
- 표 형식에서 열 항목 이름
- 아이템(Item)
- 행을 나타냄
- 서브 아이템(Sub Item)
- 열을 나타냄
- 컬럼(Column)
LVCOLUMN 구조체
- 리스트의 컬럼을 설정하는 구조체
typedef struct tagLVCOLUMNA
{
// 이 구조체에서 적용할 속성을 마스킹(OR연산)
// LVCF_FMT, LVCF_WIDTH, LVCF_TEXT, LVCF_SUBITEM, ...
// 일반적인 형태 LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM
UINT mask;
// 컬럼과 서브 아이템의 데이터 정렬 형식 지정
// LVCFMT_LEFT, LVCFMT_RIGHT, LVCFMT_CENTER
// 0인덱스의 컬럼과 서브아이템은 항상 왼쪽 정렬
int fmt;
// 가로 길이
int cx;
// 문자열 지정
LPSTR pszText;
int cchTextMax;
// 서브 아이템 인덱스
int iSubItem;
int iImage;
int iOrder;
#if (NTDDI_VERSION >= NTDDI_VISTA)
int cxMin; // min snap point
int cxDefault; // default snap point
int cxIdeal; // read only. ideal may not eqaul current width if auto sized (LVS_EX_AUTOSIZECOLUMNS) to a lesser width.
#endif
}
LVITEM 구조체
typedef struct tagLVITEMA
{
// 이 구조체에서 적용할 속성을 마스킹(OR연산)
// LVIF_TEXT, ...
UINT mask;
// 삽입하고자 하는 인덱스
int iItem;
// 0으로 고정(?)
int iSubItem;
UINT state;
UINT stateMask;
// 출력될 문자열
LPSTR pszText;
int cchTextMax;
int iImage;
LPARAM lParam;
int iIndent;
#if (NTDDI_VERSION >= NTDDI_WINXP)
int iGroupId;
UINT cColumns; // tile view columns
PUINT puColumns;
#endif
#if (NTDDI_VERSION >= NTDDI_VISTA) // Will be unused downlevel, but sizeof(LVITEMA) must be equal to sizeof(LVITEMW)
int* piColFmt;
int iGroup; // readonly. only valid for owner data.
#endif
}
List 컨트롤 매크로 함수
- ListView_InsertColumn
- 컬럼 삽입
- ListView_InsertItem
- 아이템 삽입
- ListView_SetItemText
- 서브 아이템에 텍스트 설정
- ListView_GetItemCount
- 아이템 개수 조사
- 아이템 생성 및 추가 등
List 컨트롤 코드
#include "Windows.h"
#include "resource.h"
//#include "Windowsx.h" // include 안해도 ListView 사용가능하네...
#include "Commctrl.h" // LVCOLUMN
#include <stdio.h>
const UINT nMax_Char = 100;
HWND g_hList = nullptr;
INT_PTR CALLBACK DialogProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), nullptr, DialogProc);
return 0;
}
INT_PTR CALLBACK DialogProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
switch (message)
{
case WM_INITDIALOG:
{
g_hList = GetDlgItem(hWnd, IDC_LIST1);
// 컬럼 : 항목
// 아이템 : 행(Row)
// 서브아이템 : 열(Column)
// ===== List에 컬럼 추가 =====
// List 속성의 View를 Report로 설정해야 컬럼을 추가할 수 있다.
char colText0[] = "col0";
char colText1[] = "col1";
LVCOLUMN col = {};
col.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
col.fmt = LVCFMT_LEFT;
col.cx = 100;
col.pszText = colText0;
ListView_InsertColumn(g_hList, 0, &col); // 컬럼 추가0
col.pszText = colText1;
ListView_InsertColumn(g_hList, 1, &col); // 컬럼 추가1
// ===== List에 컬럼 추가 ===== end
// ===== List에 아이템 추가 =====
char itemText0[] = "item0";
char itemText1[] = "item1";
LVITEM item = {};
item.mask = LVIF_TEXT;
item.iItem = 0;
item.iSubItem = 0; // 아이템을 처음 추가하므로 0번째 서브아이템을 선택한다.
item.state;
item.stateMask;
item.pszText = itemText0;
ListView_InsertItem(g_hList, &item); // 아이템 추가0
int itemCount = ListView_GetItemCount(g_hList);
item.iItem = itemCount;
item.pszText = itemText1;
ListView_InsertItem(g_hList, &item); // 아이템 추가1
// ===== List에 아이템 추가 ===== end
// ===== List에 서브아이템 추가 =====
char suhbitemText0[] = "subitem0";
char suhbitemText1[] = "subitem1";
ListView_SetItemText(g_hList, 0/*item idx*/, 1/*subitem idx*/, suhbitemText0); // 서브아이템 추가0
ListView_SetItemText(g_hList, 1, 1, suhbitemText1); // 서브아이템 추가1(컬럼이 없다면 화면에 보이지 않는다)
// ===== List에 서브아이템 추가 ===== end
// ===== List 뷰 설정 =====
// 기본값은 첫 번째 서브아이템의 텍스트 영역만 선택됨
ListView_SetExtendedListViewStyle(
g_hList,
LVS_EX_FULLROWSELECT // 아이템 전체가 클릭되도록 한다.
| LVS_EX_GRIDLINES // 서브아이템 사이에 그리드 라인을 넣는다.
);
// ===== List 뷰 설정 ===== end
return true;
}
break;
// 표준 컨트롤은 WM_COMMAND를 통해 메시지를 통지
case WM_COMMAND:
{
switch (LOWORD(wParam))
{
case IDC_BUTTON1: // Delete button
{
UINT itemIndex = ListView_GetNextItem(
g_hList, // 윈도우 핸들
-1, // 검색을 시작할 인덱스
LVNI_SELECTED // 검색 조건
);
if (itemIndex != -1) {
ListView_DeleteItem(g_hList, itemIndex); // 지정한 인덱스의 아이템 제거
}
}
break;
case IDC_BUTTON2: // Delete All button
{
if (MessageBox(hWnd, "Remove all?", "", MB_OKCANCEL) == IDOK) {
ListView_DeleteAllItems(g_hList); // ListView의 모든 아이템 제거
};
}
break;
case IDOK:
case IDCANCEL:
EndDialog(hWnd, 0);
break;
default:
break;
}
}
break;
// 공통 컨트롤의 통지 메시지는 WM_NOTIFY로 받는다.
case WM_NOTIFY:
{
// ListView의 통지 메시지 받기
if (wParam == IDC_LIST1) {
NMHDR *pnmhdr = nullptr;
// NMHDR을 얻는 방법1.
NMTTDISPINFO *nmttdispinfo = (NMTTDISPINFO*)lParam;
pnmhdr = &(nmttdispinfo->hdr);
// NMHDR을 얻는 방법2.
// NMTTDISPINFO는 NMHDR을 확장하는 구조체로서
// 첫 멤버변수로 NMHDR을 가지고 있기 때문에 NMHDR 형으로도 캐스팅이 가능하다.
pnmhdr = (NMHDR*)lParam;
pnmhdr->hwndFrom; // 윈도우 핸들
pnmhdr->idFrom; // 컨트롤 아이디
pnmhdr->code; // 통지 코드
if (pnmhdr->code == NM_CLICK) {
// 클릭된 아이템 인덱스 알아내기
UINT itemIndex = ListView_GetNextItem(
pnmhdr->hwndFrom, // 윈도우 핸들
-1, // 검색을 시작할 인덱스
LVNI_SELECTED // 검색 조건
);
if (itemIndex == -1) {
// 선택 없음
}
else {
// 서브아이템의 텍스트 가져오기
char result[nMax_Char] = {};
char subItem0[nMax_Char] = {};
char subItem1[nMax_Char] = {};
ListView_GetItemText(pnmhdr->hwndFrom, itemIndex, 0, subItem0, nMax_Char);
ListView_GetItemText(pnmhdr->hwndFrom, itemIndex, 1, subItem1, nMax_Char);
sprintf_s(result, "%s, %s", subItem0, subItem1);
// 출력
SetDlgItemText(hWnd, IDC_EDIT1, result);
}
}
else if (pnmhdr->code == LVN_ITEMCHANGED) {
UINT itemIndex = ListView_GetNextItem(
pnmhdr->hwndFrom, // 윈도우 핸들
-1, // 검색을 시작할 인덱스
LVNI_SELECTED // 검색 조건
);
if (itemIndex == -1) {
SetDlgItemText(hWnd, IDC_EDIT1, "");
}
}
}
}
break;
default:
break;
}
return false;
}
스크롤바
- 표준 스크롤바
- 별도의 윈도우 핸들이 없다.
- 스크롤바 생성
- 스크롤바 영역이 클라이언트 영역dp 포함되기 때문에 정확한 클라이언트 영역을 위해 재조정해야한다.
- WS_VSCROLL, WS_HSCROLL
- 일반적인 스크롤바 크기
- 17px
- 메시지
- WM_HSCROLL, WM_VSCROLL
- 실시간 스크롤 박스 이동 메시지
- LOWORD(wParam) == SB_THUMBTRACK
- 위치 값
- HIWORD(wParam)
- 이 메시지가 발생하면 스크롤바가 위치해야할 위치 값이 들어오고 SetScrollPos() 함수를 통해 실제 스크롤바를 이동시켜줘야한다.
- WM_HSCROLL, WM_VSCROLL
- 스크롤바 컨트롤
- 컨트롤 윈도우 핸들이 있다.
스크롤바 위치 설정 함수
SetScrollPos()
Picture 컨트롤
- 부분적으로 화면 DC를 얻어와 그릴 수 있는 공간을 만든다.
통지 메시지
표준 컨트롤
- WM_COMMAND 메시지로 통지 메시지 전달
- LOWORD(WPARAM)
- 메시지를 보낸 컨트롤의 ID
- HIWORD(WPARAM)
- 메시지를 보낸 컨트롤에 일어난 변화의 종류
- 이게 통지 코드
- LPARAM
- 메시지를 보낸 윈도우 핸들
- 표준 컨트롤의 사건 발생만 알리기 때문에 데이터양이 적어서 생기는 문제는 없었다.
공통 컨트롤
- WM_NOTIFY 메시지로 통지 메시지 전달
- WPARAM
- 메시지를 보낸 컨트롤의 ID
- LPARAM
- NMHDR 구조체의 포인터
- NMHDR 구조체
- HWND hwndForm // 컨트롤의 윈도우 핸들
- UINT idForm // 컨트롤의 아이디
- UINT code // 통지 메시지 코드
- 통지 전용 메시지 WM_NOTIFY
- 윈95에 와서 컨트롤들이 복잡해지면서 통지 메시지로 복잡한 정보를 전달해야할 필요성이 생겼고 WM_COMMAND는 이미 64비트의 공간이 할당돼있었기 때문에 통지 전용 메시지인 WM_NOTIFY가 생기게 되었다.
- WPARAM, LPARAM을 직접 사용하지 않고 NMHDR 구조체의 포인터를 전달하도록 변경됨
- NMHDR로도 용량 부족해지자 NMTTDISPINFO 구조체의 포인터를 전달하도록 함
- NMTTDISPINFO 구조체는 첫 멤버가 NMHDR로 선언되기 때문에 (NMHDR), (NMTTDISPINFO) 둘 다 캐스팅이 가능하고 NMHDR의 멤버를 조회할 수 있다.
영어 단어
- indeterminate
- 불확정한, 막연한, 애매한