VC++ Tips

コントロール関連

  1. タブコントロールとエディットボックスを重ねるとエディットボックスの方が下となり見えない
  2. タブコントロール
  3. ラジオボタン

Win32API関連

  1. ドラッグアンドドロップ
  2. フォルダ選択ダイアログ
  3. 書式化エラーコードの表示
  4. ファイル削除時の事前ファイル存在確認
  5. フォルダ削除関数
  6. フォルダ削除方法
  7. シェル実行終了タイミング取得
  8. シェル実行終了タイミング取得 その2

その他

  1. 改行
  2. 数値をCString型へ変更する
  3. ダイアログにビットマップを貼る
  4. ダイアログの「OK」、「キャンセル」ボタンの処理
  5. ファイルのバイナリでの読み込み、表示方法
  6. TCP/IPを利用したプログラム

(注:以下に記した情報を用いたおこないの結果生じた問題等には一切責任を負いません)

タブコントロールとエディットボックスを重ねるとエディットボックスの方が下となり見えない

どうすれば重なりの配置を変更できるの?
top

タブコントロール

--------------------
1)初期化
--------------------
// タブコントロールの初期化
TC_ITEM tc0;
tc0.mask = TCIF_TEXT;
tc0.pszText = "情報";
m_tab.InsertItem( 0, &tc0 );
--------------------

2)SelChange タブをクリックしたときに呼ばれる関数
	1>タブにIDをつけておく(プロパティ 例:IDC_TAB_MAIN)
	2>ClassWizardで、「メッセージマップ」タブの
	「オブジェクトID」でタブのIDを指定し、「メッセージ」
	で「TCN_CELCHANGE」を選択
	そして関数を追加する
	例:
	OnSelchangeTabMain(NMHDR* pNMHDR,LRESULT* pResult)

3)どのタブがクリックされたかを知るには
	int n = m_tab.GetCurSel();
--------------------
top

ラジオボタン

--------------------
排他的なやつ

1)複数のラジオボタンを配置し、グループボックスで囲む

2)ソースでの記述
CButton* testCheck = (CButton*)GetDlgItem(IDC_BUTTON1);
testCheck->SetCheck(1); // IDC_BUTTON1のラジオボタンにチェックを入れる
testCheck->SetCheck(0): // チェックをはずす
tmp = testCheck->GetCheck();  // チェックが入っているかどうかの確認
--------------------
top

ドラッグアンドドロップ

1> onInitDialog() に
--------------------
// ドラッグアンドドロップをサポート
DragAcceptFiles(TRUE);
--------------------
を追加

2> ClassWizard の[クラス情報]タブの[詳細設定オプション][メッセージフィルタ]
を「ダイアログ」から「ウィンドウ」に変更

3> ClassWizard の[メッセージマップ]タブの[オブジェクトID]で[C〜Dialog]を
選択し、メッセージで[WM_DROPFILES]を選択し、[関数を追加]する

4> OnDropFiles()に以下を記述
--------------------
char dropFolderName[MAX_PATH + 1];
// ドロップされたフォルダ名の取得
DragQueryFile( hDropInfo, 0, dropFolderName, MAX_PATH );
m_temp = dropFolderName;
m_material = dropFolderName;
UpdateData( FALSE );
--------------------
top


フォルダ選択ダイアログ

1)出し方
--------------------
BROWSEINFO bInfo;
LPITEMIDLIST pIDList;
TCHAR szDisplayName[MAX_PATH+1];

// BROWSEINFO構造体に値を設定
bInfo.hwndOwner             = AfxGetMainWnd()->m_hWnd;
bInfo.pidlRoot              = NULL;
bInfo.pszDisplayName        = szDisplayName;
bInfo.lpszTitle             = _T("フォルダの選択");
bInfo.ulFlags               = BIF_RETURNONLYFSDIRS;
bInfo.lpfn                  = NULL;
bInfo.lParam                = (LPARAM)0;

// フォルダ選択ダイアログを表示
pIDList = ::SHBrowseForFolder(&bInfo);
if(pIDList == NULL){
	// フォルダが選択されずにダイアログが閉じられた
}else{
	// ItemIDListをパス名に変換します
	if(!::SHGetPathFromIDList(pIDList, szDisplayName)){
		// エラー処理
		AfxMessageBox( "パス名取得エラー発生しました",
						MB_OK );
	}
	// szDisplayNameに選択されたフォルダのパスが入っています
	toEdit = szDisplayName;
	// 最後にpIDListのポイントしているメモリを開放します
	::CoTaskMemFree( pIDList );
}
--------------------

2)フォルダ名、フォルダ名を除いたパスの取得方法
--------------------
// 一番右の"\"の位置
int b = m_temp.ReverseFind( '\\' );
// フォルダ名を除いたパス名
prevPath = m_temp.Left( b );
// フォルダ名
CString tempFileName = m_temp.Right( m_temp.GetLength()-1-b );
--------------------
top

書式化エラーコードの表示

--------------------
void ShowErrCode( DWORD dwError )
{
	CString strbuff;
	::FormatMessage(
		FORMAT_MESSAGE_FROM_SYSTEM |
		FORMAT_MESSAGE_IGNORE_INSERTS,
		NULL,dwError,NULL,
		(char *)(const char *)strbuff,
		256,NULL );
	AfxMessageBox( strbuff );
}

void delBefore()
{
	// log.txtの削除
	CString logFileName = prevPath + "\\log.txt";

	// エラー情報の初期化
	::SetLastError( NO_ERROR );
	if(!::DeleteFile( (LPCTSTR)logFileName )
	{
		// 削除失敗
		ShowErrCode( ::GetLastError() );
	}else{
		// 削除成功
	}
}
--------------------
top

ファイル削除時の事前ファイル存在確認

--------------------
// log.txtの削除
CString logFileName = prevPath + "\\log.txt";

// エラー情報の初期化
::SetLastError( NO_ERROR );
if(!::DeleteFile( (LPCTSTR)logFileName ))
{
	DWORD err = ::GetLastError();
	// err == 2 の場合ファイルが存在しない
	if( err != 2 ){
		// 削除失敗
		ShowErrCode( ::GetLastError() );
		return;
	}
}
--------------------
top

フォルダ削除関数

--------------------
int CAutoSyntheDlg::delFolders( LPCTSTR pszPath )
{
WIN32_FIND_DATA fd;
HANDLE hFind;
BOOL bResult = TRUE;
CString crlf = _T("\x0d\x0a");

CString strPath = pszPath;
if( strPath.Right(1) != _T("\\") )
	strPath += _T("\\");
strPath += _T("*.*");

if( (hFind = ::FindFirstFile( strPath, &fd ))
		== INVALID_HANDLE_VALUE )
	return FALSE;

do{
	if( fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ){
		CString strComp = (LPCSTR) &fd.cFileName;
		if( (strComp != _T(".")) && (strComp != _T("..")) ){
			CString strNewPath = pszPath;
			if( strNewPath.Right(1) != _T("\\") )
				strNewPath += _T("\\");
			strNewPath += (LPCSTR) &fd.cFileName;
			if( !delFolders( strNewPath ) ){
				AfxMessageBox( "フォルダ削除エラー:"
					+ strNewPath +
					"が消せない!",MB_OK );
				break;
			}
			if( !::RemoveDirectory( strNewPath ) ){
				AfxMessageBox( "フォルダ削除エラー:"
					+ strNewPath +
					"が空ではない!",MB_OK );
				break;
			}
		}
	}else{
		CString filePath = pszPath;
		if( filePath.Right(1) != _T("\\") )
			filePath += _T("\\");
		filePath += (LPCSTR) &fd.cFileName;

		TRY
		{
			CFile::Remove( filePath );
		}
		CATCH( CFileException, e )
		{
			AfxMessageBox( "outputフォルダ中の\n"
				+filePath+ "\nファイル削除エラー",
				MB_OK );
			break;
		}
		END_CATCH
	}
}while( ::FindNextFile( hFind, &fd ) );

::FindClose( hFind );
return bResult;
}
--------------------
top

フォルダ削除方法

--------------------
// outputの削除
CString outputFolderName = prevPath + "\\output";

// エラー情報の初期化
::SetLastError( NO_ERROR );
if(!::RemoveDirectory( (LPCTSTR)outputFolderName ))
{
	DWORD err = ::GetLastError();
	// err == 2 の場合フォルダが存在しない
	// err = 145 の場合フォルダが空でない
	if( err != 2 && err != 145){
		// 削除失敗
		ShowErrCode( ::GetLastError() );
		return;
	}else if( err == 145 ){
		char pFolderName[MAX_PATH + 1];
		strcpy( pFolderName, outputFolderName );
		if( !delFolders( pFolderName ) ){
			return;
		}else if( !::RemoveDirectory( pFolderName ) ){
			AfxMessageBox( "outputフォルダ削除エラー",
			MB_OK );
			return;
		}
	}
}
--------------------
top

シェル実行終了タイミング取得

「考え方」:あるウィンドウからシェル実行するとそのウィンドウは
	フォーカスを失い、フォーカスは実行ウィンドウに移動する。
	シェル実行が終わるとフォーカスは元のウィンドウに戻る。

1)クラスに次のメンバを追加
	BOOL m_bShellExecute;

2)クラスのコンストラクタ(initでもいいのか?)の中で FALSE に初期化
	m_bShellExecute = FALSE;

3)::ShellExecuteを呼び出す直前で TRUE に設定
--------------------
m_bShellExecute = TRUE;
HINSTANCE ret = ShellExecute( m_hWnd, "open", "synthesizeall",
	(LPSTR)(LPCSTR)iniFile, NULL, SW_MINIMIZE );
if( (int)ret <= 32 )
	AfxMessageBox( "Synthesizeallで失敗しました", MB_OK );
--------------------

4)ClassWizardを使ってクラスに WM_SETFOCUS メッセージ用のハンドラ関数
	OnSetFocus( CWnd* pOldWnd ) を追加

5)OnSetFocus 関数の中で、フォーカスが戻ってきた後に実行する処理を記述する
--------------------
void CAutoSyntheDlg::OnSetFocus(CWnd* pOldWnd) 
{
	CDialog::OnSetFocus(pOldWnd);
	
	// TODO: この位置にメッセージ ハンドラ用のコードを追加してください
	// シェル実行中のステータスを確認する
	if( m_bShellExecute)
	{
		// シェル実行中であり、フォーカスがこのダイアログに
		// 戻ってきたら、シェルが終了したことがわかる。
		// (シェルの実行後にファイルが生成されたことがわかる)

		// シェル実行中のステータスを解除する
		m_bShellExecute = FALSE;
	}
}
--------------------
top

シェル実行終了タイミング取得 その2

--------------------
BOOL WaitProcessFinished( CString comLine )
{
	PROCESS_INFORMATION pI;
	STARTUPINFO sI;
	memset( &sI, 0, sizeof( STARTUPINFO ) );
	sI.cb = sizeof( STARTUPINFO );

	// プロセスを起動する
	BOOL ret = ::CreateProcess( NULL, (LPSTR)(LPCSTR)comLine,
		NULL, NULL, FALSE, 0, NULL, NULL, &sI, &pI );
	if( ret == FALSE ){
		AfxMessageBox( "実行失敗", MB_OK );
		return FALSE;
	}

	// プロセス終了を待つ
	DWORD result = WaitForSingleObject( pI.hProcess, INFINITE );
	switch( result ){
	case WAIT_OBJECT_0:
		return TRUE;
	case WAIT_TIMEOUT:
		AfxMessageBox( "プロセスが時間内に応答しない", MB_OK );
		break;
	case WAIT_ABANDONED:
	case WAIT_FAILED:
	default:
		AfxMessageBox( "その他のエラー", MB_OK );
	}
	return FALSE;
}
--------------------
top

改行について

CString crlf = "\x0d\x0a";
top

数値をCString型へ変更するには

--------------------
CString ans;
ans.Format( "%d", ::GetLastError() );
AfxMessageBox( ans, MB_OK );
--------------------
top

ダイアログにビットマップを貼るには

--------------------
画面更新をするとダイアログの場合、OnPaint()が呼ばれる
	// 画面の更新
	Invalidate();

以下、OnPaint()
void CBackImageDlg::OnPaint() 
{
	if (IsIconic())
	{
		CPaintDC dc(this); // 描画用のデバイス コンテキスト

		SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);

		// クライアントの矩形領域内の中央
		int cxIcon = GetSystemMetrics(SM_CXICON);
		int cyIcon = GetSystemMetrics(SM_CYICON);
		CRect rect;
		GetClientRect(&rect);
		int x = (rect.Width() - cxIcon + 1) / 2;
		int y = (rect.Height() - cyIcon + 1) / 2;

		// アイコンを描画します。
		dc.DrawIcon(x, y, m_hIcon);
	}
	else
	{
		CDialog::OnPaint();
	}

	// 以下から追加------------------------------------

	// フォームのクライアント領域を取得
	CRect ClientRect;

	// クライアント領域の座標取得
	GetClientRect(ClientRect);

	// 1.ビットマップファイルを読み込むためにCBitmapクラスオブジェクトを作成
	BITMAP BMP; 

	// 表示したいビットマップのハンドル
	// リソースに読み込まず、直接ファイルからビットマップファイルを読み込む場合は、LoadImage関数を使用する
	HBITMAP h_BMP = (HBITMAP)LoadImage(
		AfxGetApp()->m_hInstance, // ロードするイメージが入ったモジュールのインスタンスハンドル
		_T(m_FileName),           // ロードする画像ファイル名
		IMAGE_BITMAP,             // ロードする画像ファイルのタイプ(IMAGE_BITMAP:ビットマップ、IMAGE_CURSOR:カーソル、IMAGE_ICON:アイコン)
		0,                        // アイコンやカーソルの幅をピクセル単位で指定(0 で、かつ最後の引数がLR_DEFAULTSIZEなら実際のリソースの幅)
		0,                        // 高さ(上に同じ)
		LR_LOADFROMFILE);         // LR_DEFAULTCOLOR:デフォルト、LR_LOADFROMFILE:上で指定したファイルからイメージをロード

	// 2.メモリデバイスコンテキストの取得と作成
	// ダイアログベースはOnDraw()がなく、関数の引数でDCを渡せないため、OnPaint()関数では以下のようにしてDCを取得
	CDC* pDC = this->GetDC();
	CDC MemDC;

	MemDC.CreateCompatibleDC(pDC);

	// 3.ビットマップオブジェクトを選択
	// 作成したメモリデバイスコンテキストに先ほど読み込んだビットマップを関連付けると同時に現在のビットマップを保存する
	HBITMAP h_OLD_BMP = (HBITMAP)::SelectObject( MemDC.m_hDC, h_BMP );

	// ビットマップの情報を取得
	::GetObject(h_BMP, sizeof(BITMAP), &BMP);

	// ビットマップをメモリデバイスコンテキストからコピー
	pDC->StretchBlt(
		0,                     // 転送先のビットマップの左上隅の論理x座標
		0,                    // y(上と同じ)
		BMP.bmWidth,
		BMP.bmHeight,
		//ClientRect.right,      // 転送先のビットマップの幅
		//ClientRect.bottom-0,  // 高さ(上と同じ)
		&MemDC,                // コピーするデバイスコンテキストを識別するCDCオブジェクトのポインタ
		0,                     // 転送元のビットマップの左上隅の論理x座標
		0,                     // y(上と同じ)
		BMP.bmWidth,           // 転送元のビットマップの幅
		BMP.bmHeight,          // 高さ(上と同じ)
		SRCCOPY);              // ラスタオペレーションコード(SRCCOPY:転送元ビットマップを転送先ビットマップにコピー)

	// オブジェクトを元に戻す
	::SelectObject(MemDC.m_hDC, h_OLD_BMP );

	MemDC.DeleteDC();

	// 以上まで-----------------------------
}
--------------------
top

ダイアログの「OK」、「キャンセル」ボタンの処理

--------------------
1)ClassWizardで
 オブジェクトID:C...Dlg
 メッセージ  :PreTranslateMessage
でメンバ関数を作成

2)以下のように関数内に記述
BOOL CFileDumpDlg::PreTranslateMessage(MSG* pMsg) 
{
	// TODO: この位置に固有の処理を追加するか、または基本クラスを呼び出してください
	
	switch(pMsg->message){
	case WM_KEYDOWN:
		switch(pMsg->wParam){
		case VK_RETURN:
			MessageBeep(MB_OK);
			return TRUE;
			break;
		case VK_ESCAPE:
			return TRUE;
			break;
		}
	}

	return CDialog::PreTranslateMessage(pMsg);
}
--------------------
top

ファイルのバイナリでの読み込み、表示方法

--------------------
void CFileDumpDlg::fileRead(CString fn)
{
	FILE *pfile;
	CString hexWord;
	int n, count = 0;

	m_Disp = "";

	// バイナリ読み取りモードでファイルを開く
	if((pfile = fopen(fn, "rb")) == NULL)
		AfxMessageBox("入力ファイルを開けませんでした");

	// EOFを返すまで変換を繰り返す
	while((n = fgetc(pfile)) != EOF){
		// 上位4ビットを16進文字に変換出力する
		hexWord = HalfToHex((n & 0xf0) >> 4);
		m_Disp += hexWord;
		// 下位4ビットを16進文字に変換出力する
		hexWord = HalfToHex(n & 0x0f);
		m_Disp += hexWord;
		// 半角スペースを出力
		m_Disp += " ";

		// 見やすくするために
		if(!(++count % 24)){
			m_Disp += "\r\n";
		}else if(!(count % 8)){
			m_Disp += ' ';
		}
	}
	// ファイルを閉じる
	fclose(pfile);
}

unsigned char CFileDumpDlg::HalfToHex(int x)
{
	// 1/2バイトを16進文字に変換する

	static unsigned char str[] = {
		'0','1','2','3','4','5','6','7','8',
			'9','A','B','C','D','E','F' };

	// 下位4ビットのマスクをかける
	x &= 0x0f;
	return str[x];
}
--------------------
top

TCP/IPを利用したプログラム

--------------------
1)プロジェクトの作成時、AppWizardのステップ2で
 「Windowsソケット」のチェックボックスをチェック

2)ダイアログボックスのデザイン作成

IPアドレス入力するエディットコントロールは値はint型
サーバ名はCString型でもよい

3)ソケットクラスの作成

「挿入」-「クラスの新規作成」でクラスの新規作成ダイアログを出し、
クラス名に「CSock」基本クラスに「CAsyncSocket」でOKを押す

4)ダイアログウィンドウへのポインタを保持するメンバ変数の作成

ワークスペースウィンドウの「ClassView」から「CSock」を右クリックし、
メニューから「メンバ変数の追加」を選択
変数のタイプ「CDialog*」変数名「m_pWnd」アクセス制御「Private」で
メンバ変数を追加

5)ポインタを設定するためのメンバ関数の追加

ワークスペースウィンドウの「ClassView」から「CSock」を右クリックし、
メニューから「メンバ関数の追加」を選択
「void」型の「SetpWnd(CDialog* pWnd)」関数を追加、以下のように編集

void CSock::SetpWnd(CDialog *pWnd)
{
	m_pWnd = pWnd;
}

6)Sock.cppの最初の部分に以下のようにコードを追加

#include "stdafx.h"
#include "SocketClient.h"
#include "Sock.h"

#include "SocketClientDlg.h" // この部分を追加

7)ソケットクラスのインスタンスを生成する以下のメンバ変数を
ダイアログクラスに追加

変数の型  変数名      アクセス制御
------------------------------------------
CSock    m_ListenSock   Private
CSock    m_ConnectSock   Private

サーバの場合は2つとも必要。
クライアントの場合はm_ConnectSockだけでもいい

8)OnInitDialog関数に以下のようなアプリケーションの初期化を
行うコードを追加

m_ListenSock.SetpWnd(this);
m_ConnectSock.SetpWnd(this);

サーバの場合は2つとも必要。
クライアントの場合はm_ConnectSockだけでもいい

9)ネットワークへの接続

例えば、接続ボタンを押したとき、ソケットを作成するコードを追加

void CSocketClientDlg::OnStandby() 
{
	// TODO: この位置にコントロール通知ハンドラ用のコードを追加してください
	
	UpdateData(TRUE);

/*	// サーバの場合
	m_ListenSock.Create(m_PortNum); // ソケットの作成
	m_ListenSock.Listen(); // ソケットの接続
*/
	// クライアントの場合
	m_ConnectSock.Create(); // ソケットの作成(ポート番号はいらない)
	m_ConnectSock.Connect(m_IPaddress, m_PortNum); // ソケットの接続
}

10)ソケットクラスのイベント関数をダイアログクラスに追加

ワークスペースウィンドウの「ClassView」から「CxxxxDlg」を右クリックし、
メニューから「メンバ関数の追加」を選択

// サーバの場合
「void」型の「OnAccept」関数を追加、以下のように編集

void CSocketClientDlg::OnAccept()
{
	m_ListenSock.Accept(m_ConnectSock);
}

// クライアントの場合
「void」型の「OnConnect」関数を追加、以下のように編集

void CSocketClientDlg::OnConnect()
{
	// 何かの処理
}

11)ソケットクラスにイベント関数を追加

ワークスペースウィンドウの「ClassView」から「CSock」を右クリックし、
メニューから「メンバ関数の追加」を選択
サーバの場合、「void」型の関数「OnAccept(int Error)」
クライアントの場合、「void」型の関数「OnConnect(int Error)」
アクセス制御は「Protected」「Virtual」にチェックを入れて追加

// サーバの場合
void CSock::OnAccept(int Error)
{
	if(Error == 0)
	{
		((CSocketClientDlg*)m_pWnd)->OnAccept();
	}
}

// クライアントの場合
void CSock::OnConnect(int Error)
{
	if(Error == 0)
	{
		((CSocketClientDlg*)m_pWnd)->OnConnect();
	}
}

12)メッセージの送信

例えば、送信ボタンを押したとき、データ送信の処理を追加

void CSocketClientDlg::OnSend() 
{
	// TODO: この位置にコントロール通知ハンドラ用のコードを追加してください
	
	int Buff;
	UpdateData(TRUE);
	if(m_SendData != "")
	{
		Buff = m_SendData.GetLength();

		if(m_ConnectSock.Send((LPCTSTR)m_SendData, Buff)) != SOCKET_ERROR)
		{
			m_LogList.InsertString(0, "===送信済み===");
			m_SendData = "";
			UpdateData(FALSE);
		}
	}
}

13)メッセージの受信

メッセージの受信は、OnReceive関数を作成し受け持たせる
ワークスペースウィンドウの「ClassView」から「CxxxxDlg」を右クリックし、
メニューから「メンバ関数の追加」を選択
関数の型「void」関数の宣言「OnReceive」で作成し、以下のように編集

void CSocketClientDlg::OnReceive()
{
	char buff[1024];
	int buffsize = 1024;
	int Rbuffsize;
	CString TempMsg;

	UpdateData(TRUE);
	Rbuffsize = m_ConnectSock.Receive(buff, buffsize);
	if(Rbuffsize != SOCKET_ERROR)
	{
		buff[Rbuffsize] = NULL;
		TempMsg = buff;
		m_LogList.InsertString(0, TempMsg);
		UpdateData(FALSE);
	}
}

そして、ソケットクラスにイベント関数を追加
ワークスペースウィンドウの「ClassView」から「CSock」を右クリックし、
メニューから「メンバ関数の追加」を選択
「void」型の関数「OnReceive(int Error)」
アクセス制御「Protected」「Virtual」にチェックを入れ、関数作成
以下のように編集

void CSock::OnReceive(int Error)
{
	if(Error == 0)
	{
		((CSocketClientDlg*)m_pWnd)->OnReceive();
	}
}

14)ネットワークの切断

例として、切断ボタンを押したときに切断するようにする
OnCloseConnect関数を以下のように編集

void CSocketClientDlg::OnCloseConnect() 
{
	m_ConnectSock.Close();
}

ワークスペースウィンドウの「ClassView」から「CSock」を右クリックし、
メニューから「メンバ関数の追加」を選択
「void」型の関数「OnCloseConnect(int Error)」を追加
アクセス制御は「Protected」「Virtual」以下のように編集

void CSock::OnCloseConnect(int Error)
{
	if(Error == 0)
	{
		((CSocketClientDlg*)m_pWnd)->OnCloseConnect();
	}
}

以上。
--------------------
top