우와. 포스팅량이 늘었군요. 자축 자축.
에... 2부에서 예고했던데로 커서를 그려보고 왔습니다.
사실 만드는건 한 시간도 안걸렸는데 놀다보니(...) 포스팅이 좀 늦어졌네요.
별것도 아닌데 3부를 괜히 날려먹는거 아닌가? 몇 부작으로 갈 생각이지? 라는 생각은 저 멀리 다른 차원으로 집어던져버리고 빨리 코드를 살펴보겠습니다.

일단 간단하게 한개만 인클루드 해보죠.

#include <usp10.h>

좋습니다. 자 이제 함수 하나를 만들어 보겠습니다.

// 귀찮으니 전역 변수로 때워봅시다.
SCRIPT_CONTROL ScriptControl;
SCRIPT_STATE ScriptState;
SCRIPT_STRING_ANALYSIS analysis = nullptr;

void InitScriptStringAnalysis(HDC hdc, std::wstring str)
{
	if ( analysis == nullptr )
	{
		ZeroMemory(&ScriptControl, sizeof(ScriptControl));
		ZeroMemory(&ScriptState, sizeof(ScriptState));
		ScriptApplyDigitSubstitution(nullptr, &ScriptControl, &ScriptState);
	}
	else
		ScriptStringFree(&analysis);

	ScriptStringAnalyse(hdc, str.c_str(), str.length() + 1,
						str.length() * 1.5 + 16,
						-1,
						SSA_BREAK | SSA_GLYPHS | SSA_FALLBACK | SSA_LINK,
						0,
						&ScriptControl,
						&ScriptState,
						nullptr,
						nullptr,
						nullptr,
						&analysis);
}


으음. 뭐. 그럭저럭 테스트 해볼만 한 함수가 나왔습니다.

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	int wmId, wmEvent;
	PAINTSTRUCT ps;
	HDC hdc;
	static HIMC imeID = nullptr;
	static std::wstring str;

	wchar_t debug[256] = { 0 };
	static int x = 0;

	RECT rect = { 0, 0, 0, 0 };

	switch ( message )
	{
		case WM_ACTIVATE:
			if ( imeID == nullptr )
			{
				imeID = ImmCreateContext();
				ImmAssociateContext(hWnd, imeID);
			}
			break;
		case WM_IME_STARTCOMPOSITION:
			break;
		case WM_IME_CHAR:
			str.push_back((wchar_t)wParam);
			InvalidateRect(hWnd, NULL, TRUE);
			break;
		case WM_CHAR:
			if ( (wchar_t)wParam == '\b' )
			{
				str.pop_back();
			}
			else
			{
				str.push_back((wchar_t)wParam);
			}
			InvalidateRect(hWnd, NULL, true);
			break;
		case WM_PAINT:
			hdc = BeginPaint(hWnd, &ps);
			// DT_CALCRECT옵션은 출력할 글자의 범위를 구하는 옵션입니다.
			// 따라서, 아래 함수를 호출하게 되면 rect변수에 출력할 글자의 범위를 구해서 던져줍니다.
			// 여기에서 우리는 top와 bottom만 필요합니다. 전체 width 는 필요 없으니까요.
			DrawText(hdc, str.empty() ? L"A":str.c_str(), -1, &rect, DT_CALCRECT | DT_CENTER);
			DrawText(hdc, str.c_str(), str.length(), &rect, DT_CENTER);

			// 이 함수를 호출하게 되면 ScriptStringCPtoX를 사용하기 위한
			// SCRIPT_STRING_ANALYSIS 를 얻게됩니다. 자세한 내용은 MSDN을 참고해주세요.
			InitScriptStringAnalysis(hdc, str.c_str());
			ScriptStringCPtoX(analysis, str.length(), false, &nTrail);

			// 커서를 그립니다. DrawText에서 얻어온 글자의 출력 범위를 top와 bottom 으로 하고,
			// ScriptStringCPtoX에서 얻어온 마지막 글자 인덱스의 위치를 left로 하고 여기서 1을 더해 width가 1인
			// 사각형을 그립니다.
			Rectangle(hdc, x, rect.top, x + 1, rect.bottom);
			wsprintf(debug, L"커서 위치 : %d %d %d %d", x, rect.top, x + 1, rect.bottom);
			SetWindowText(hWnd, debug);

			EndPaint(hWnd, &ps);
			break;
		case WM_DESTROY:
			ImmDestroyContext(imeID);
			PostQuitMessage(0);
			break;
		// 코드가 길어져서 기본 프로시저 함수에서 수정되지 않은 이벤트는 쓰지 않았습니다.
	}
	return 0;
}

자 그럼 사진을 하나 투척해보죠.


짞짞짞.

으음. 하지만 커서가 안움직이니 뭔가 허전하군요. 명색의 커서인데 움직여야 하지 않겠어요? 움직였으면 움직인 자리에 무언가 입력하거나 삭제도 되야죠.
으음.. 한번 해보죠. 재밋을거같으니.

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	// 기존 변수 생략
	static int nowPosition = 0;

	switch ( message )
	{
		case WM_ACTIVATE:
			if ( imeID == nullptr )
			{
				imeID = ImmCreateContext();
				ImmAssociateContext(hWnd, imeID);
			}
			break;
		case WM_IME_STARTCOMPOSITION:
			break;
		case WM_IME_CHAR:
		case WM_CHAR:
			if ( (wchar_t)wParam == '\b' )
			{
				if ( nowPosition == str.length() )
				{
					str.erase(nowPosition - 1);
				}
				else
				{
					// 중간 문자를 삭제하기 위한 코드입니다.
					const wchar_t* strBack = str.c_str();
					strBack = &strBack[nowPosition];

					str.erase(nowPosition - 1);
					str.append(strBack);
				}
				nowPosition = nowPosition - 1 < 0 ? 0:nowPosition - 1;
			}
			else
			{
				// 중간에 글자를 삽입하기 위해 insert로 바꾸었습니다.
				str.insert(nowPosition, (wchar_t*)&wParam);
				nowPosition = nowPosition + 1 > str.length() ? str.length():nowPosition + 1;
			}
			InvalidateRect(hWnd, NULL, true);
			break;
		case WM_KEYDOWN:
			if ( (int)wParam == VK_LEFT )
			{
				nowPosition = nowPosition - 1 < 0 ? 0:nowPosition - 1;
				InvalidateRect(hWnd, NULL, true);
			}
			else if ( (int)wParam == VK_RIGHT )
			{
				nowPosition = nowPosition + 1 > str.length() ? str.length():nowPosition + 1;
				InvalidateRect(hWnd, NULL, true);
			}
			break;
		case WM_PAINT:
			hdc = BeginPaint(hWnd, &ps);
			DrawText(hdc, str.empty() ? L"A":str.c_str(), -1, &rect, DT_CALCRECT | DT_CENTER);
			DrawText(hdc, str.c_str(), str.length(), &rect, DT_CENTER);


			InitScriptStringAnalysis(hdc, str.c_str());
			ScriptStringCPtoX(analysis, nowPosition, false, &x);

			Rectangle(hdc, x, rect.top, x + 1, rect.bottom);
			wsprintf(debug, L"커서 위치 : %d %d %d %d", x, rect.top, x + 1, rect.bottom);
			SetWindowText(hWnd, debug);
			EndPaint(hWnd, &ps);
			break;
		case WM_DESTROY:
			ImmDestroyContext(imeID);
			PostQuitMessage(0);
			break;
		default:
			return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}

이 기세라면! 마우스로 클릭해서 커서를 옮기는 것도 가능할거같군요. 확인해 보는것도 좋겠지만 이번 편은 ScriptString를 테스트해보기 위한 편이었으니 그냥 안할려고 합니다 ㅇㅅ.
작동되는거 확인했으니 됐죠 뭐 ㅋㅅㅋ.

이번에는 GDI로 직접 EditBox를 만들어 보았습니다. 코드는 좀 지저분하지만 어떠신지요?
중복된 코드를 지우기 위해서 WM_IME_CHAR에 keybd_event로 VK_RIGHT를 누르게 해 보았는데 왜인지 모르겠지만 한글 타이핑에 문제가 생기더군요.
원인을 모르겠네요. 아시는분 알려주시면 감사하겟습니다(_ _ )

그런고로! 다음에는 DirectX11에 직접 적용시켜 볼 차례군요.
자 그럼 다음시간에! (언제가 될지 모르겠지만 노력해 보겠습니다..)

커서 위치에 주목!

+ Recent posts