« 2006年11月 | メイン | 2007年01月 »

前の10件 1  2  3  4  5  6  7  8  9  10

2006年12月 記事一覧

CStringArrayをソートする

MSDNライブラリを見ていたら「qsort_s」という関数を発見。

クイックソート関数qsortは昔からあったが、知らないうちにqsort_sができていた。これら2つは何が違うかと言うと...比較用関数に独自パラメータ(context)を渡せるかどうか。qsortはパラメータを渡せないため、単純なソートには使えるものの実際問題として使い物にならない関数だった。

そのため今まではクイックソートが必要な時はqsortのソースコードを元にパラメータが利用できる関数を作成しなければいけなかった。qsort_sのおかげでその手間が省けるようになった。ここではこのqsort_sを利用してCStringArrayの文字列をソートする。

#include "atlstr.h"
#include "atlcoll.h"

class	CDnpSortString
{
	//比較関数
	static	int	SortStringArrayCompare(void* pContext,const void* pData1,const void* pData2)
	{
		if(pContext == NULL)
			return	0;
		return	((CDnpSortString*)pContext)->SortStringArrayCompare_(pData1,pData2);
	}

	//昇順か降順か
	bool		_bABC;

	//ソート対象
	CAtlArray<CAtlString>*	_pastrText;

public:

	CDnpSortString()
	{
		_bABC = true;
		_pastrText = NULL;
	}

	//比較関数の実体
	//外部からは呼ばないこと!
	int	SortStringArrayCompare_(const void* pData1,const void* pData2)
	{
		size_t	nSize;

		if(_pastrText == NULL)
			return	0;

		nSize = _pastrText->GetCount();
		if(*(size_t*)pData1 >= nSize || *(size_t*)pData2 >= nSize)
			return	0;

		if(_bABC)
			return	::_tcscmp((*_pastrText)[*(size_t*)pData1],(*_pastrText)[*(size_t*)pData2]);
		else
			return	::_tcscmp((*_pastrText)[*(size_t*)pData2],(*_pastrText)[*(size_t*)pData1]);
	}


	//
	//	CStringArrayのソート
	//
	bool	SortStringArray(CAtlArray<CAtlString>& astrText,bool bABC)
	{
		size_t	i;
		size_t	nSize;
		size_t*	pnIndex;
		CAtlArray<CAtlString>	astrTmp;

		nSize = astrText.GetCount();
		if(nSize == 0)
			return	false;

		pnIndex = new size_t[nSize];
		if(pnIndex == NULL)
			return	false;

		astrTmp.SetCount(nSize);
		for(i = 0; i < nSize; i++)
		{
			pnIndex[i] = i;
			astrTmp[i] = astrText[i];
		}

		_bABC = bABC;
		_pastrText = &astrText;
		::qsort_s(pnIndex,nSize,sizeof(size_t),SortStringArrayCompare,this);
		_pastrText = NULL;

		for(i = 0; i < nSize; i++)
			astrText[i] = astrTmp[pnIndex[i]];

		delete	pnIndex;

		return	true;
	}

};



void	Test(void)
{
	///////////////////
	//ダミーデータ用意
	//
	CAtlArray<CAtlString>	astrFiles;

	astrFiles.Add(_T("abc"));
	astrFiles.Add(_T("aac"));
	astrFiles.Add(_T("aec"));
	astrFiles.Add(_T("bbc"));


	///////////////////
	//ソート
	//
	CDnpSortString	cSort;
	cSort.SortStringArray(astrFiles,false);


	///////////////////
	//結果表示
	//
	size_t		i;
	size_t		nSize;
	CAtlString	strMessage;

	nSize = astrFiles.GetCount();
	for(i = 0; i < nSize; i++)
	{
		strMessage += astrFiles[i];
		strMessage += _T("\n");
	}

	::MessageBox(NULL,strMessage,_T(""),MB_OK);
}

プロジェクトファイルをダウンロード

第14回 カーソルキーで次の画像を表示する

ImageViewer14_01.gif 今回は画像を表示している状態で[↑][↓][←][→]キーを押したら、次の画像を表示できるようにする。 まずは現在読み込んでいる画像ファイルのパスを保存するように変更する。「ImageViewerView.h」を開いてメンバ変数を1つ増やす。
	CAtlString	_strImageFile;
			_strImageFile = pszFile;
		}
		else
			_strImageFile = _T("");

ImageViewer14_02.gif 「次の画像」をとは「現在読み込んでいる画像があるフォルダー内のファイルを名前順に並べたときの次の画像」ということになる。 実現するにはフォルダ内のファイルを列挙する処理、列挙したファイルを名前順にソートする処理が必要になるのでそれらを追加する。
#include "atlcoll.h"

//
//	フォルダ内のファイル検索
//
//見つかったファイルをpastrFilesに追加する
//nRecursiveは子フォルダの探索数(デフォルトは探索しない)
//
bool	GetFilesInFolder(LPCTSTR pszFolder,CAtlArray<CAtlString>* pastrFiles,int nRecursive=0)
{
	BOOL			bFind;
	HANDLE			hFind;
	WIN32_FIND_DATA	FindFileData;
	CAtlString		strFolder;

	if(pszFolder == NULL || _tcslen(pszFolder) == 0 || pastrFiles == NULL)
		return	false;

	//astrFiles.RemoveAll();

	strFolder = pszFolder;
	if(strFolder.Right(1) != _T('\\') && strFolder.Right(1) != _T('/'))
		strFolder += _T("\\");

	hFind = ::FindFirstFile(strFolder + _T("*.*"),&FindFileData);
	if(hFind == INVALID_HANDLE_VALUE)
		return	false;

	bFind = TRUE;
	while(bFind)
	{
		if(FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
		{
			//フォルダなら...
			if(nRecursive > 0 && _tcsncmp(FindFileData.cFileName,_T("."),1) != 0 && _tcsncmp(FindFileData.cFileName,_T(".."),2) != 0 )
			{
				//再起で子フォルダ内を調べる
				GetFilesInFolder(strFolder + FindFileData.cFileName,pastrFiles,nRecursive - 1);
			}
		}
		else
		{
			//ファイルなら...
			pastrFiles->Add(strFolder + FindFileData.cFileName);
		}

		bFind = ::FindNextFile(hFind,&FindFileData);
	}
	::FindClose(hFind);

	return	true;
}



class	CDnpSortString
{
	//比較関数
	static	int	SortStringArrayCompare(void* pContext,const void* pData1,const void* pData2)
	{
		if(pContext == NULL)
			return	0;
		return	((CDnpSortString*)pContext)->SortStringArrayCompare_(pData1,pData2);
	}

	//昇順か降順か
	bool		_bABC;

	//ソート対象
	CAtlArray<CAtlString>*	_pastrText;

public:

	CDnpSortString()
	{
		_bABC = true;
		_pastrText = NULL;
	}

	//比較関数の実体
	//外部からは呼ばないこと!
	int	SortStringArrayCompare_(const void* pData1,const void* pData2)
	{
		size_t	nSize;

		if(_pastrText == NULL)
			return	0;

		nSize = _pastrText->GetCount();
		if(*(size_t*)pData1 >= nSize || *(size_t*)pData2 >= nSize)
			return	0;

		if(_bABC)
			return	::_tcscmp((*_pastrText)[*(size_t*)pData1],(*_pastrText)[*(size_t*)pData2]);
		else
			return	::_tcscmp((*_pastrText)[*(size_t*)pData2],(*_pastrText)[*(size_t*)pData1]);
	}


	//
	//	CStringArrayのソート
	//
	bool	SortStringArray(CAtlArray<CAtlString>& astrText,bool bABC)
	{
		size_t	i;
		size_t	nSize;
		size_t*	pnIndex;
		CAtlArray<CAtlString>	astrTmp;

		nSize = astrText.GetCount();
		if(nSize == 0)
			return	false;

		pnIndex = new size_t[nSize];
		if(pnIndex == NULL)
			return	false;

		astrTmp.SetCount(nSize);
		for(i = 0; i < nSize; i++)
		{
			pnIndex[i] = i;
			astrTmp[i] = astrText[i];
		}

		_bABC = bABC;
		_pastrText = &astrText;
		::qsort_s(pnIndex,nSize,sizeof(size_t),SortStringArrayCompare,this);
		_pastrText = NULL;

		for(i = 0; i < nSize; i++)
			astrText[i] = astrTmp[pnIndex[i]];

		delete	pnIndex;

		return	true;
	}

};

ImageViewer14_03.gif さらにキーが押されたときの処理を追加する。キーが押されるとWM_KEYDOWNが送られるのでそれを利用する。 これで実行し、画像ファイルを読み込んだ後にカーソルキーで次のファイルを表示できるようになった。
		MESSAGE_HANDLER(WM_KEYDOWN, OnKeyDown)
	LRESULT OnKeyDown(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled)
	{
		bHandled = FALSE;
		if(wParam != VK_UP && wParam != VK_DOWN && wParam != VK_LEFT && wParam != VK_RIGHT)
			return	0;
		if(_pImage == NULL)
			return	0;

		CAtlString				strFolder;
		CDnpSortString			cSort;
		CAtlArray<CAtlString>	astrFiles;

		//現在読み込んでいる画像の存在するフォルダ名を取得
		strFolder = _strImageFile;
		{
			int		nFind;

			strFolder.Replace(_T('/'),_T('\\'));
			nFind = strFolder.ReverseFind(_T('\\'));
			if(nFind < 0)
				return	0;
			strFolder = strFolder.Left(nFind);
		}

		//画像のあるフォルダに存在するファイルを全て取得
		GetFilesInFolder(strFolder,&astrFiles);

		//取得したファイル一覧を名前順にソート
		//[←][↑]の場合は降順、[→][↓]の場合は昇順
		cSort.SortStringArray(astrFiles,(wParam == VK_UP || wParam == VK_LEFT) ? false : true);


		//取得したファイル一覧の中から現在表示している画像のインデックスを取得
		size_t	nIndex;
		size_t	nSize;
		nSize = astrFiles.GetCount();
		{
			CAtlString	strFile;

			strFile = _strImageFile;
			strFile.MakeLower();

			for(nIndex = 0; nIndex < nSize; nIndex++)
			{
				astrFiles[nIndex].MakeLower();
				if(strFile == astrFiles[nIndex])
					break;
			}
		}

		//次のファイルを表示
		{
			bool	ret;
			size_t	nTryCount;

			nTryCount = 0;
			while(1)
			{
				nIndex++;
				if(nIndex >= nSize)
					nIndex = 0;

				ret = LoadImage(astrFiles[nIndex]);
				if(ret)
					break;

				nTryCount++;
				if(nTryCount >= nSize)
					break;
			}
		}

		return	0;
	}

プロジェクトファイルをダウンロード

第15回 「送る」メニューから画像を開く

ImageViewer15_01.gif 起動時にコマンドラインで指定されたファイルを開くようにする。これによりEXEファイルやEXEファイルへのショートカットへ画像ファイルをドラッグ・アンド・ドロップしても画像が開くようになる。
コマンドラインの取得および解析にはCommandLineToArgvWとGetCommandLineWを利用した。GetCommandLineはユニコードおよび非ユニコードバージョンがAPIとして用意されているが、CommandLineToArgvWは非ユニコード版がない(対応OSもWindows 2000以降)。そのため取得したファイル名はCStringに一度変換してから処理している。
GDI+へ渡すファイル名はもともとユニコードなので本来変換の必要はないのだが...
		LPWSTR*	ppszArglist;
		int		nArgs;

		//コマンドライン引数で指定された画像を読み込む
		ppszArglist = ::CommandLineToArgvW(::GetCommandLineW(),&nArgs);
		if(ppszArglist && nArgs > 1)
			LoadImage((CAtlString)ppszArglist[1]);
		::LocalFree(ppszArglist);

ImageViewer15_02.gif
コマンドラインから引数の対応により、「送る」メニューからの画像表示にも対応する。

当然のことながら利用するためにはあらかじめSendToフォルダへショートカットを作っておく必要がある。Windows Vistaの場合のフォルダ位置は

C:\ Users\ ○○\ AppData\ Roaming\ Microsoft\ Windows\ SendTo\

になる。


プロジェクトファイルをダウンロード

2006年12月31日

MFCでテキストファイルを読み込む

test88_01.gif
ここではMFCを利用して簡単にテキストファイルを読み込む。
「ファイル」メニューの「新規作成」から「プロジェクト」を選択し、現れたウインドウで「MFCアプリケーション」を使う。


test88_02.gif
MFCアプリケーションウイザードが開いたら「アプリケーションの種類」から「ダイアログベース」を選択する。


test88_03.gif
これでプロジェクトが生成された。
次にダイアログにボタンを配置するため「表示」メニューから「リソースビュー」を選択する。


test88_04.gif
左側にリソースビューが開いたら「Dialog」にあるメインウインドウ用のフォームを開く。


test88_05.gif
そしてボタンを配置するためツールボックスの「Button」をフォームへドラッグアンドドロップする。


test88_06.gif
これでボタンが配置された。次にボタンを押したときの動作を実装するため、ボタンをダブルクリックする。


test88_07.gif
すると自動的にメッセージハンドラなどが作成されると同時にソースコードの該当部分が開く。

test88_08.gif ここにボタンを押したときのソースコードを入力する。
ここではCStdioFileを利用してファイルを開き、1行ずつCStringへ読み込み、最後にAfxMessageBoxにてメッセージボックスへ表示している。
	BOOL		ret;
	CString		strLine;
	CString		strMessage;
	CStdioFile	cFile;

	ret = cFile.Open(_T("stdafx.h"),CFile::modeRead | CFile::shareDenyNone);
	if(ret == FALSE)
		return;

	while(ret)
	{
		ret = cFile.ReadString(strLine);
		strMessage += strLine;
		strMessage += _T("\n");
	}

	cFile.Close();

	AfxMessageBox(strMessage);

test88_09.gif
そして実行する。


test88_10.gif
ボタンを押すとファイルが読み込まれ、ファイル内容がメッセージボックスとして表示される。しかしここでは日本語部分が完全に文字化けしてしまった。

これはMFCアプリケーションウイザードではデフォルトでユニコードビルドとなっているためと、MFCのバグ(設計ミス)からCStdioFile::ReadStringAのようなメンバー関数が用意されていないためだ。


test88_11.gif
この場合、簡単に文字化けを直すには非ユニコードビルドにする。設定するにはまず「プロジェクト」メニューから「プロパティ」を選択する。

test88_12.gif
そして開いたウインドウの文字セットを「マルチバイト文字セットを使用する」に変更する。

test88_13.gif
これで実行すると文字化けせずにファイル内容をメッセージボックスに表示できた。

プロジェクトファイルをダウンロード

大文字と小文字を区別せずに文字列を比較する

CompareStringを利用すると大文字と小文字を区別しない文字列比較や全角文字と半角文字を区別しない文字列比較などができる。

このAPIは戻り値がstrcmpなどの通常の比較関数とは異なることに注意。戻り値から2を引いた値がstrcmpの戻り値と同じように扱えるようになる。つまりCompareStringによって文字列が等しいと判断された時の戻り値は2となる。

void	Test(void)
{
	int		nRet;

	//"あいうABCDEF"と"あいうabcdef"を比較(結果は「同じ」と判断)
	nRet = ::CompareString(LOCALE_USER_DEFAULT,NORM_IGNORECASE,_T("あいうABCDEF"),-1,_T("あいうabcdef"),-1);
	if(nRet - 2 == 0)
		::MessageBox(NULL,_T("同じです"),_T(""),MB_OK);
	else
		::MessageBox(NULL,_T("違います"),_T(""),MB_OK);


	//"ABC"と"abc"を比較(結果は「同じ」と判断)
	nRet = ::CompareString(LOCALE_USER_DEFAULT,NORM_IGNORECASE,_T("ABC"),-1,_T("abc"),-1);
	if(nRet - 2 == 0)
		::MessageBox(NULL,_T("同じです"),_T(""),MB_OK);
	else
		::MessageBox(NULL,_T("違います"),_T(""),MB_OK);


	//"abc"と"abc"を比較(結果は「違う」と判断)
	nRet = ::CompareString(LOCALE_USER_DEFAULT,NORM_IGNORECASE,_T("abc"),-1,_T("abc"),-1);
	if(nRet - 2 == 0)
		::MessageBox(NULL,_T("同じです"),_T(""),MB_OK);
	else
		::MessageBox(NULL,_T("違います"),_T(""),MB_OK);


	//"abc"と"abc"を比較(結果は「同じ」と判断)
	nRet = ::CompareString(LOCALE_USER_DEFAULT,NORM_IGNORECASE | NORM_IGNOREWIDTH,_T("abc"),-1,_T("abc"),-1);
	if(nRet - 2 == 0)
		::MessageBox(NULL,_T("同じです"),_T(""),MB_OK);
	else
		::MessageBox(NULL,_T("違います"),_T(""),MB_OK);
}

プロジェクトファイルをダウンロード

インターネット上からファイルをダウンロードする

インターネット上からファイルをダウンロードする方法はいくつかある。ここではwininetのInternetOpen、InternetOpenUrl、InternetReadFileを利用した。

#include "wininet.h" 
#pragma comment(lib,"wininet.lib")


//
//	ファイルのダウンロード
//
//	FTP/HTTP両方に対応する。
//	FTPの場合は「ret = DownloadFile(_T("ftp://userid:password@usefullcode.net/home/user/htdocs/index.html"),_T("index.html"));」
//	のようにしてユーザーアカウントとパスワードを設定する
//
bool	DownloadFile(LPCTSTR pszURL,LPCTSTR pszLocalFile,DWORD dwBuffSize=1024)
{
	TCHAR		pszHeader[] = _T("Accept: */*\r\n\r\n");
	BOOL		ret;
	DWORD		dwReadSize;
	DWORD		dwWrittenSize;
	BYTE*		pcbBuff;
	HINTERNET	hInternet;
	HINTERNET	hConnect;
	HANDLE		hFile;

	hInternet = ::InternetOpen(NULL,INTERNET_OPEN_TYPE_PRECONFIG,NULL,NULL,0);
	if(hInternet == NULL)
		return	false;

	hConnect = ::InternetOpenUrl(hInternet,pszURL,pszHeader,-1,INTERNET_FLAG_DONT_CACHE,0);
	pcbBuff = new BYTE[dwBuffSize];
	if(hConnect == NULL || pcbBuff == NULL)
	{
		if(hConnect)
			::InternetCloseHandle(hConnect);
		::InternetCloseHandle(hInternet);
		if(pcbBuff)
			delete	pcbBuff;
		return	false;
	}

	//保存先のファイルを作る。ファイルがあった場合は失敗
	hFile = ::CreateFile(pszLocalFile,GENERIC_WRITE,0,NULL,CREATE_NEW,FILE_ATTRIBUTE_NORMAL,NULL);
	ret = (hFile == INVALID_HANDLE_VALUE) ? FALSE : TRUE;

	while(ret)
	{
		::Sleep(0);
		ret = ::InternetReadFile(hConnect,pcbBuff,dwBuffSize,&dwReadSize);
		if(ret == FALSE || dwReadSize == 0)
			break;

		ret = ::WriteFile(hFile,pcbBuff,dwReadSize,&dwWrittenSize,NULL);
	}

	if(hFile != INVALID_HANDLE_VALUE)
	{
		::CloseHandle(hFile);

		//失敗時は作成したファイルを削除
		if(ret == FALSE)
			::DeleteFile(pszLocalFile);
	}

	::InternetCloseHandle(hConnect);
	::InternetCloseHandle(hInternet);

	delete	pcbBuff;

	return	ret ? true : false;
}




void	Test(void)
{
	bool	ret;

	ret = DownloadFile(_T("http://www.google.co.jp/"),_T("google.html"));
	if(ret)
		::MessageBox(NULL,_T("ダウンロードしました"),_T(""),MB_OK);
	else
		::MessageBox(NULL,_T("失敗しました"),_T(""),MB_OK);
}

プロジェクトファイルをダウンロード

タブを指定してドライブなどのプロパティを表示する

test91.gif
SHObjectPropertiesを利用するとドライブなどのプロパティを表示する際にデフォルトで開くタブを設定できる。

例えば「ツール」タブを開きたい場合は L"ツール" のように指定する。このときに指定する文字列はタブ名になる。そのため日本語OSでは L"ツール" でいいが、その他の言語ではほかの文字列になる。きちんと利用したい場合はshell32.dllかどこかに格納されているリソース文字列を参照する必要があるだろう。

#include "shlobj.h"


void	Test(void)
{
	::SHObjectProperties(NULL,SHOP_FILEPATH,L"c:\\",L"ツール");

	::MessageBox(NULL,_T("ダミー"),_T(""),MB_OK);
}

プロジェクトファイルをダウンロード

「今後、このダイアログボックスを表示しない」があるメッセージボックスを使う

test92.gif
Windows2000以降には「今後、このダイアログボックスを表示しない」を備えたメッセージボックスとしてSHMessageBoxCheckが用意されている。

このAPIはチェックを入れてボタンを押したとき、レジストリに押されたボタンの情報を保存している。

依存環境:ATL
#include "shlwapi.h"
#pragma	comment(lib,"shlwapi.lib")


void	Test(void)
{
	int		ret;

	//初回はメッセージボックスが表示される
	ret = ::SHMessageBoxCheck(NULL,_T("今後表示しないにチェックを入れてボタンを押してください"),_T("test"),MB_YESNO | MB_ICONINFORMATION,IDNO,_T("{D438034E-E6C2-4dae-BF16-12CEE68E201E}"));

	//メッセージボックスが表示されない
	ret = ::SHMessageBoxCheck(NULL,_T("チェックを入れたらこのダイアログは表示されません"),_T("test"),MB_YESNO | MB_ICONINFORMATION,IDNO,_T("{D438034E-E6C2-4dae-BF16-12CEE68E201E}"));

	//レジストリを削除
	{
		LONG	nRet;
		CRegKey	cRegistry;

		nRet = cRegistry.Open(HKEY_CURRENT_USER,_T("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\DontShowMeThisDialogAgain"));
		if(nRet == ERROR_SUCCESS)
		{
			cRegistry.DeleteValue(_T("{D438034E-E6C2-4dae-BF16-12CEE68E201E}"));
			cRegistry.Close();
		}
	}

	//メッセージボックスが表示される
	ret = ::SHMessageBoxCheck(NULL,_T("レジストリを削除したので表示されます"),_T("test"),MB_YESNO | MB_ICONINFORMATION,IDNO,_T("{D438034E-E6C2-4dae-BF16-12CEE68E201E}"));
}

プロジェクトファイルをダウンロード

前の10件 1  2  3  4  5  6  7  8  9  10





usefullcode@gmail.com

About 2006年12月

2006年12月にブログ「UsefullCode.net」に投稿されたすべてのエントリーです。

前の記事は2006年11月です。

次の記事は2007年01月です。

他にも多くのエントリーがあります。メインページ記事一覧も見てください。