ファイルをバイナリ形式で表示する (C/C++編)
ディレクトリツリーでノード(ディレクトリ)を選択したときに、ファイル一覧ビューで選択したディレクトリ直下のサブディレクトリとファイル名を一覧表示します。
ディレクトリツリーからファイル一覧ビューへのディレクトリ変更通知は、ドキュメントを介して行うので、処理の流れは以下のようになります。
ディレクトリ選択メッセージのイベントハンドラ
ドキュメントのファイル一覧を更新
ファイル一覧ビューの表示を更新
ディレクトリツリーのノードが選択されたときに呼び出されるイベント処理です。
ディレクトリツリーのノード(ディレクトリ)を選択したときのイベント処理を追加します。
「クラスウィザード」を開いて、「CFileTreeView」クラスに「メッセージ」タブから「TVN_SELCHANGED」を選択してイベントハンドラ「OnTvnSelchanged()」を追加します。
選択されたノードに対応したディレクトリの絶対パスを取得して、ドキュメントに対してファイル一覧を表示するディレクトリが変更されることを通知します。
ソースコード [FileTreeView.cpp]
/****************************************************************************/
/*!
* @brief ディレクトリツリーのディレクトリの選択が変更されるときのイベント処理.
*
* @param [in] pNMHDR 通知メッセージ(NMHDR)構造体のポインタ.
* @param [out] pResult 戻り値を格納する領域のポインタ.
*
* @retval なし.
*/
void CFileTreeView::OnTvnSelchanged( NMHDR *pNMHDR, LRESULT *pResult )
{
LPNMTREEVIEW pNMTreeView = reinterpret_cast(pNMHDR);
CMFCApp001Doc* pDoc = dynamic_cast( GetDocument() );
if( pDoc != NULL ){
//! 選択されたノードに該当するディレクトリ名を取り出す.
CTreeCtrl& treeCtrl = GetTreeCtrl();
CString path = GetAbsolutePath( treeCtrl, pNMTreeView->itemNew.hItem );
//! ドキュメントに選択されたディレクトリを通知する.
pDoc->UpdateFileList( path );
}
*pResult = 0;
}
ファイル一覧に表示するディレクトリの絶対パスが指定された場合は、該当するディレクトリのファイルリストを作ります。
ファイル一覧に表示するディレクトリの絶対パスが指定されなかった場合は、ドライブ名一覧を作って、これをファイルリストにします。
ファイルリストが変更されたことをファイル一覧ビューに通知します。
ソースコード [MFCApp001Doc.cpp]
/****************************************************************************/
/*!
* @brief ファイル一覧を更新する.
*
* @param [in] path 一覧表示するパス名.
*
* @retval なし.
*/
void CMFCApp001Doc::UpdateFileList( CString& path )
{
//! 一覧表示するパス名を保存する.
m_CurrentDir = path;
if( path.GetLength() > 0 ){
//! 指定されたパスのファイル一覧を生成する.
CreateFileList( path );
}else{
//! パスが指定されていない場合はドライブ一覧を生成する.
CreateDriveList();
}
//! ファイル一覧を更新する.
CFileListView* listView = GetFileListView();
if( listView != NULL ){
listView->UpdateFileList();
}
}
ファイル一覧ビューに表示するディレクトリ名とファイル名をに追加します。
ソースコード [MFCApp001Doc.cpp]
/****************************************************************************/
/*!
* @brief 指定されたディレクトリのファイル一覧を取得する.
*
* @param [in] path ファイル一覧を取得するディレクトリの絶対パスのポインタ.
*
* @retval なし.
*/
void CMFCApp001Doc::CreateFileList( CString& path )
{
//! ファイル名リストをクリアする.
ClearFileList();
CString buf = path;
buf += _T("\\*.*");
WIN32_FIND_DATA ffd;
memset( &ffd, 0, sizeof(WIN32_FIND_DATA) );
try {
/*!
* カレント・ディレクトリの下にあるディレクトリ名とファイル名を抽出する.
* 但し隠しディレクトリとカレント・ディレクトリ( "." ) はリストに含めない.
*/
HANDLE sch = ::FindFirstFile( (LPCTSTR)buf, &ffd );
if( sch != INVALID_HANDLE_VALUE ){
do{
if( !( ffd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN )
&& ( _tcscmp( ffd.cFileName, _T(".") ) != 0 )
&& ( _tcscmp( ffd.cFileName, _T("..") ) != 0 )){
//! ファイル情報を生成する.
CFileInfo* info = new CFileInfo();
info->SetFileName( CString( ffd.cFileName ) );
//! ファイル日付にはファイルの最終更新日時をセットする.
info->SetFileDate( ConvertFileDate( ffd.ftLastWriteTime ) );
if( ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ){
info->SetType( CFileInfo::TYPE_DIR );
//! ディレクトリの場合、ファイルサイズに「<DIR>」をセットする.
info->SetFileSize( CString(_T("<DIR>")) );
}else{
info->SetType( CFileInfo::TYPE_FILE );
//! ファイルの場合、ファイルサイズをセットする.
LONGLONG size = (LONGLONG)((DWORDLONG)ffd.nFileSizeHigh << 32) + ((DWORDLONG)ffd.nFileSizeLow);
info->SetFileSize( DigitSeparator( size ) );
}
/*!
* ファイルリストに生成したファイル情報を追加する.
* (ファイルリストにはディレクトリ→ファイルの順に、かつ、名前順に格納する)
*/
int i;
for( i = 0; i < m_FileList.GetCount(); i++ ){
//! ファイルリストの先頭からファイル情報を1個づつ取り出す.
CFileInfo* item = m_FileList.GetAt( i );
if( item->GetType() == info->GetType() ){
//! ファイル種別が同じ場合、名前順に格納する.
if( info->GetFileName().CompareNoCase( item->GetFileName() ) < 0 ){
m_FileList.InsertAt( i, info );
break;
}
}else{
//! ファイル種別が変わった場合、ディレクトリを先に格納する.
if( info->GetType() == CFileInfo::TYPE_DIR ){
m_FileList.InsertAt( i, info );
break;
}
}
}
if( i >= m_FileList.GetCount() ){
m_FileList.Add( info );
}
}
memset( &ffd, 0, sizeof(WIN32_FIND_DATA) );
//! 次のファイル情報を取得する.
}while( ::FindNextFile( sch, &ffd ) ) ;
//! 一つ上のディレクトリに移動する為の「..」をリストの先頭に追加する.
CFileInfo* info = new CFileInfo();
info->SetType( CFileInfo::TYPE_DIR );
info->SetFileName( CString( _T("..") ) );
info->SetFileSize( CString(_T("<DIR>")) );
m_FileList.InsertAt( 0, info );
}
}catch( ... ){
TRACE0( "ファイルリストの生成に失敗しました。\n" );
}
}
最初に現在保持しているファイルリストをクリアします。
FindFirstFile()とFindNextFile()を呼び出して、指定されたディレクトリ直下のファイルとディレクトリのリストを取得します。
do~while()ループでFindNextFile()でファイル情報が取れなくなるまで処理します。
取得したファイル情報から次の条件のいずれかが一致するファイルはファイル一覧から除外します。
- 隠しファイル。
- カレントディレクトリ「.」。
- 親ディレクトリ「..」。
表示リストに追加する「ディレクトリ」のファイル情報は以下のとおりです。
項目 | 値 |
---|---|
ファイル名 | ファイル情報から取得したディレクトリ名 |
ファイルサイズ | 「<DIR>」 |
更新日時 | ファイル情報から取得した最終更新日時 |
ファイル種別 | TYPE_DIR |
表示リストに追加する「ファイル」のファイル情報は以下のとおりです。
項目 | 値 |
---|---|
ファイル名 | ファイル情報から取得したファイル名 |
ファイルサイズ | ファイル情報から取得したファイルサイズ |
更新日時 | ファイル情報から取得した最終更新日時 |
ファイル種別 | TYPE_FILE |
表示リストにファイル情報を追加するときに以下の手順でディレクトリ→ファイルの順に、かつ、名前順に格納するようにします。
(FindNextFile()で取得できる順番が、「ディレクトリ」→「ファイル」の順になっていない為にファイル一覧のリストに格納するときに並べ替えます。)
リストの先頭から追加するファイル情報とファイル種別を比較します。
ファイル種別が同じ場合、ファイル名を比較し、追加するファイル情報のファイル名が小さい時は、比較したリストのファイル情報の前に新しいファイル情報を追加します。
→ファイル種別が同じなので、単純に名前順のソートになります。
ファイル種別が違う場合、追加するファイル情報のファイル種別が「ディレクトリ」の時は、比較したリストのファイル情報の前に新しいファイル情報を追加します。
→ファイル種別が違うときにリストにアイテムを追加するケースは、『追加するファイル情報が「ディレクトリ」でディレクトリ名がリスト中で一番大きい』ときになるので、追加するファイルの種別が「ディレクトリ」なら、「ファイル」の前に追加挿入となります。
リストの最後まで比較してファイル情報が追加できなかった時は、リストの最後に新しいファイル情報を追加します。
一つ上のディレクトリに移動する為の「..」をリストの先頭に追加します。
項目 | 値 |
---|---|
ファイル名 | 「..」 |
ファイルサイズ | 「<DIR>」 |
更新日時 | 空白 |
ファイル種別 | TYPE_DIR |
ファイル一覧ビューに表示するファイルリストをドライブ名のリストで作成します。
ソースコード [MFCApp001Doc.cpp]
/****************************************************************************/
/*!
* @brief ドライブのリストを取得する.
*
* @param なし.
*
* @retval なし.
*/
void CMFCApp001Doc::CreateDriveList( void )
{
//! ファイル名リストをクリアする.
ClearFileList();
//! 有効なドライブの文字列を取得する.
TCHAR buf[128] = {'\0'};
DWORD size = sizeof( buf ) / sizeof( buf[0] );
::GetLogicalDriveStrings( size, buf );
/*!
* 取得したドライブ文字列のリストは以下のようになっている.
*
* "C:\" + '\0' + "D:\" + '\0' + '\0'
*/
for( LPTSTR ptr = buf ; *ptr != _T('\0') ; ){
//! ドライブ文字列からバックスラッシュ('\')を取り除く.
CString path = ptr;
path.Remove( _T('\\') );
//! ファイル情報を生成する.
CFileInfo* info = new CFileInfo();
info->SetFileName( path );
//! ファイルリストに生成したファイル情報を追加する.
m_FileList.Add( info );
//! (文字数 + NULL文字)分を読み飛ばして次の文字列の先頭へ移動する.
ptr += (_tcslen( ptr ) + 1);
}
}
最初に現在保持しているファイルリストをクリアします。
次にGetLogicalDriveStrings()を呼び出してコンピュータに接続されている有効なドライブのドライブ名リストを取得します。
ドライブ名リストにはNULL文字終端されたドライブ名の文字列が順次格納されていて、リストの最後にNULL文字のみの文字列があります。
for()ループで回しながらドライブ名リストからドライブ名の文字列を取り出します。
取り出したドライブ名の最後にセパレータ文字「\」が付いているので、これを取り除いてファイルリストに追加します。
ファイルリストに追加する情報は以下のとおりです。
項目 | 値 |
---|---|
ファイル名 | ドライブ名。 |
ファイルサイズ | 空白。 |
更新日時 | 空白。 |
ファイル種別 | TYPE_NONE。 |
最初にリストビューに表示している項目をクリアします。
ドキュメント(CMFCApp001Doc)で保持している現在選択されているディレクトリのファイルリストを取得してリストビューに登録します。
リストビューに登録するデータは以下のとおりです。
- ファイル名
- ファイルサイズ
- 更新日時
このとき第一カラムの「ファイル名」の項目のみ ユーザーパラメータ(LVITEM.lParam)としてドキュメントで保持しているファイルリストのインデックスをセットします。
(今回は行いませんが、カラム別にソート機能とかを実装した時に、ドキュメントのファイルリストと関連付けが出来ていないと困ることになるので。)
また、第一カラム以外ではLVITEM.lParamは無効にしておきます。(LVITEM.mask に LVIF_PARAMをセットしません。)
これ、有効にしてしまうとその項目がリストビューに表示されません。
ソースコード [FileListView.cpp]
/****************************************************************************/
/*!
* @brief ファイル一覧を更新する.
*
* @param なし.
*
* @retval なし.
*/
void CFileListView::UpdateFileList()
{
//! ドキュメント・クラスのオブジェクトを取得する.
CMFCApp001Doc* pDoc = dynamic_cast( GetDocument() );
if( pDoc == NULL ){
return;
}
CListCtrl& listCtrl = GetListCtrl();
//! リストビューの内容を一旦クリアする.
listCtrl.DeleteAllItems();
for( int i = 0; i < pDoc->GetFileInfoCount(); i++ ){
CFileInfo* info = pDoc->GetFileInfo( i );
if( info == NULL ){
continue;
}
//! リストビューにファイル情報をセットする.
LVITEM lvi;
memset( &lvi, 0, sizeof( lvi ) );
//! - ファイル名をセットする.
lvi.mask = LVIF_TEXT | LVIF_PARAM;
lvi.iItem = i;
lvi.iSubItem = 0;
lvi.pszText = info->GetFileName().GetBuffer();
lvi.lParam = i;
listCtrl.InsertItem( &lvi );
//! - ファイルサイズをセットする.
lvi.mask = LVIF_TEXT; //! LVIF_PARAMが付いていると表示しないので外す.
lvi.iSubItem = 1;
lvi.pszText = info->GetFileSize().GetBuffer();
listCtrl.SetItem( &lvi );
//! - ファイル日付をセットする.
lvi.iSubItem = 2;
lvi.pszText = info->GetFileDate().GetBuffer();
listCtrl.SetItem( &lvi );
}
}