barus's diary

とても真面目なblogですにゃ

VC++でドラゴン曲線を描画する for Visual Studio 2017

Visual Studio 2015で遊んできましたが

2017がリリースされていることを知りました。

 

なんと、この2017版ではVC++のリソース部分(表示画面)を

編集できるようになっているようです。

確か、2015までのVC++ではリソース部分を編集出来なかったので

C#で画面を作っていました。

 

無料版でここまで出来るとすごいですね。

さっそく2017版を試したいと思います。

 

今回、VC++で作成したのはドラゴン曲線です。

回帰回数と、表示の大きさスケールを指定してドラゴン曲線するものです。

f:id:hatakeka:20170619155608p:plain

 

では、まず。

 

Visual Studio 2017(無料版)をダウンロードします。

 

Visual Studio 2017 の新機能 | 製品の更新機能と新機能でダウンロード。

 

オフラインインストールの仕方は以下を参考にしました。

 

『Visual Studio 2017』をオフラインでインストールする方法 - Qiita 

Windows10にインストールしています。

 

f:id:hatakeka:20170619093633p:plain

 

vs_community_**.exe の*部分はダウンロード時期によって異なります。

 

※別にオフラインでなくても構わないというかたは、

 ダウンロードしたEXEをWクリックでも構いません。

 

Windows PoerShellを起動します。

Windows10の場合。Windows+R でファイル名を指定して実行を起動させ

名前にPowerShellと入力。

f:id:hatakeka:20170619094017p:plain

 

vs_community_**.exeがあるフォルダに移動し、

コマンドラインに以下のように入力します。

 

vs_community.exe --layout 「展開するフォルダパス」

 

PS C:\>
PS C:\> cd M:\windows10_初期インストールバックアップ\Windowsインストール時にダウンロード\vs2017
PS M:\windows10_初期インストールバックアップ\Windowsインストール時にダウンロード\vs2017>
PS M:\windows10_初期インストールバックアップ\Windowsインストール時にダウンロード\vs2017> .\vs_community__203343543.1497666155.exe --layout M:\windows10_初期インストールバッ
クアップ\Windowsインストール時にダウンロード\vs2017

 

 

開発環境は以上でそろいました。

新しいプロジェクトをVC++でWin32プロジェクト名称をdefaultとしました。

 

f:id:hatakeka:20170619094506p:plain

 

 次へ

f:id:hatakeka:20170619094642p:plain

 

どこも変更せずにOKを押す。

f:id:hatakeka:20170619094719p:plain

 

 

プロジェクト>プロパティ>全般で

文字セットをUnicodeからマルチバイト文字に変換しています。

f:id:hatakeka:20170619164120p:plain

 

 

 

何もいじらずに ビルド(F7やF5)などしてコンパイルすると

以下のような画面がでます。

f:id:hatakeka:20170620093834p:plain

 

この画面にボタンなどをペタペタ張る方法があるかもしれませんが

私はわからないので、以下にダイアログを作成します。

 

いじるファイルは3種類

 

default.rc   ・・・リソースファイル 画面の表示を編集

default.cpp   ・・・ソース

default.h    ・・・ヘッダーファイル

 

の3つだけです。

 

 リソースファイルの編集

defautl.rcをWクリックします。

もし、Resource.hを開いていたら閉じて再度Wクリック試してみてください。

f:id:hatakeka:20170619160833p:plain

 

Wクリックするとリソースビューが表示されます。

この画面にて、ダイアログを追加します。 

f:id:hatakeka:20170619095742p:plain

 

 初期のダイアログはこんな感じです。

f:id:hatakeka:20170619100021p:plain

 

このダイアログのIDをプロパティで確認する。

IDD_DIALOG1

 

f:id:hatakeka:20170619100106p:plain

 

ツールボックスより

EditControlとButtonとStaticTextを選択しペタペタ張り付ける。

f:id:hatakeka:20170619132029p:plain

 

以下のような感じ。

f:id:hatakeka:20170619155741p:plain

 

エディタが勝手に編集してくれるので 

Resource.hは下手にいじらないほうがいいです。

が中身を見てみます。

 

// default.rc で使用
//
#define IDC_MYICON 2
#define IDD_DEFAULT_DIALOG 102
#define IDS_APP_TITLE 103
#define IDD_ABOUTBOX 103
#define IDM_ABOUT 104
#define IDM_EXIT 105
#define IDI_DEFAULT 107
#define IDI_SMALL 108
#define IDC_DEFAULT 109
#define IDR_MAINFRAME 128
#define IDD_DIALOG1 129
#define IDC_EDIT1 1000
#define IDC_BUTTON1 1002
#define IDC_EDIT2 1003
#define IDC_STATIC -1

 

 

以上の赤文字が追加された箇所です。

確認したら、Resource.hは閉じてください。

(手で編集しないほうが無難なので)

 

StaticTextにはそれぞれ、回数とスケールというのを

プロパティのCaptionに設定する。

EditControlのOKボタンをドラゴンに変更した。

 

それぞれのIDを確認する。

ダイアログ本体 IDD_DIALOG1 

回数のEditControlは IDC_EDIT1

スケールのEditControlは IDC_EDIT2

ドラゴンボタンは IDOK 

Polygonサンプルボタンは IDC_BUTTON1

 

 

 

 以上、表面の準備は出来たのでソースをいじります。

 

編集するファイルは

default.h

default.cpp

の赤文字です。青文字は削除しても構わないです。

編集したらビルド(F5やF7)をして実行ファイルを作ります。

 

f:id:hatakeka:20170619162340p:plain

 

default.h

 

#pragma once

#include "resource.h"

#include <windows.h>	//追加1

//	ダイアログボックスプロシージャー 追加2
LRESULT CALLBACK DlgMain(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam);

//ポリゴンラインのサンプル
void mydraw(HWND hDlg);

//ドラゴン曲線を描くメソッド 追加
void drawDragon_main(HDC hdc, int number, int scale);
void drawDragon(HDC hdc, POINT a, POINT b, int n, int scale);


 

default.cpp

 

// default.cpp : アプリケーションのエントリ ポイントを定義します。
//

#include "stdafx.h"
#include "default.h"


#define MAX_LOADSTRING 100

// グローバル変数:
HINSTANCE hInst;                                // 現在のインターフェイス
WCHAR szTitle[MAX_LOADSTRING];                  // タイトル バーのテキスト
WCHAR szWindowClass[MAX_LOADSTRING];            // メイン ウィンドウ クラス名

// このコード モジュールに含まれる関数の宣言を転送します:
ATOM                MyRegisterClass(HINSTANCE hInstance);
BOOL                InitInstance(HINSTANCE, int);
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK    About(HWND, UINT, WPARAM, LPARAM);



int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPWSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    // TODO: ここにコードを挿入してください。

	//	ダイアログボックスの表示 追加4
	DialogBox(hInst, MAKEINTRESOURCE(IDD_DIALOG1), 0, (DLGPROC)DlgMain);
	return 0;

  // 以下削除
    // グローバル文字列を初期化しています。
    LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    LoadStringW(hInstance, IDC_DEFAULT, szWindowClass, MAX_LOADSTRING);
    MyRegisterClass(hInstance);

    // アプリケーションの初期化を実行します:
    if (!InitInstance (hInstance, nCmdShow))
    {
        return FALSE;
    }

    HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_DEFAULT));

    MSG msg;

    // メイン メッセージ ループ:
    while (GetMessage(&msg, nullptr, 0, 0))
    {
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }

    return (int) msg.wParam;
}


//追加
LRESULT CALLBACK DlgMain(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam) {
	TCHAR buf[256];
	TCHAR number[256];
	TCHAR scale[256];
	SYSTEMTIME LocalTime;
	DWORD s, sz;
	static HWND hEdit1, hEdit2;
	switch (msg) {
	case WM_INITDIALOG:
	{
		//回数の初期
		hEdit1 = GetDlgItem(hDlg, IDC_EDIT1);
		SetWindowText(hEdit1, _TEXT("10"));
		//スケールの初期
		hEdit2 = GetDlgItem(hDlg, IDC_EDIT2);
		SetWindowText(hEdit2, _TEXT("2"));

		return TRUE;
	}
	case WM_COMMAND:
		switch (LOWORD(wParam)) {
		case IDC_BUTTON1:
			InvalidateRect(hDlg, NULL, TRUE);//再描画
			mydraw(hDlg);
			return TRUE;
		case IDOK:
		{
			InvalidateRect(hDlg, NULL, TRUE);//再描画
			//回数
			GetWindowText(hEdit1, number, sizeof(number) / sizeof(TCHAR));
			//スケール
			GetWindowText(hEdit2, scale,  sizeof(scale)  / sizeof(TCHAR));

			PAINTSTRUCT ps;
			HDC hdc = BeginPaint(hDlg, &ps);

			drawDragon_main(hdc, _ttoi(number), _ttoi(scale));

			EndPaint(hDlg, &ps);

			return TRUE;
		}
		case IDCANCEL:
			EndDialog(hDlg, TRUE);
			return TRUE;
		default:
			return FALSE;

		}
	case WM_PAINT:
	{
		
		PAINTSTRUCT ps;
		HDC hdc;
		hdc = BeginPaint(hDlg, &ps);

		TextOut(hdc, 50, 70, "Hello!", 13);

		EndPaint(hDlg, &ps);
	}
	
	break;
	default:
		return FALSE;
	}
	return TRUE;
}

void mydraw(HWND hDlg) 
{
	PAINTSTRUCT ps;
	HDC hdc = BeginPaint(hDlg, &ps);
	POINT       points[5];

	Rectangle(hdc, 10, 20, 600, 700);
	Ellipse(hdc, 10, 20, 60, 70);

	points[0].x = 110; points[0].y = 20;
	points[1].x = 130; points[1].y = 70;
	points[2].x = 80; points[2].y = 40;
	points[3].x = 140; points[3].y = 40;
	points[4].x = 90; points[4].y = 70;
	Polygon(hdc, points, 5);


	EndPaint(hDlg, &ps);
	
	
}



//ドラゴン
void drawDragon_main(HDC hdc, int number, int scale)
{
	POINT p, q;
	p.x = 170; p.y = 140;
	q.x = 400; q.y = 350;
	
	//対となる二点の間にドラゴン曲線を描きます
	drawDragon(hdc, p, q, number, scale);

}

//ドラゴン曲線を描くメソッド 追加
void drawDragon(HDC hdc, POINT a, POINT b, int n, int scale)
{

	POINT c, pl[2];

	int xx, yy;
	xx = b.x - a.x;
	yy = -(b.y - a.y);

	c.x = a.x + (xx + yy) / 2;
	c.y = b.y + (xx + yy) / 2;

	//最後なので、実際に線を引きます
	if (n <= 0)
	{
		//スケールの変更
		a.x /= scale; a.y /= scale;
		b.x /= scale; b.y /= scale;
		c.x /= scale; c.y /= scale;

		pl[0] = a;	pl[1] = c;
		Polygon(hdc, pl, 2);//点Aから点Cへ
		pl[0] = b;	pl[1] = c;
		Polygon(hdc, pl, 2);//点Bから点Cへ
		
	}
	//最後ではないので、さらにメソッドを呼び出します(再帰処理)
	else
	{
		drawDragon(hdc, a, c, n - 1, scale);    //点Aから点Cへ
		drawDragon(hdc, b, c, n - 1, scale);    //点Bから点Cへ
	}
}


//
//  関数: MyRegisterClass()
//
//  目的: ウィンドウ クラスを登録します。
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEXW wcex;

    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_DEFAULT));
    wcex.hCursor        = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);
    wcex.lpszMenuName   = MAKEINTRESOURCEW(IDC_DEFAULT);
    wcex.lpszClassName  = szWindowClass;
    wcex.hIconSm        = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

    return RegisterClassExW(&wcex);
}

//
//   関数: InitInstance(HINSTANCE, int)
//
//   目的: インスタンス ハンドルを保存して、メイン ウィンドウを作成します。
//
//   コメント:
//
//        この関数で、グローバル変数インスタンス ハンドルを保存し、
//        メイン プログラム ウィンドウを作成および表示します。
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   hInst = hInstance; // グローバル変数インスタンス処理を格納します。

   HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);

   if (!hWnd)
   {
      return FALSE;
   }

   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

   return TRUE;
}

//
//  関数: WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  目的:    メイン ウィンドウのメッセージを処理します。
//
//  WM_COMMAND  - アプリケーション メニューの処理
//  WM_PAINT    - メイン ウィンドウの描画
//  WM_DESTROY  - 中止メッセージを表示して戻る
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_COMMAND:
        {
            int wmId = LOWORD(wParam);
            // 選択されたメニューの解析:
            switch (wmId)
            {
            case IDM_ABOUT:
                DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
                break;
            case IDM_EXIT:
                DestroyWindow(hWnd);
                break;
            default:
                return DefWindowProc(hWnd, message, wParam, lParam);
            }
        }
        break;
    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            // TODO: HDC を使用する描画コードをここに追加してください...
            EndPaint(hWnd, &ps);
        }
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

// バージョン情報ボックスのメッセージ ハンドラーです。
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    UNREFERENCED_PARAMETER(lParam);
    switch (message)
    {
    case WM_INITDIALOG:
        return (INT_PTR)TRUE;

    case WM_COMMAND:
        if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
        {
            EndDialog(hDlg, LOWORD(wParam));
            return (INT_PTR)TRUE;
        }
        break;
    }
    return (INT_PTR)FALSE;
}

    

 

 // ダイアログボックスの表示 追加4 DialogBox(hInst, MAKEINTRESOURCE(IDD_DIALOG1), 0, (DLGPROC)DlgMain);

wWinMainにてダイアログ(IDD_DIALOG1)を呼び出しています。

 

DlgMainがダイアログの

switch (msg) {
    case WM_INITDIALOG:
    ( 略 )
    case WM_COMMAND:
      switch (LOWORD(wParam)) {      case IDC_BUTTON1:
          InvalidateRect(hDlg, NULL, TRUE);//再描画
            mydraw(hDlg);

            return TRUE;
        case IDOK:
            ( 略 )

switch文でイベントを拾っています。
WM_INITDIALOGは名称から推測するに初期に呼ばれるようですね。
WM_COMMANDはボタンなどのイベントを拾っています。
IDC_BUTTON1
Polygonサンプルボタンが押されるとICD_BUTTONに来ます。
InvaliadateRectは再描画みたいです。

C#では再描画は勝手にしてくれた記憶が・・・
確か、チラつきも勝手に補正してくれていたと思う。
最新のVC++でもチラつき勝手に補正してくれるかは
ググらないとわからない。

mydraw()で簡単なサンプルの呼び出し。
ドラゴンボタンを押すと IDOK に入ります。
回帰回数の文字列をGetWindowTextで取得しています。
GetWindowText(hEdit1, number, sizeof(number) / sizeof(TCHAR));
_ttoi()関数でint型にしています。
drawDragon_main(hdc, _ttoi(number), _ttoi(scale));

atoi()なら見たことあるので、ナニコレ?と思いましたが
TCHAR型からの変換ではこれ使うみたいですね。
bool	Test()
{
	////////////////////////////////
	//	10進数表記の文字列をint型数値に変換
	//
	{
		//char型文字列から変換
		int		nValue;
		char	pszText[] = "123";

		nValue = ::atoi(pszText);
	}
	{
		//wchar_t型文字列から変換
		int		nValue;
		wchar_t	pszText[] = L"123";

		nValue = ::_wtoi(pszText);
	}
	{
		//TCHAR型文字列から変換
		int		nValue;
		TCHAR	pszText[] = _T("123");

		nValue = ::_ttoi(pszText);
	}
	{
		//CString型文字列から変換
		int		nValue;
		CString	strText = _T("123");

		nValue = ::_ttoi(strText);
	}

	return	true;
}

参考 http://www.usefullcode.net/2007/02/string2int.html
numberに回数、scaleに表示の大きさを入れています。
あとは、C#で作成したドラゴン関数の処理です。

		//スケールの変更
		a.x /= scale; a.y /= scale;
		b.x /= scale; b.y /= scale;
		c.x /= scale; c.y /= scale;

		pl[0] = a;	pl[1] = c;
		Polygon(hdc, pl, 2);//点Aから点Cへ
		pl[0] = b;	pl[1] = c;
		Polygon(hdc, pl, 2);//点Bから点Cへ

Polygonを使ってラインを結んでいます。


以上、VC++ for VisualStudio2017のサンプルでした。



補足

ネットサーフィンしている際に CDC を使っているサイトがあり
これを利用しようとしたところ
「個別のコンポーネント」をインストールする必要があると
以下のようなメッセージが出て怒られました。

f:id:hatakeka:20170619151858p:plain

今回は、CDCを利用していませんが、利用した方が便利かと思うので
Visual Studio Installer」を選択して起動し
必要なコンポーネントをダウンロードした方がいいかもしれません。

f:id:hatakeka:20170619151943p:plain

 

 

参考 Visual Studio 2017にコンポーネント(機能)を追加する方法 - Qiita

 

 

 

以上です。