EternalWindows
エディットコントロール / 編集操作

エディットコントロール上で右クリックが行われた場合は、 テキストを編集するためのポップアップメニューが表示されますが、 このような編集はアプリケーション自ら行うこともできます。 これは、メモ帳などのエディタがメニューから切り取りやコピーをサポートしている点からも明らかです。 今回はこのような編集と、入力されたテキストを保存するプログラムを作成します。

#include <windows.h>

#define ID_EDIT  10
#define ID_CUT   20
#define ID_COPY  30
#define ID_PASTE 40

void InitializeMenuItem(HMENU hmenu, LPTSTR lpszItemName, int nId, HMENU hmenuSub);
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR      szAppName[] = TEXT("sample");
	HWND       hwnd;
	MSG        msg;
	WNDCLASSEX wc;

	wc.cbSize        = sizeof(WNDCLASSEX);
	wc.style         = 0;
	wc.lpfnWndProc   = WindowProc;
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = hinst;
	wc.hIcon         = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	wc.hCursor       = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName  = NULL;
	wc.lpszClassName = szAppName;
	wc.hIconSm       = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	
	if (RegisterClassEx(&wc) == 0)
		return 0;

	hwnd = CreateWindowEx(0, szAppName, szAppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinst, NULL);
	if (hwnd == NULL)
		return 0;

	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);
	
	while (GetMessage(&msg, NULL, 0, 0) > 0) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int)msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static HWND hwndEdit = NULL;
	
	switch (uMsg) {
		
	case WM_CREATE: {
		HMENU hmenu;
		HMENU hmenuPopup;
		
		hwndEdit = CreateWindowEx(WS_EX_CLIENTEDGE, TEXT("EDIT"), TEXT(""), WS_CHILD | WS_VISIBLE | ES_WANTRETURN | ES_MULTILINE | WS_VSCROLL, 0, 0, 0, 0, hwnd, (HMENU)1, ((LPCREATESTRUCT)lParam)->hInstance, NULL);		
		SetFocus(hwndEdit);

		hmenu  = CreateMenu();
		hmenuPopup = CreatePopupMenu();

		InitializeMenuItem(hmenuPopup, TEXT("切り取り"), ID_CUT, NULL);
		InitializeMenuItem(hmenuPopup, TEXT("コピー"), ID_COPY, NULL);
		InitializeMenuItem(hmenuPopup, TEXT("貼り付け"), ID_PASTE, NULL);

		InitializeMenuItem(hmenu, TEXT("編集(&E)"), ID_EDIT, hmenuPopup);
		
		SetMenu(hwnd, hmenu);
		
		return 0;
	}

	case WM_COMMAND:
		if (LOWORD(wParam) == ID_CUT)
			SendMessage(hwndEdit, WM_CUT, 0, 0);
		else if (LOWORD(wParam) == ID_COPY)
			SendMessage(hwndEdit, WM_COPY, 0, 0);
		else if (LOWORD(wParam) == ID_PASTE)
			SendMessage(hwndEdit, WM_PASTE, 0, 0);
		else
			;
		return 0;

	case WM_INITMENUPOPUP: {
		HMENU hmenuPopup = (HMENU)wParam;
		DWORD dwStart, dwEnd;

		SendMessage(hwndEdit, EM_GETSEL, (WPARAM)&dwStart, (LPARAM)&dwEnd);

		if (dwStart != dwEnd) {
			EnableMenuItem(hmenuPopup, ID_COPY, MF_ENABLED | MF_BYCOMMAND);
			EnableMenuItem(hmenuPopup, ID_CUT, MF_ENABLED | MF_BYCOMMAND);
		}
		else {
			EnableMenuItem(hmenuPopup, ID_COPY, MF_GRAYED | MF_BYCOMMAND);
			EnableMenuItem(hmenuPopup, ID_CUT, MF_GRAYED | MF_BYCOMMAND);
		}

		if (IsClipboardFormatAvailable(CF_TEXT)) 
			EnableMenuItem(hmenuPopup, ID_PASTE, MF_ENABLED | MF_BYCOMMAND);
		else
			EnableMenuItem(hmenuPopup, ID_PASTE, MF_GRAYED | MF_BYCOMMAND);

		return 0;
	}

	case WM_CLOSE:
		if (SendMessage(hwndEdit, EM_GETMODIFY, 0, 0)) {
			if (MessageBox(NULL, TEXT("テキストが変更されています。保存しますか?"), TEXT("保存"), MB_YESNO) == IDYES) {
				HANDLE hFile;
				LPTSTR lpszText;
				DWORD  dwSize, dwWriteByte;
				
				dwSize = GetWindowTextLength(hwndEdit) * sizeof(TCHAR);

				hFile = CreateFile(TEXT("sample.txt"), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
				if (dwSize > 0) {
					lpszText = (LPTSTR)HeapAlloc(GetProcessHeap(), 0, dwSize);
					GetWindowText(hwndEdit, lpszText, dwSize);
					WriteFile(hFile, lpszText, dwSize, &dwWriteByte, NULL);
					CloseHandle(hFile);
				}
				CloseHandle(hFile);
				
				SendMessage(hwndEdit, EM_SETMODIFY, FALSE, 0);
				return 0;
			}
		}
		break;

	case WM_SIZE:
		MoveWindow(hwndEdit, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
		return 0;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

void InitializeMenuItem(HMENU hmenu, LPTSTR lpszItemName, int nId, HMENU hmenuSub)
{
	MENUITEMINFO mii;
	
	mii.cbSize = sizeof(MENUITEMINFO);
	mii.fMask  = MIIM_ID | MIIM_TYPE;
	mii.wID    = nId;

	if (lpszItemName != NULL) {
		mii.fType      = MFT_STRING;
		mii.dwTypeData = lpszItemName;
	}
	else
		mii.fType = MFT_SEPARATOR;

	if (hmenuSub != NULL) {
		mii.fMask   |= MIIM_SUBMENU;
		mii.hSubMenu = hmenuSub;
	}

	InsertMenuItem(hmenu, nId, FALSE, &mii);
}

今回はエディットコントロールをウインドウのクライアント領域全体に貼り付けるために、 WM_SIZEでMoveWindowを呼び出しています。 また、メニューから編集操作が行えるように、InitializeMenuItemという自作関数でメニューの作成を行っています。 メニューの項目が選択された場合は、WM_COMMANDが送られます。

case WM_COMMAND:
	if (LOWORD(wParam) == ID_CUT)
		SendMessage(hwndEdit, WM_CUT, 0, 0);
	else if (LOWORD(wParam) == ID_COPY)
		SendMessage(hwndEdit, WM_COPY, 0, 0);
	else if (LOWORD(wParam) == ID_PASTE)
		SendMessage(hwndEdit, WM_PASTE, 0, 0);
	else
		;
	return 0;

WM_CUTが切り取りで、WM_COPYがコピー、WM_PASTEが貼り付けになります。 WPARAMとLPARAMはそれぞれ0で問題ありません。 これらのメッセージの名前はWMで始まっていますが、 親ウインドウに送信してはいけません。

編集操作はいつでも行えるわけではないため、 行えない場合は指定のメニュー項目を無効にするべきです。 ポップアップメニューが表示される段階になるとWM_INITMENUPOPUPが送られるため、 ここで項目の調整を行っています。

case WM_INITMENUPOPUP: {
	HMENU hmenuPopup = (HMENU)wParam;
	DWORD dwStart, dwEnd;

	SendMessage(hwndEdit, EM_GETSEL, (WPARAM)&dwStart, (LPARAM)&dwEnd);

	if (dwStart != dwEnd) {
		EnableMenuItem(hmenuPopup, ID_COPY, MF_ENABLED | MF_BYCOMMAND);
		EnableMenuItem(hmenuPopup, ID_CUT, MF_ENABLED | MF_BYCOMMAND);
	}
	else {
		EnableMenuItem(hmenuPopup, ID_COPY, MF_GRAYED | MF_BYCOMMAND);
		EnableMenuItem(hmenuPopup, ID_CUT, MF_GRAYED | MF_BYCOMMAND);
	}

	if (IsClipboardFormatAvailable(CF_TEXT)) 
		EnableMenuItem(hmenuPopup, ID_PASTE, MF_ENABLED | MF_BYCOMMAND);
	else
		EnableMenuItem(hmenuPopup, ID_PASTE, MF_GRAYED | MF_BYCOMMAND);

	return 0;
}

EM_GETSELを送信しているのは、現在テキストが選択されているかを確認するためです。 dwStartとdwEndが異なっている場合はテキストが選択されていることを意味するため、 このような場合は切り取りとコピーの項目をEnableMenuItemで有効します。 逆に、テキストが選択されていない場合は、EnableMenuItemで項目を無効にします。 貼り付けは、クリップボードにテキストがある場合のみ実行できるので、 IsClipboardFormatAvailableにCF_TEXTを指定することでこれを確認しています。

今回は、入力されたテキストを保存するかどうかの確認をWM_CLOSEで行っています。 ここで保存するように選択した場合は、ウインドウを閉じずに保存処理を行います。

case WM_CLOSE:
	if (SendMessage(hwndEdit, EM_GETMODIFY, 0, 0)) {
		if (MessageBox(NULL, TEXT("テキストが変更されています。保存しますか?"), TEXT("保存"), MB_YESNO) == IDYES) {
			HANDLE hFile;
			LPTSTR lpszText;
			DWORD  dwSize, dwWriteByte;
			
			dwSize = GetWindowTextLength(hwndEdit) * sizeof(TCHAR);

			hFile = CreateFile(TEXT("sample.txt"), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
			if (dwSize > 0) {
				lpszText = (LPTSTR)HeapAlloc(GetProcessHeap(), 0, dwSize);
				GetWindowText(hwndEdit, lpszText, dwSize);
				WriteFile(hFile, lpszText, dwSize, &dwWriteByte, NULL);
				CloseHandle(hFile);
			}
			CloseHandle(hFile);
			
			SendMessage(hwndEdit, EM_SETMODIFY, FALSE, 0);
			return 0;
		}
	}
	break;

最初にEM_GETMODIFYでテキストが変更されているかを確認する点が重要です。 変更されていない場合は、保存処理を実行する必要はありません。 ユーザーが保存するよう選択した場合は、 GetWindowTextLengthでテキストの文字数を取得し、 これにsizeof(TCHAR)を掛けることでテキストのサイズを算出します。 このようなサイズが必要になるのは、 GetWindowTextにどれだけのサイズを持ったバッファを指定すればよいか分からないためです。 よって、HeapAllocで動的にバッファを確保し、その後にGetWindowTextでバッファを初期化します。 後は、このバッファをWriteFileに指定すれば、バッファの中身はファイルに書き込まれることになります。 書き込みが終了した場合は、変更が終了したことを示すためにEM_SETMODIFYを送信します。 このとき、WPARAMにはFALSEを指定します。


戻る