8.ファイル分割とヘッダファイル(1)

 

※ 修正版をこちらのページにアップしています。将来的にはこのページは削除される可能性があります。

 


1.とりあえずぶった切る

さて、ここまでソースを書いてきてなんか思ったことはないですか?
ウインドウプロシージャ WndProc() の WM_CREATE の項が非常に長くなってきています。

これで困ることは、なんでしょうか?
コンピュータが困ることはありませんが、人間側にとって問題がありますね。
視認性が悪くなるというのが大きな問題なのです。
画面では、表示できる行数はせいぜい50行くらいだと思います。
それを超えた場合、スクロールしないと見えないというのでは、プログラムを書く側にとって、ちょっとつらいですね。

そこで、50行位を超えると、関数を分割することを考えても良いと思いますが、これはあくまで目安です。
分割すると、スパゲッティー状態になりやすいので、あなたの センス が問われるところです。
早速分割を行って行き1たいと思いますが、方針として、WndProc() では、メッセージの振り分けを行うことに集中して、それぞれのメッセージに対応する部分は関数を作成して外出しすることにしたいと思います。

以下のように、とりあえず分割しました。


/* -------------------------------------------------------------------------- */
/*          CALLBACK    WndProc                                               */
/*          PARAM       HWND        hWnd        : ウィンドウハンドル          */
/*                      UINT        uMsg        : メッセージ                  */
/*                      WPARAM      wParam      : メッセージの追加情報        */
/*                      LPARAM      lParam      : メッセージの追加情報        */
/*          RETURN      LRESULT                                               */
/*                      メッセージ処理の結果                                  */
/*          REMARK                                                            */
/* -------------------------------------------------------------------------- */
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  /* 関数本体部 */
  switch (uMsg)
  {
    case WM_COMMAND:
      switch (LOWORD(wParam))
      {
        case IDM_EXIT:
          SendMessage(hWnd, WM_CLOSE, 0, 0L);
          break;
        default:
          return (DefWindowProc(hWnd, uMsg, wParam, lParam));
      }
      break;
    case WM_CREATE:
      OnCreate(hWnd);
      break;
    case WM_DESTROY:
      PostQuitMessage(0);
      break;
    default:
      return (DefWindowProc(hWnd, uMsg, wParam, lParam));
  }

  return 0L;
}



/* -------------------------------------------------------------------------- */
/*          PROCEDURE   OnCreate                                              */
/*          PARAM       HWND        hWnd          : ウインドウハンドル        */
/*          REMARK                                                            */
/* -------------------------------------------------------------------------- */
void OnCreate(HWND hWnd)
{
  /* 変数宣言部 */
  HINSTANCE         hInstance;
  HWND              hCtlCombo;
  HWND              hCtlListView;
  HANDLE            hFind;
  WIN32_FIND_DATA   WinFindData;
  SYSTEMTIME        SystemTime;
  FILETIME          FileTime;
  LVITEM            LVItem;
  LV_COLUMN         LVColumn[] =
                    {
                      {LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM, LVCFMT_LEFT,  150, "名前",     0, 0},
                      {LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM, LVCFMT_RIGHT,  75, "サイズ",   0, 1},
                      {LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM, LVCFMT_LEFT,  110, "更新日時", 0, 2}
                    };
  char              szCurDir[MAX_PATH + 1];
  char              szDrives[128];
  int               nIndex = 0;
  int               i;
  INITCOMMONCONTROLSEX ICC;


  /* 関数本体部 */
  hInstance = (HINSTANCE) GetWindowLong(hWnd, GWL_HINSTANCE);
  hCtlCombo = CreateWindow("COMBOBOX",
                           NULL,
                           WS_CHILD | WS_VISIBLE | CBS_SORT | CBS_DROPDOWN | CBS_AUTOHSCROLL,
                           10,
                           5,
                           450,
                           100,
                           hWnd,
                           0,
                           hInstance,
                           NULL);
  SendMessage(hCtlCombo, WM_SETFONT, (WPARAM) GetStockObject(DEFAULT_GUI_FONT), MAKELPARAM(true , 0));

  GetCurrentDirectory(MAX_PATH + 1, szCurDir);
  ComboBox_SetText(hCtlCombo, szCurDir);
  ComboBox_AddString(hCtlCombo, szCurDir);

  GetLogicalDriveStrings(sizeof(szDrives), szDrives);

  i = 0;
  while (szDrives[i] != '\0')
  {
    ComboBox_AddString(hCtlCombo, &szDrives[i]);

    // 次のドライブ名へ
    while (szDrives[i] != '\0')
    {
      i++;
    }

    i++;
  }

  // コモンコントロール初期化
  ICC.dwSize = sizeof(INITCOMMONCONTROLSEX);
  ICC.dwICC  = ICC_LISTVIEW_CLASSES;
  InitCommonControlsEx(&ICC);

  // リストビュウ作成
  hCtlListView = CreateWindowEx(WS_EX_CLIENTEDGE,
                                WC_LISTVIEW,
                                NULL,
                                WS_CHILD | WS_VISIBLE | LVS_REPORT,
                                10,
                                30,
                                380,
                                320,
                                hWnd,
                                0,
                                hInstance,
                                NULL);
  for (i = 0; i < (sizeof(LVColumn) / sizeof(LV_COLUMN)); i++)
  {
    ListView_InsertColumn(hCtlListView, i, &LVColumn[i]);
  }

  if ((hFind = FindFirstFile("*.*", &WinFindData)) == INVALID_HANDLE_VALUE)
  {
    if (GetLastError() != ERROR_NO_MORE_FILES)
    {
      MessageBox(NULL, "ファイルの検索に失敗しました", "ERR", MB_OK);
      return;
    }
  }

  do
  {
    // ルートディレクトリ・カレントディレクトリは無視
    if (strcmp(WinFindData.cFileName, "." ) == 0 ||
        strcmp(WinFindData.cFileName, "..") == 0)
    {
      continue;
    }

    // 1項目(名前)
    LVItem.mask     = LVIF_TEXT;
    LVItem.iItem    = nIndex;
    LVItem.iSubItem = 0;
    LVItem.pszText  = WinFindData.cFileName;
    ListView_InsertItem(hCtlListView, &LVItem);

    // 2項目(サイズ)
    LVItem.iSubItem = 1;
    if (WinFindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
    {
      wsprintf(LVItem.pszText, "%s", "<DIR>");
    }
    else
    {
      wsprintf(LVItem.pszText, "%d", WinFindData.nFileSizeHigh * MAXDWORD + WinFindData.nFileSizeLow);
    }
    ListView_SetItem(hCtlListView, &LVItem);

    // 3項目(更新日時)
    LVItem.iSubItem = 2;
    // 協定世界時(UTC)を地域標準時に変更
    FileTimeToLocalFileTime(&WinFindData.ftLastWriteTime, &FileTime);
    FileTimeToSystemTime(&FileTime, &SystemTime);
    wsprintf(LVItem.pszText, "%d/%02d/%02d %d:%02d", SystemTime.wYear, SystemTime.wMonth,
                                                     SystemTime.wDay,  SystemTime.wHour,  SystemTime.wMinute);
    ListView_SetItem(hCtlListView, &LVItem);

    nIndex++;
  }
  while (FindNextFile(hFind, &WinFindData));

  FindClose(hFind);

  // ソート
  ListView_SortItemsEx(hCtlListView, LVWCmpProc, hCtlListView);

  return;
}

理解しやすさを考えて順番は逆にしましたが、OnCreate() 関数は、WndProc() 関数の前に置いてください。
理由は、前回も言いましたが、WndProc() の OnCreate(hWnd); のコードに到達した時点でコンパイラが、OnCreate() 関数が既知の関数になっておかないといけないためです。

また、ただぶった切っただけでは、コンパイルエラーになってしまいますね。
MessageBox(NULL, "Invalid File Handle. ", "ERR-INVALID", MB_OK); の後の break;return; に変更しています。
break は、case 文からの脱出です。関数自体を脱出するには return を使いましょう。
ちょっと気にしてほしいのは、break の後に何も書かずにすぐ ";" (セミコロン) が書いてあることです。
つまりこの関数は、戻り値がありません。こういった場合は、関数の戻り値の宣言に void と書きます。

WndProc() は、WM_CREATE メッセージを受け取ると、OnCreate() 関数にさっさと処理を投げています。
OnCreate() 関数の中では、ウインドウのハンドルが必要なので、引数で持っていくことにしました。
別に、グローバル変数でもいいのですが、あまり流行らない方法なので、引数を使うことにします。


2.まだまだぶった切る

さて、ウィンドウプロシージャは、すっきりしましたが、OnCreate() 関数はまだ大きいですね。
そこで、さらに分解することにします。

大きなくくりでいくと、
@コントロール(コンボボックス、リストビュウ)の作成
Aコンボボックスの項目設定
Bリストビュウの項目設定
に分けられそうですので、コントロールを作成する関数とコンボボックスの項目設定を行う関数、リストビュウの項目設定を行う関数の3つに分け、さらにそれらを管理する関数を作りました。

はじめに、管理する関数です。先ほどの OnCreate() 関数の基幹部分だけ残しました。
つまり、WndProc() 関数からコールされると、この関数は、@ABの処理を行う関数をそれぞれコールします。


/* -------------------------------------------------------------------------- */
/*          PROCEDURE   OnCreate                                              */
/*          PARAM       HWND        hWnd          : ウインドウハンドル        */
/*          REMARK                                                            */
/* -------------------------------------------------------------------------- */
void OnCreate(HWND hWnd)
{
  /* 変数宣言部 */
  HWND              hCtlCombo;
  HWND              hCtlListView;
  LV_COLUMN         LVColumn[] =
                    {
                      {LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM, LVCFMT_LEFT,  150, "名前",     0, 0},
                      {LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM, LVCFMT_RIGHT,  75, "サイズ",   0, 1},
                      {LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM, LVCFMT_LEFT,  110, "更新日時", 0, 2}
                    };
  INITCOMMONCONTROLSEX  ICC;


  /* 関数本体部 */
  ICC.dwSize = sizeof(INITCOMMONCONTROLSEX);
  ICC.dwICC  = ICC_LISTVIEW_CLASSES;
  InitCommonControlsEx(&ICC);

  /* ------------------------------------------------------------------------ */
  /* 配列全体のバイト数は、配列を宣言した関数に限ってsizeof演算子で求められる */
  /* ------------------------------------------------------------------------ */
  hCtlCombo    = WinCtlInit("COMBOBOX",  hWnd, NULL,     0);
  hCtlListView = WinCtlInit(WC_LISTVIEW, hWnd, LVColumn, sizeof(LVColumn));

  GetComboBoxItems(hCtlCombo);
  GetListViewItems(hCtlListView);

  return;
}

まだ、作成していませんが、@のコントロールを作成する関数は、WinCtlInit()、Aのコンボボックスの項目設定を行う関数は、GetComboBoxItems()、Bのリストビュウの項目設定を行う関数は、GetListViewItems() になりますね。

ここに、コメントで書いてある、配列全体のバイト数は、配列を宣言した関数に限ってsizeof演算子で求められるとは何でしょうか?
WinCtlInit() 関数を作ってから説明したいと思います。

では、早速、@のコントロールを作成する関数です。


/* -------------------------------------------------------------------------- */
/*          FUNCTION    WinCtlInit                                            */
/*          PARAM       LPCSTR      szClassName   : ウィンドウクラス名        */
/*                      HWND        hParentWnd    : 親ウィンドウのハンドル    */
/*                      LPVOID      lpParam       : アイテム情報              */
/*                      UINT        uSize         : アイテムのサイズ          */
/*          RETURN      HWND        : 作成されたウィンドウのハンドル          */
/*          REMARK      コントロールウインドウの作成                          */
/* -------------------------------------------------------------------------- */
HWND WinCtlInit(LPCTSTR szClassName, HWND hParentWnd, LPVOID lpParam, UINT uSize)
{
  /* 変数宣言部 */
  HWND              hControl;
  HINSTANCE         hInstance;
  LPLVCOLUMN        lpLVColumn;
  DWORD             dwStyle;
  UINT              i;


  /* 関数本体部 */
  // インスタンス取得
  hInstance = (HINSTANCE) GetWindowLong(hParentWnd, GWL_HINSTANCE);

  // コンボボックス作成
  if (lstrcmp(szClassName,"COMBOBOX") == 0)
  {
    dwStyle  = WS_CHILD | WS_VISIBLE | CBS_SORT | CBS_DROPDOWN | CBS_AUTOHSCROLL;
    hControl = CreateWindowEx(WS_EX_CLIENTEDGE,
                              szClassName,
                              NULL,
                              dwStyle,
                              10,
                              5,
                              380,
                              100,
                              hParentWnd, 0,
                              hInstance,
                              NULL);

    if (! hControl)
    {
      return NULL;
    }

    SendMessage(hControl, WM_SETFONT, (WPARAM) GetStockObject(DEFAULT_GUI_FONT), MAKELPARAM(true, 0));
  }
  // リストビュー作成
  else if (lstrcmp(szClassName, WC_LISTVIEW) == 0)
  {
    dwStyle  = WS_CHILD | WS_VISIBLE | LVS_REPORT | LVS_SINGLESEL;
    hControl = CreateWindowEx(WS_EX_CLIENTEDGE,
                              szClassName,
                              NULL,
                              dwStyle,
                              10,
                              30,
                              380,
                              320,
                              hParentWnd, 0,
                              hInstance,
                              NULL);

    if (! hControl)
    {
      return NULL;
    }

    if (lpParam)
    {
      lpLVColumn = (LPLVCOLUMN) lpParam;

      for (i = 0; i < (uSize / sizeof(LV_COLUMN)); i++)
      {
        ListView_InsertColumn(hControl, i, &lpLVColumn[i]);
      }
    }
  }

  return hControl;
}

さて、この関数が今回のキモになる関数なので、順を追って解説していくことにします。

まず、戻り値ですが HWND 型 になっていますね。この関数は、作成したコントロールのハンドルを返すことになります。
コントロールの作成に失敗したときはどうなりますか?
この場合は、NULL を返しますね。if (! hControl) { return NULL; } がこの部分に該当します。
また、1回に呼び出しにつき返せるのはひとつのコントロールのハンドルです。
このため、コンボボックスとリストビュウの計2回この関数をコールする必要があります。

ここで、ひとつの疑問がわいてきます。
わかなくても問題ありませんが…、その場合は、この段落を読み飛ばしてください。
通常関数では、スコープというものがあって、スコープの外に出ると使用していた変数はすべて破棄されます。
hControl も破棄されてしまいますが、なぜ問題ないのでしょうか?
実は、コントロール自体は、明確に DestroyWindow() しないと破棄されません。
破棄されるのは、コントロールのハンドルを格納した変数なのです。
何度もいっているようにハンドルは、数値でした。
WinCtlInit() 関数が終了した時点で、戻り値を OnCreate() 関数の hCtlCombo、hCtlListView に格納するため、問題なくコントロールを識別できるのです。

次に、引数を見ていきましょう。
HWND WinCtlInit(LPCTSTR szClassName, HWND hParentWnd, lpParam, UINT uSize) ですが、ひとつずつ説明します。
第1引数のLPCTSTR szClassName は、ウインドウクラス(定義済みのシステムクラス)名 です。
関数を呼び出す時点で、今回は、コンボボックス("COMBOBOX")を作るのか、リストビュウ(WC_LISTVIEW)を作るのかを指定します。
WinCtlInit() 関数では、どちらを作成するか判断するために、if (lstrcmp(szClassName, "COMBOBOX") == 0) と分岐条件を作っています。
第2引数 HWND hParentWnd は、CreateWindowEx() 関数で使用する親ウィンドウを指定します。
第3引数 LPVOID lpParam は、ちょっと難しいですが、コントロール作成時の付帯情報を指定します。
具体的には、リストビュウを作成する際のカラム情報(LVColumn)を渡しています。
このため、付帯項目がないコンボボックスでは、NULL を指定します。

パラメータとして渡すのは、カラム情報(LVColumn)ですが、LVColumn の型は、LV_COLUMN の配列になっています。
ちょっと複雑ですが、LV_COLUMN という構造体の配列ですね。
配列は、[ ] の記号の有無にかかわらず、「配列の先頭要素へのポインタ(アドレス)」に自動で読み替えられるため、ここでもアドレスが渡されることになります。
これは、構造体の配列でも変わりません。ちょっと見てみましょう。

ここで、引数の型を見てください。LPVOID 型 になっています。
LPLVCOLUMN 型でなくて大丈夫なのでしょうか?
結論から言うと、LPVOID 型 は、ポインタのオールマイティーなので、大丈夫です。
char のポインタでも、int のポインタでも、構造体のポインタでも大丈夫なのです。
ですが、使うときには、char のポインタなのか、int のポインタなのか、構造体のポインタなのかを明らかにする必要があります。
そうしないと、コンパイルエラーになってしまいます。
※ちょっと考えてみれば、当たり前ですが、アドレスだけではそこにどんな型のデータが格納されているかは、わかりませんね。
このため、lpLVColumn = (LPLVCOLUMN) lpParam; というように、LPLVCOLUMN のキャストをかけています。

そもそも、ここはなぜ、 LPVOID 型にしたのでしょうか。
今は、この引数を LPLVCOLUMN としてしか使っていませんが、将来、他の型の配列のポインタや構造体の配列のポインタを渡すことがあるかもしれません。
このため、先んじて、LPVOID 型 にしているのです。
※ もしかしたら使わないかもしれません。この場合は、単に手間が増えただけになります。

第4引数の UINT uSize ですが、これも第3引数におおいに関係があります。
したがって、コンボボックスでは、第3引数と同様に使いませんので 0 を指定します。

解説する前に、カラムを追加するロジックを見てみてください。


  lpLVColumn = (LPLVCOLUMN) lpParam;

  for (i = 0; i < (uSize / sizeof (LV_COLUMN)); i++)
  {
    ListView_InsertColumn(hControl, i, &lpLVColumn[i]);
  }

前回までは、以下のように書いていました。


  for (i = 0; i < (sizeof(LVColumn) / sizeof(LV_COLUMN)); i++)
  {
    ListView_InsertColumn(hCtlListView, i, &LVColumn[i]);
  }

変更されているのは、sizeof(LVColumn) が、uSize になっています。
ここで、先ほどの「配列全体のバイト数は、配列を宣言した関数に限ってsizeof演算子で求められる」というコメントを思い出してください。
今までと同じように、WinCtlInit() 関数内で配列全体のバイト数をもとめればいいような気がしますが、実はできません。
lpLVColumn は構造体の配列を指すポインタであって、OnCreate() で作成した LVColumn の配列とは、全くの別物なのです。
ポインタを使うと、まるで LVColumn の配列 そのものを扱っているようですが、実は指す値を取得するだけでそれ以上の機能はありません。
つまり、配列全体のバイト数は、ポインタでもらった時点で誰にもわからなくなるため、配列を宣言した関数でしか取得できないのです。
このため、わざわざ、第4引数を使って、配列全体のバイト数を渡しているのでした。
…難しいですね。わからなくても問題ありません。
とっとと読み進めてください。何回か読んでいるとそのうちに理解できるときが来ます。多分…。
ただ、普通に int や、char を引数として渡すときと、配列などポインタを引数として渡すときには、実は決定的な違いがあることは理解しておいてください。

長くなりましたが、次にAコンボボックスの項目設定を行う関数を見てみましょう。


/* -------------------------------------------------------------------------- */
/*          PROCEDURE   GetComboBoxItems                                      */
/*          PARAM       HWND        hCtlCombo   : コンボボックスハンドル      */
/*          RETURN      void                                                  */
/*          REMARK      コンボボックスフォルダ情報取得                        */
/* -------------------------------------------------------------------------- */
void GetComboBoxItems(HWND hCtlCombo)
{
  /* 変数宣言部 */
  char              szCurDir[MAX_PATH + 1];
  char              szDrives[128];


  /* 関数本体部 */
  // カレントディレクトリ取得
  if (! GetCurrentDirectory(_MAX_DIR + 1, szCurDir))
  {
    MessageBox(NULL, "現在のディレクトリを取得できません", "ERR", MB_OK);
    return;
  }

  ComboBox_SetText(hCtlCombo, szCurDir);
  ComboBox_AddString(hCtlCombo, szCurDir);

  // ドライブ名取得
  GetLogicalDriveStrings(sizeof(szDrives), szDrives);

  for (int i = 0; szDrives[i] != '\0'; i++)
  {
    ComboBox_AddString(hCtlCombo, &szDrives[i]);

    // 次のドライブ名へ
    while (szDrives[i] != '\0')
    {
      i++;
    }
  }

  return;
}

GetCurrentDirectory() 関数が失敗したときのエラーを追加しました。
引数には、対象となるコンボボックスのハンドルを指定します。

最後にBリストビュウの項目設定を行う関数を見てみます。


/* -------------------------------------------------------------------------- */
/*          PROCEDURE   GetListViewItems                                      */
/*          PARAM       HWND        hCtlLView   : リストビュウハンドル        */
/*          RETURN                                                            */
/*          REMARK      リストビュウファイル情報取得                          */
/* -------------------------------------------------------------------------- */
void GetListViewItems(HWND hCtlLView)
{
  /* 変数宣言部 */
  HANDLE            hFind;
  WIN32_FIND_DATA   WinFindData;
  SYSTEMTIME        SystemTime;
  FILETIME          FileTime;
  LVITEM            LVItem;


  /* 関数本体部 */
  if ((hFind = FindFirstFile("*.*", &WinFindData)) == INVALID_HANDLE_VALUE)
  {
    if (GetLastError() != ERROR_NO_MORE_FILES)
    {
      MessageBox(NULL, "ファイルの検索に失敗しました", "ERR", MB_OK);
      return;
    }
  }

  do
  {
    // ルートディレクトリ・カレントディレクトリは無視
    if (strcmp(WinFindData.cFileName, "." ) == 0 ||
        strcmp(WinFindData.cFileName, "..") == 0)
    {
      continue;
    }

    // 1項目(名前)
    LVItem.mask     = LVIF_TEXT;
    LVItem.iItem    = 0;
    LVItem.iSubItem = 0;
    LVItem.pszText  = WinFindData.cFileName;
    ListView_InsertItem(hCtlLView, &LVItem);

    // 2項目(サイズ)
    LVItem.iSubItem = 1;
    if (WinFindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
    {
      wsprintf(LVItem.pszText, "%s", "<DIR>");
    }
    else
    {
      wsprintf(LVItem.pszText, "%d", WinFindData.nFileSizeHigh * MAXDWORD + WinFindData.nFileSizeLow);
    }
    ListView_SetItem(hCtlLView, &LVItem);

    // 3項目(更新日時)
    LVItem.iSubItem = 2;
    // 協定世界時(UTC)を地域標準時に変更
    FileTimeToLocalFileTime(&WinFindData.ftLastWriteTime, &FileTime);
    FileTimeToSystemTime(&FileTime, &SystemTime);
    wsprintf(LVItem.pszText, "%d/%02d/%02d %d:%02d", SystemTime.wYear, SystemTime.wMonth,
                                                     SystemTime.wDay,  SystemTime.wHour,  SystemTime.wMinute);
    ListView_SetItem(hCtlLView, &LVItem);
  }
  while (FindNextFile(hFind, &WinFindData));

  FindClose(hFind);

  // ソート
  ListView_SortItemsEx(hCtlLView, LVWCmpProc, hCtlLView);

  return;
}

引数には、対象となるリストビュウのハンドルを指定します。

最後に今回の「WinMain.cpp」の全文を載せておきます。
関数をどこまで細かくするかは自由ですが、ひとつの関数が長くなりすぎた場合、機能ごとに分けることを考えてください。
そうすることで、関数名がズバリの機能をあらわすことができ、視認性が高まります。


/* ========================================================================== */
/*          FILE        WinMain.cpp                                           */
/*          CREATE      2006/10/01                                            */
/*          MODIFY      2007/02/21                                            */
/*          REMARK                                                            */
/*          COPYRIGHT   2000-2007, Latin. All Rights Reserved                 */
/* ========================================================================== */
#include <Windows.h>
#include <WindowsX.h>
#include <CommCtrl.h>
#include "Resource.h"


/* -------------------------------------------------------------------------- */
/*          CALLBACK    LVWCmpProc                                            */
/*          PARAM       LPARAM      lParam1     : 比較される 2 つのアイテム1 */
/*                      LPARAM      lParam2     : 比較される 2 つのアイテム2 */
/*                      LPARAM      lParamSort  : アプリケーション定義(任意)*/
/*          RETURN      -1 : lParam1 のアイテムが lParam2 のアイテムより前    */
/*                       0 : lParam1 のアイテムが lParam2 のアイテムと等しい  */
/*                       1 : lParam1 のアイテムが lParam2 のアイテムより後    */
/*                      lParamSortにはポインタ指定で出力先の指定が可能        */
/* -------------------------------------------------------------------------- */
static int CALLBACK LVWCmpProc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
{
  /* 変数宣言部 */
  HWND              hCtlLView;
  char              szItemText1[_MAX_PATH];
  char              szItemText2[_MAX_PATH];
  char              szAttribute1[256];
  char              szAttribute2[256];


  /* 関数本体部 */
  hCtlLView = (HWND) lParamSort;

  // パラム1情報取得
  ListView_GetItemText(hCtlLView, lParam1, 0, szItemText1,  sizeof(szItemText1));
  ListView_GetItemText(hCtlLView, lParam1, 1, szAttribute1, sizeof(szAttribute1));

  // パラム2情報取得
  ListView_GetItemText(hCtlLView, lParam2, 0, szItemText2,  sizeof(szItemText2));
  ListView_GetItemText(hCtlLView, lParam2, 1, szAttribute2, sizeof(szAttribute2));

  if (strcmp(szAttribute1, "<DIR>") == 0 &&
      strcmp(szAttribute2, "<DIR>") == 0)
  {
    return lstrcmpi(szItemText1, szItemText2);
  }
  else if (lstrcmpi(szAttribute1, "<DIR>") == 0)
  {
    return -1;
  }
  else if (lstrcmpi(szAttribute2, "<DIR>") == 0)
  {
    return 1;
  }
  else
  {
    return lstrcmpi(szItemText1, szItemText2);
  }
}

/* -------------------------------------------------------------------------- */
/*          FUNCTION    WinCtlInit                                            */
/*          PARAM       LPCSTR      szClassName   : ウィンドウクラス名        */
/*                      HWND        hParentWnd    : 親ウィンドウのハンドル    */
/*                      LPVOID      lpParam       : アイテム情報              */
/*                      UINT        uSize         : アイテムのサイズ          */
/*          RETURN      HWND        : 作成されたウィンドウのハンドル          */
/*          REMARK      コントロールウインドウの作成                          */
/* -------------------------------------------------------------------------- */
HWND WinCtlInit(LPCTSTR szClassName, HWND hParentWnd, LPVOID lpParam, UINT uSize)
{
  /* 変数宣言部 */
  HWND              hControl;
  HINSTANCE         hInstance;
  LPLVCOLUMN        lpLVColumn;
  DWORD             dwStyle;
  UINT              i;


  /* 関数本体部 */
  // インスタンス取得
  hInstance = (HINSTANCE) GetWindowLong(hParentWnd, GWL_HINSTANCE);

  // コンボボックス作成
  if (lstrcmp(szClassName,"COMBOBOX") == 0)
  {
    dwStyle  = WS_CHILD | WS_VISIBLE | CBS_SORT | CBS_DROPDOWN | CBS_AUTOHSCROLL;
    hControl = CreateWindowEx(WS_EX_CLIENTEDGE,
                              szClassName,
                              NULL,
                              dwStyle,
                              10,
                              5,
                              380,
                              100,
                              hParentWnd, 0,
                              hInstance,
                              NULL);

    if (! hControl)
    {
      return NULL;
    }

    SendMessage(hControl, WM_SETFONT, (WPARAM) GetStockObject(DEFAULT_GUI_FONT), MAKELPARAM(true, 0));
  }
  // リストビュー作成
  else if (lstrcmp(szClassName, WC_LISTVIEW) == 0)
  {
    dwStyle  = WS_CHILD | WS_VISIBLE | LVS_REPORT | LVS_SINGLESEL;
    hControl = CreateWindowEx(WS_EX_CLIENTEDGE,
                              szClassName,
                              NULL,
                              dwStyle,
                              10,
                              30,
                              380,
                              320,
                              hParentWnd, 0,
                              hInstance,
                              NULL);

    if (! hControl)
    {
      return NULL;
    }

    if (lpParam)
    {
      lpLVColumn = (LPLVCOLUMN) lpParam;

      for (i = 0; i < (uSize / sizeof(LV_COLUMN)); i++)
      {
        ListView_InsertColumn(hControl, i, &lpLVColumn[i]);
      }
    }
  }

  return hControl;
}

/* -------------------------------------------------------------------------- */
/*          PROCEDURE   GetComboBoxItems                                      */
/*          PARAM       HWND        hCtlCombo   : コンボボックスハンドル      */
/*          RETURN      void                                                  */
/*          REMARK      コンボボックスフォルダ情報取得                        */
/* -------------------------------------------------------------------------- */
void GetComboBoxItems(HWND hCtlCombo)
{
  /* 変数宣言部 */
  char              szCurDir[MAX_PATH + 1];
  char              szDrives[128];


  /* 関数本体部 */
  // カレントディレクトリ取得
  if (! GetCurrentDirectory(_MAX_DIR + 1, szCurDir))
  {
    MessageBox(NULL, "現在のディレクトリを取得できません", "ERR", MB_OK);
    return;
  }

  ComboBox_SetText(hCtlCombo, szCurDir);
  ComboBox_AddString(hCtlCombo, szCurDir);

  // ドライブ名取得
  GetLogicalDriveStrings(sizeof(szDrives), szDrives);

  for (int i = 0; szDrives[i] != '\0'; i++)
  {
    ComboBox_AddString(hCtlCombo, &szDrives[i]);

    // 次のドライブ名へ
    while (szDrives[i] != '\0')
    {
      i++;
    }
  }

  return;
}

/* -------------------------------------------------------------------------- */
/*          PROCEDURE   GetListViewItems                                      */
/*          PARAM       HWND        hCtlLView   : リストビュウハンドル        */
/*          RETURN                                                            */
/*          REMARK      リストビュウファイル情報取得                          */
/* -------------------------------------------------------------------------- */
void GetListViewItems(HWND hCtlLView)
{
  /* 変数宣言部 */
  HANDLE            hFind;
  WIN32_FIND_DATA   WinFindData;
  SYSTEMTIME        SystemTime;
  FILETIME          FileTime;
  LVITEM            LVItem;


  /* 関数本体部 */
  if ((hFind = FindFirstFile("*.*", &WinFindData)) == INVALID_HANDLE_VALUE)
  {
    if (GetLastError() != ERROR_NO_MORE_FILES)
    {
      MessageBox(NULL, "ファイルの検索に失敗しました", "ERR", MB_OK);
      return;
    }
  }

  do
  {
    // ルートディレクトリ・カレントディレクトリは無視
    if (strcmp(WinFindData.cFileName, "." ) == 0 ||
        strcmp(WinFindData.cFileName, "..") == 0)
    {
      continue;
    }

    // 1項目(名前)
    LVItem.mask     = LVIF_TEXT;
    LVItem.iItem    = 0;
    LVItem.iSubItem = 0;
    LVItem.pszText  = WinFindData.cFileName;
    ListView_InsertItem(hCtlLView, &LVItem);

    // 2項目(サイズ)
    LVItem.iSubItem = 1;
    if (WinFindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
    {
      wsprintf(LVItem.pszText, "%s", "<DIR>");
    }
    else
    {
      wsprintf(LVItem.pszText, "%d", WinFindData.nFileSizeHigh * MAXDWORD + WinFindData.nFileSizeLow);
    }
    ListView_SetItem(hCtlLView, &LVItem);

    // 3項目(更新日時)
    LVItem.iSubItem = 2;
    // 協定世界時(UTC)を地域標準時に変更
    FileTimeToLocalFileTime(&WinFindData.ftLastWriteTime, &FileTime);
    FileTimeToSystemTime(&FileTime, &SystemTime);
    wsprintf(LVItem.pszText, "%d/%02d/%02d %d:%02d", SystemTime.wYear, SystemTime.wMonth,
                                                     SystemTime.wDay,  SystemTime.wHour,  SystemTime.wMinute);
    ListView_SetItem(hCtlLView, &LVItem);
  }
  while (FindNextFile(hFind, &WinFindData));

  FindClose(hFind);

  // ソート
  ListView_SortItemsEx(hCtlLView, LVWCmpProc, hCtlLView);

  return;
}

/* -------------------------------------------------------------------------- */
/*          PROCEDURE   OnCreate                                              */
/*          PARAM       HWND        hWnd          : ウインドウハンドル        */
/*          REMARK                                                            */
/* -------------------------------------------------------------------------- */
void OnCreate(HWND hWnd)
{
  /* 変数宣言部 */
  HWND              hCtlCombo;
  HWND              hCtlListView;
  LV_COLUMN         LVColumn[] =
                    {
                      {LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM, LVCFMT_LEFT,  150, "名前",     0, 0},
                      {LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM, LVCFMT_RIGHT,  75, "サイズ",   0, 1},
                      {LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM, LVCFMT_LEFT,  110, "更新日時", 0, 2}
                    };
  INITCOMMONCONTROLSEX  ICC;


  /* 関数本体部 */
  ICC.dwSize = sizeof(INITCOMMONCONTROLSEX);
  ICC.dwICC  = ICC_LISTVIEW_CLASSES;
  InitCommonControlsEx(&ICC);

  /* ------------------------------------------------------------------------ */
  /* 配列全体のバイト数は、配列を宣言した関数に限ってsizeof演算子で求められる */
  /* ------------------------------------------------------------------------ */
  hCtlCombo    = WinCtlInit("COMBOBOX",  hWnd, NULL,     0);
  hCtlListView = WinCtlInit(WC_LISTVIEW, hWnd, LVColumn, sizeof(LVColumn));

  GetComboBoxItems(hCtlCombo);
  GetListViewItems(hCtlListView);

  return;
}

/* -------------------------------------------------------------------------- */
/*          CALLBACK    WndProc                                               */
/*          PARAM       HWND        hWnd        : ウィンドウハンドル          */
/*                      UINT        uMsg        : メッセージ                  */
/*                      WPARAM      wParam      : メッセージの追加情報        */
/*                      LPARAM      lParam      : メッセージの追加情報        */
/*          RETURN      LRESULT                                               */
/*                      メッセージ処理の結果                                  */
/*          REMARK                                                            */
/* -------------------------------------------------------------------------- */
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  /* 関数本体部 */
  switch (uMsg)
  {
    case WM_COMMAND:
      switch (LOWORD(wParam))
      {
        case IDM_EXIT:
          SendMessage(hWnd, WM_CLOSE, 0, 0L);
          break;
        default:
          return (DefWindowProc(hWnd, uMsg, wParam, lParam));
      }
      break;
    case WM_CREATE:
      OnCreate(hWnd);
      break;
    case WM_DESTROY:
      PostQuitMessage(0);
      break;
    default:
      return (DefWindowProc(hWnd, uMsg, wParam, lParam));
  }

  return 0L;
}

/* -------------------------------------------------------------------------- */
/*          WINAPI      WinMain                                               */
/*          PARAM       HINSTANCE   hInstance   : 現在のインスタンス          */
/*                      HINSTANCE   hPrevInst   : 直前のインスタンス          */
/*                      LPSTR       lpCmdLine   : コマンドライン引数          */
/*                      int         nCmdShow    : ウインドウの表示形式        */
/*          RETURN      int                     :                             */
/*                      メッセージの wParam パラメータ                        */
/*          REMARK      Win32 アプリケーションの初期エントリポイント          */
/*                      Windows システムが呼び出す。                          */
/* -------------------------------------------------------------------------- */
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nCmdShow)
{
  /* 変数宣言部 */
  HWND              hWnd;
  WNDCLASSEX        WndClass;
  MSG               Msg;


  /* 関数本体部 */
  // ウインドウクラス作成
  WndClass.cbSize        = sizeof(WndClass);
  WndClass.style         = CS_HREDRAW | CS_VREDRAW;
  WndClass.lpfnWndProc   = WndProc;
  WndClass.cbClsExtra    = 0;
  WndClass.cbWndExtra    = 0;
  WndClass.hInstance     = hInstance;
  WndClass.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
  WndClass.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);
  WndClass.hCursor       = LoadCursor(NULL, IDC_ARROW);
  WndClass.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
  WndClass.lpszMenuName  = MAKEINTRESOURCE(IDM_MENUMAIN);
  WndClass.lpszClassName = "FTPManager";

  // ウィンドウクラス登録
  if (! RegisterClassEx(&WndClass))
  {
    return FALSE;
  }

  // ウィンドウ作成
  hWnd = CreateWindow(WndClass.lpszClassName, // 登録されているクラス名
                      "FTPManager",           // ウィンドウ名
                      WS_OVERLAPPEDWINDOW,    // ウィンドウスタイル
                      CW_USEDEFAULT,          // ウィンドウの横方向の位置
                      CW_USEDEFAULT,          // ウィンドウの縦方向の位置
                      CW_USEDEFAULT,          // ウィンドウの幅
                      CW_USEDEFAULT,          // ウィンドウの高さ
                      NULL,                   // 親ウィンドウまたはオーナーウィンドウのハンドル
                      NULL,                   // メニューハンドルまたは子ウィンドウ ID
                      hInstance,              // アプリケーションインスタンスのハンドル
                      NULL);                  // ウィンドウ作成データ

  if (! hWnd)
  {
    return FALSE;
  }

  // ウィンドウの表示
  ShowWindow(hWnd, nCmdShow);
  UpdateWindow(hWnd);

  // メッセージループ
  while (GetMessage(&Msg, NULL, 0, 0))
  {
    TranslateMessage(&Msg);
    DispatchMessage(&Msg);
  }

  return (int) Msg.wParam;
}

本日のソース: FTPManager108(VC8).zip   FTPManager108(BCC).zip


※小言

このサイトでは、よくウオッチを使って実際の値を表示していますが、皆さんもできるだけやってください。
頭でいくら考えてもなかなか理解しにくいものです。
皆さんは、コンピューターではないのですから、コンピューターの中でどうなっているかを想像しろといわれても、そもそも難しいのです。

これは、実際にバグがでてきた場合にも役に立ちます。
デバッグといいますが、怪しそうなところにあたりをつけて、実際にコンピュータがどのように処理を行っているのか 1 ステップずつ行います。
確かに、めんどくさい作業です。
このめんどくさい作業をやった後でないと、「こんなめんどくさい作業をやりたくないから、デバックしなくていいようにきちんとコーディングしよう」と思えるようになりません。
この辺にも、プログラミングがいやになる原因が潜んでいるのかも、しれませんね。


Last Update 2014/02/06 22:43
[Index] [Prev] [Next]