Win32 - API - Control

[TOC]

컨트롤

컨트롤이란

미리 정의된 컨트롤 윈도우 클래스

윈도우 클래스 컨트롤
button 버튼, 체크, 라디오
static 텍스트
scrollbar 스크롤 바
edit 에디트
listbox 리스트 박스
combobox 콤보 박스

컨트롤 핸들 얻기

/*
HWND hDlg // 컨트롤의 부모 윈도우 핸들러
int nIDDlgItem // 컨트롤의 아이디
*/
HWND GetDlgItem(HWND hDlg, int nIDDlgItem);

컨트롤 핸들로 컨트롤 조작하기

컨트롤 기본 속성

부모 윈도우와 자식 컨트롤 간에 메시지 교환

(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 컨트롤

#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 컨트롤

#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
GetDlgItemInt
SetDlgItemText
SetDlgItemInt

ComboBox 컨트롤

ComboBox 관련 매크로 함수
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 컨트롤

ListBox 컨트롤 관련 매크로 함수
ListBox 코드
// ComboBox와 비슷하기 때문에 생략

List 컨트롤

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 컨트롤 매크로 함수
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;
}

스크롤바

스크롤바 위치 설정 함수
SetScrollPos()

Picture 컨트롤

통지 메시지

표준 컨트롤

공통 컨트롤

영어 단어

참고