【C++】VISTA以降のファイル選択ダイアログをカスタマイズする方法

C++
この記事は約10分で読めます。

C++で自作テキストエディタを作成しだして2ヵ月経過しました。基本的にC#をベースに移植する形で進めています。毎日何かにつけて詰まるので調べながら進めています。

ほんと色々とあるんですが、まずできるん?って思ったのがファイルを選択するダイアログのカスタマイズです。Vista以降のダイアログをカスタマイズする場合は、従来のコントロールではなく、COMを利用する必要があります。

従来のコントロールをカスタマイズするとクラシックスタイルなっちゃうんですよね。C#の場合だとWindows API CodePackを使えば簡単に拡張子とか文字コードを選択するコンボボックスは追加できるんですが、C++の場合どーすんのって話。

色々調べて、拡張子と文字コードの追加ができました。

ダイアログのイメージ

拡張子とエンコードのコンボボックスを追加しています。

サンプルコード

.hと.cppの両方になります。

OpenFileDialog.h

#pragma once

class OpenFileDialog {

public:
    OpenFileDialog(HWND hWnd);
    ~OpenFileDialog(void);

    bool ShowDialog();
    bool GetResult() const;
    std::wstring GetFilePath() const;
    int GetEncodingIndex() const;

private:
    void SetResult(bool result);
    void SetFilePath(std::wstring filePath);
    void SetEncodingIndex(int encodingIndex);

private:
    HWND parentHWnd;
    bool result;
    std::wstring filePath;
    int  encodingIndex;

};

OpenFileDialog.cpp

#include "pch.h"
#include "OpenFileDialog.h"
#include <ShObjIdl.h>

/// <summary>
/// コンストラクタ
/// </summary>
OpenFileDialog::OpenFileDialog(HWND parentHWnd) {
    this->parentHWnd = parentHWnd;
    this->result = false;
    this->encodingIndex = 0;
}

/// <summary>
/// デストラクタ
/// </summary>
OpenFileDialog::~OpenFileDialog(void) {
}

/// <summary>
/// ダイアログを表示します。
/// </summary>
bool OpenFileDialog::ShowDialog() {
    HRESULT hr;
    IFileDialog* pIFileDialog;

    pIFileDialog = NULL;
    hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, IID_IFileDialog, reinterpret_cast<void**>(&pIFileDialog));
    if (FAILED(hr) || pIFileDialog == NULL) {
        return false;
    }

    // オプションの設定
    DWORD options;
    //pIFileDialog->SetTitle(L"aaa");
    pIFileDialog->GetOptions(&options);
    //pIFileDialog->SetOptions(options | FOS_PICKFOLDERS); 
    pIFileDialog->SetOptions(options);

    IFileDialogCustomize* pIFileDialogCustomize;
    pIFileDialog->QueryInterface(IID_IFileDialogCustomize, (void**)&pIFileDialogCustomize);

    pIFileDialogCustomize->StartVisualGroup(1, L"エンコード(&E):");
    pIFileDialogCustomize->AddComboBox(2);
    pIFileDialogCustomize->AddControlItem(2, 0, L"自動選択");
    pIFileDialogCustomize->AddControlItem(2, 1, L"日本語(シフトJIS)");
    pIFileDialogCustomize->AddControlItem(2, 2, L"日本語(JIS)");
    pIFileDialogCustomize->AddControlItem(2, 3, L"日本語(EUC)");
    pIFileDialogCustomize->AddControlItem(2, 4, L"UTF-16LE");
    pIFileDialogCustomize->AddControlItem(2, 5, L"UTF-16BE");
    pIFileDialogCustomize->AddControlItem(2, 6, L"UTF-32LE");
    pIFileDialogCustomize->AddControlItem(2, 7, L"UTF-32BE");
    pIFileDialogCustomize->AddControlItem(2, 8, L"UTF-7");
    pIFileDialogCustomize->AddControlItem(2, 9, L"UTF-8");
    // デフォルトは自動選択
    pIFileDialogCustomize->SetSelectedControlItem(2, 0);
    pIFileDialogCustomize->EndVisualGroup();

    //拡張子フィルタの登録
    COMDLG_FILTERSPEC filterspec[] = {
        {L"すべてのファイル(*.*)", L"*.*"},
        {L"テキストファイル(*.txt)", L"*.txt"},
        {L"C++(*.cpp;*.c;*.h)", L"*.cpp;*.c;*.h"},
        {L"C#(*.cs)", L"*.cs"}
    };

    // 拡張子フィルタ追加
    hr = pIFileDialog->SetFileTypes(_countof(filterspec), filterspec);
    // デフォルトはべてのファイル
    hr = pIFileDialog->SetFileTypeIndex(1);

    // ダイアログ表示
    hr = pIFileDialog->Show(this->parentHWnd);

    if (SUCCEEDED(hr)) {
        wchar_t* pszFileName = NULL;
        IShellItem* pIShellItem = NULL;
        DWORD encodeIndex = NULL;

        hr = pIFileDialog->GetResult(&pIShellItem);
        if (SUCCEEDED(hr)) {
            // 文字コード取得
            pIFileDialogCustomize->GetSelectedControlItem(1, &encodeIndex);
            // ファイル名取得
            hr = pIShellItem->GetDisplayName(SIGDN_FILESYSPATH, &pszFileName);
        }

        if (SUCCEEDED(hr)) {
            this->SetFilePath(pszFileName);
            this->SetEncodingIndex((int)encodeIndex);
            this->SetResult(true);
            CoTaskMemFree(pszFileName);

        } else {
            this->SetResult(false);
        }

        if (pIShellItem) {
            pIShellItem->Release();
        }
    }
    pIFileDialogCustomize->Release();
    pIFileDialog->Release();

    return this->GetResult();
}

/// <summary>
/// ダイアログの実行結果を取得します。
/// </summary>
/// <returns></returns>
bool OpenFileDialog::GetResult() const {
    return this->result;
}

/// <summary>
/// ダイアログの実行結果を設定します。
/// </summary>
void OpenFileDialog::SetResult(bool result) {
    this->result = result;
}

/// <summary>
/// ファイルパスを取得します。
/// </summary>
/// <returns></returns>
std::wstring OpenFileDialog::GetFilePath() const {
    return this->filePath;
}

/// <summary>
/// ファイルパスを設定します。
/// </summary>
/// <param name="filePath"></param>
void OpenFileDialog::SetFilePath(std::wstring filePath) {
    this->filePath = filePath;
}

/// <summary>
/// 選択したエンコードのインデックスを取得します。
/// </summary>
/// <returns></returns>
int OpenFileDialog::GetEncodingIndex() const {
    return this->encodingIndex;
}

/// <summary>
/// 選択したエンコードのインデックスを設定します。
/// </summary>
/// <param name="encordingIndex"></param>
void OpenFileDialog::SetEncodingIndex(int encodingIndex) {
    this->encodingIndex = encodingIndex;
}

呼び出し方法

OpenFileDialogの引数には呼び出し元のウィンドウハンドルを設定します。呼び出してOKボタンを押した場合は、選択したファイルパスと選択したエンコードのインデックスを取得します。呼び出し方は.NETっぽくしました。

OpenFileDialog dialog = OpenFileDialog(this->GetHWindow());
if (dialog.ShowDialog()) {
    std::wcout.imbue(std::locale(""));
    std::wcout << dialog.GetFilePath() << std::endl;
    std::wcout << dialog.GetEncodingIndex() << std::endl;
}

おわりに 

CoCreateInstance を呼び出すパラメータの CLSID_FileOpenDialog を CLSID_FileSaveDialog にすると、ファイル保存ダイアログになります。ただ、オプション設定についてまだよく分かっていません。詳細を実装したらまた追記します。

コメント

タイトルとURLをコピーしました