きままにブログ

プログラミングを主とした私のメモ帳です。寂しいのでコメントください笑

ドラッグできる子供ウィンドウ

ドラッグして子供ウィンドウを親ウィンドウの内部で動かせる。右クリックで削除できる。複数追加できる。まだ自動的に動くような処理は書いていないがひとまずWin32APIの勉強になった。

途中、どうしても分からないバグがあったが、これはC言語何でも質問掲示板 • C言語交流フォーラム ~ mixC++ ~で質問させて頂いたところ私の馬鹿な間違いを指摘して頂いたのでなんとかなった。大変感謝しております。(個人サイトなのに大変活発な掲示板で、2ちゃ○ねるの専用スレよりも充実しています。)

ビットマップのハンドルはそのままで済んだが、コンパチブルなデバイスコンテキストは毎回取得しないといけなかったが正しいのか不明〔後で調べておきます〕。

削除は、削除通知を親ウィンドウにコマンドとして送ってそれを元に処理した。ウィンドウ=C++のクラスのインスタンスとして構成しているので、これらが関連づけられており、MovingBallクラスに親のMainWindowへのポインタを持たせなくて済んでいる。ただし美しくないので今後はこの方法を用いないことにする。(この迷いは前の記事で述べた、コンテナクラスとその要素の関係に述べた。)

一番悩んだのは動かしている最中にどうやって描画したものを削除するか、だったが、結局安直な描画する範囲をコピーして保存することにした。

  • main.h
#pragma once
#include <Windows.h>
#include <exception>
#include <memory>
#include <list>

#include "moving_ball.h"

class MainWindow {
public:
	MainWindow(); 
	
	static void Register();
	
	void create(WCHAR* title);
	void show();

protected:
	void onCreate();
	void onPaint();
	void onLButtonUp();

private:
	static LRESULT CALLBACK WindowProc(HWND h_wnd, UINT msg, WPARAM wparam, LPARAM lparam);
	
	LRESULT mainProc(HWND h_wnd, UINT msg, WPARAM wp, LPARAM lp);

	HWND h_wnd;
	SHORT width;
	SHORT height;

	std::list<std::unique_ptr<MovingBall>> moving_balls;

	int pattern;
};
  • main.cpp
#include "main.h"

int WINAPI WinMain(HINSTANCE h_instance, HINSTANCE h_prev_instance, LPSTR command_line, int command_show) {
	MainWindow main_window;

	try {
		MainWindow::Register();

		main_window.create(L"milfeulle");
		main_window.show();
	}

	catch(const std::exception&) {
		return -1;
	}

	MSG msg;
	int result;

	while(TRUE) {
		result = GetMessage(&msg, NULL, 0, 0);

		if(result == 0 || result == -1) {
			break;
		}

		DispatchMessage(&msg);
	}

	return msg.wParam;
	
}

void MainWindow::Register() {
	WNDCLASS wndc;

	wndc.style = CS_HREDRAW | CS_VREDRAW;
	wndc.lpfnWndProc = WindowProc;
	wndc.cbClsExtra = wndc.cbWndExtra = 0;
	wndc.hInstance = GetModuleHandle(NULL);
	wndc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	wndc.hCursor = LoadCursor(NULL, IDC_ARROW);
	wndc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wndc.lpszMenuName = L"MYMENU";
	wndc.lpszClassName = L"MainWindow";

	if(!RegisterClass(&wndc)) {
		throw std::exception();
	}
}

MainWindow::MainWindow() {
	width = 600;
	height = 400;
}

void MainWindow::create(WCHAR* title) {
	CreateWindow(
		L"MainWindow", title,
		WS_OVERLAPPEDWINDOW,
		100, 100, width, height, NULL, NULL,
		GetModuleHandle(NULL), this);

	// ここに来るのはWM_CREATEメッセージの処理の後
	if(h_wnd == NULL) {
		throw std::exception();
	}
}

void MainWindow::show() {
	ShowWindow(h_wnd, SW_SHOW);
}

void MainWindow::onCreate() {
	MovingBall::Register();
	pattern = 0;
}

void MainWindow::onPaint() {
	PAINTSTRUCT ps;
	HDC h_dc;

	h_dc = BeginPaint(h_wnd, &ps);
	SelectObject(h_dc, GetStockObject(WHITE_BRUSH));

	switch(pattern) {
	case 1:
		Ellipse(h_dc, 10, 10, 200, 200);
		break;
	case 2:
		RoundRect(h_dc, 10, 10, 200, 200, 10, 10);
		break;
	case 3:
		Rectangle(h_dc, 10, 10, 200, 200);
		break;
	case 4:
	{
				const int r = (200 - 10) / 2;
				const double ws = 3.14159265 / 4, we = 3.14159265;
				Pie(h_dc, 10, 10, 200, 200, 10 + r + r * cos(ws), 10 + r - r * sin(ws), 10 + r + r * cos(we), 10 + r - r * sin(we));
	}
		break;
	}
	
	EndPaint(h_wnd, &ps);
}

void MainWindow::onLButtonUp() {
	
}

LRESULT CALLBACK MainWindow::WindowProc(HWND h_wnd, UINT msg, WPARAM wp, LPARAM lp) {
	MainWindow* self = (MainWindow*)GetWindowLongPtr(h_wnd, GWLP_USERDATA);

	if(self == NULL) {
		if(msg == WM_CREATE) {
			self = (MainWindow*)((LPCREATESTRUCT)lp)->lpCreateParams;

			SetWindowLongPtr(h_wnd, GWLP_USERDATA, (LONG_PTR)self);
			self->h_wnd = h_wnd;

			self->onCreate();
		}

		return DefWindowProc(h_wnd, msg, wp, lp);
	}

	else {
		return self->mainProc(h_wnd, msg, wp, lp);
	}
}

LRESULT MainWindow::mainProc(HWND h_wnd, UINT msg, WPARAM wp, LPARAM lp) {
	switch(msg) {
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	case WM_LBUTTONUP:
		onLButtonUp();
		break;
	case WM_PAINT:
		onPaint();
		break;
	case WM_COMMAND:
		switch(LOWORD(wp)) {
		case 1: // 終了
			DestroyWindow(h_wnd);
			break;
		case 101: // 動くウィドウ
			moving_balls.push_back(std::make_unique<MovingBall>(h_wnd));
			break;
		case 1001: // 動くウィドウ - 削除
			for(auto it = moving_balls.begin(); it != moving_balls.end();) {
				if((*it).get() == (MovingBall*)lp) {
					it = moving_balls.erase(it);
					continue;
				}

				++it;
			}
			break;
		case 201: // 円
			MessageBox(h_wnd, L"円", L"図形", MB_OK);
			pattern = 1;
			InvalidateRect(h_wnd, NULL, TRUE);
			break;
		case 202: // 角丸矩形
			pattern = 2;
			MessageBox(h_wnd, L"角丸矩形", L"図形", MB_OK);
			InvalidateRect(h_wnd, NULL, TRUE);
			break;
		case 203: // 矩形
			pattern = 3;
			MessageBox(h_wnd, L"矩形", L"図形", MB_OK);
			InvalidateRect(h_wnd, NULL, TRUE);
			break;
		case 204: // 扇形
			pattern = 4;
			MessageBox(h_wnd, L"扇形", L"図形", MB_OK);
			InvalidateRect(h_wnd, NULL, TRUE);
			break;
		}
		break;
	}

	return DefWindowProc(h_wnd, msg, wp, lp);
}
  • moving_ball.h
#pragma once

#include <Windows.h>

class MovingBall {
public:
	MovingBall(HWND h_wnd);
	~MovingBall(); 
	
	static void Register();
	
private:
	static LRESULT CALLBACK WindowProc(HWND h_wnd, UINT msg, WPARAM wparam, LPARAM lparam);
	static void DrawRect(HWND h_wnd, POINTS sp, POINTS ep);

	void onLButtonUp(LPARAM lp);
	void onMouseMove(LPARAM lp);
	void onLButtonDown(LPARAM lp);
	void onRButtonUp(LPARAM lp);

	void onFrame();

	LRESULT mainProc(HWND h_wnd, UINT msg, WPARAM wp, LPARAM lp);
	
	HWND h_wnd;
	SHORT x, y;
	SHORT width, height;
	//HDC h_before_dc;
	HBITMAP h_before_bitmap;
	POINTS start_point, current_point;

	double rx, ry;
	double vx, vy;

	bool move_flag;
};
  • moving_ball.cpp
#include "moving_ball.h"
#include <exception>

MovingBall::MovingBall(HWND h_wnd) {
	rx = ry = x = y = 0;
	width = height = 20;
	move_flag = false;

	CreateWindow(
		L"MovingBall", NULL,
		WS_CHILD | WS_VISIBLE,
		x, y, width, height, h_wnd, NULL,
		GetModuleHandle(NULL), this);

	// ここに来るのはWM_CREATEメッセージの処理の後
	if(this->h_wnd == NULL) {
		throw std::exception();
	}
}

void MovingBall::Register() {
	WNDCLASS wndc;

	wndc.style = CS_DBLCLKS;
	wndc.lpfnWndProc = WindowProc;
	wndc.cbClsExtra = wndc.cbWndExtra = 0;
	wndc.hInstance = NULL;
	wndc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
	wndc.hCursor = LoadCursor(NULL, IDC_ARROW);
	wndc.hbrBackground = (HBRUSH)GetStockObject(GRAY_BRUSH);
	wndc.lpszMenuName = NULL;
	wndc.lpszClassName = L"MovingBall";

	if(!RegisterClass(&wndc)) {
		throw std::exception();
	}
}

void MovingBall::DrawRect(HWND h_wnd, POINTS sp, POINTS ep) {
	HDC h_dc = GetDC(h_wnd);
	
	HPEN h_pen = CreatePen(PS_SOLID, 2, RGB(0x00, 0x99, 0xff));
	SelectObject(h_dc, h_pen);

	HBRUSH h_brush = CreateHatchBrush(HS_CROSS, RGB(0xff, 0x77, 0x00));
	SelectObject(h_dc, h_brush);
	
	Rectangle(h_dc, sp.x + 1, sp.y + 1, ep.x - 1, ep.y - 1);

	ReleaseDC(h_wnd, h_dc);
	DeleteObject(h_pen);
	DeleteObject(h_brush);
}

LRESULT CALLBACK MovingBall::WindowProc(HWND h_wnd, UINT msg, WPARAM wp, LPARAM lp) {
	MovingBall* self = (MovingBall*)GetWindowLongPtr(h_wnd, GWLP_USERDATA);

	if(self == NULL) {
		if(msg == WM_CREATE) {
			self = (MovingBall*)((LPCREATESTRUCT)lp)->lpCreateParams;

			SetWindowLongPtr(h_wnd, GWLP_USERDATA, (LPARAM)self);
			self->h_wnd = h_wnd;
		}

		return DefWindowProc(h_wnd, msg, wp, lp);
	}

	else {
		return self->mainProc(h_wnd, msg, wp, lp);
	}
}

void MovingBall::onLButtonDown(LPARAM lp) {
	if(move_flag) {
		return;
	}
	
	SetCapture(h_wnd);

	start_point.x = LOWORD(lp);
	start_point.y = HIWORD(lp);

	current_point.x = x;
	current_point.y = y;

	// デバイスコンテキストの保存
	{
		HWND h_parent_wnd = GetParent(h_wnd);
		HDC h_dc = GetDC(h_parent_wnd);
		h_before_bitmap = CreateCompatibleBitmap(h_dc, width, height);
		HDC h_before_dc = CreateCompatibleDC(h_dc);
		SelectObject(h_before_dc, h_before_bitmap);

		BitBlt(h_before_dc, 0, 0, width, height, h_dc, current_point.x, current_point.y, SRCCOPY);
		DeleteDC(h_before_dc);
		ReleaseDC(h_parent_wnd, h_dc);
	}

	move_flag = true;
	SetCursor(LoadCursor(NULL, IDC_HAND));
}
void MovingBall::onMouseMove(LPARAM lp) {
	if(!move_flag) {
		return;
	}
	static int count = 0;

	HWND h_parent_wnd = GetParent(h_wnd);

	{
		HDC h_dc = GetDC(h_parent_wnd);
		TCHAR str[256];
		wsprintf(str, L"%d", count++);
		TextOut(h_dc, 100, 100, str, lstrlen(str));

		{
			HDC h_before_dc = CreateCompatibleDC(h_dc);
			SelectObject(h_before_dc, h_before_bitmap);
			// 直前描画を戻す
			BOOL flag = BitBlt(h_dc, current_point.x, current_point.y, width, height, h_before_dc, 0, 0, SRCCOPY);

			current_point.x = x + LOWORD(lp) - start_point.x;
			current_point.y = y + HIWORD(lp) - start_point.y;

			// 移動先を保存
			BitBlt(h_before_dc, 0, 0, width, height, h_dc, current_point.x, current_point.y, SRCCOPY);
			DeleteDC(h_before_dc);
		}
		ReleaseDC(h_wnd, h_dc);
	}

	// 移動先の枠を描画
	DrawRect(h_parent_wnd, current_point, POINTS { current_point.x + width, current_point.y + height });
}

void MovingBall::onLButtonUp(LPARAM lp) {
	if(!move_flag) {
		return;
	}

	ReleaseCapture();

	// 後始末
	{
		HWND h_parent_wnd = GetParent(h_wnd);
		HDC h_dc = GetDC(h_parent_wnd);
		HDC h_before_dc = CreateCompatibleDC(h_dc);
		SelectObject(h_before_dc, h_before_bitmap);
		BitBlt(h_dc, current_point.x, current_point.y, width, height, h_before_dc, 0, 0, SRCCOPY);

		DeleteDC(h_before_dc);
		DeleteObject(h_before_bitmap);
		ReleaseDC(h_parent_wnd, h_dc);
	}

	x += LOWORD(lp) - start_point.x;
	y += HIWORD(lp) - start_point.y;

	MoveWindow(h_wnd, x, y, width, height, TRUE);

	rx = x;
	ry = y;

	InvalidateRect(h_wnd, NULL, TRUE);
	SetCursor(LoadCursor(NULL, IDC_ARROW));

	move_flag = false;
}

void MovingBall::onFrame() {

}

void MovingBall::onRButtonUp(LPARAM lp) {
	HMENU h_menu = LoadMenu(GetModuleHandle(NULL), L"MYPOPUP");
	HMENU h_sub_menu = GetSubMenu(h_menu, 0);
	POINT point = { LOWORD(lp), HIWORD(lp) };
	ClientToScreen(h_wnd, &point);
	TrackPopupMenu(h_sub_menu, TPM_LEFTALIGN, point.x, point.y, 0, h_wnd, NULL);
	DestroyMenu(h_menu);
}

LRESULT MovingBall::mainProc(HWND h_wnd, UINT msg, WPARAM wp, LPARAM lp) {
	switch(msg) {
	case WM_LBUTTONDOWN:
		onLButtonDown(lp);
		break; 
	case WM_MOUSEMOVE:
		onMouseMove(lp);
		break;
	case WM_RBUTTONUP:
		onRButtonUp(lp);
		break;
	case WM_LBUTTONUP:
		onLButtonUp(lp);
	case WM_COMMAND:
		switch(LOWORD(wp)) {
		case 101: // 削除
			SendMessage(GetParent(h_wnd), msg, 1001, (LPARAM)this);
			break;
		case 102: // 動かす
			
			break;
		}
		break;
	}

	return DefWindowProc(h_wnd, msg, wp, lp);
}

MovingBall::~MovingBall() {
	DestroyWindow(h_wnd);
}