作成日: 2003/02/09

ATL でダイアログベースのコントロールを作成する

概要

詳細な内容はオリジナルのサポート技術情報 175503 を参照してください。 ATL オブジェクトウィザードを使ってフルコントロールを作成した場合、生成されるクラスは CComControl を継承しています。 これを、独自に CDialogImpl から派生させて作ったクラス(サンプルでは CComDlgCtrl と名づけられています)に 置換してしまうことで、ダイアログベースのコントロールを作成することができます。
が、IE をコンテナとして動作させた場合、オリジナルコードのままではダイアログボックス上のテキストボックス に入力した日本語がことごとく化けてしまうので、その回避策も併せて提示しています。 なお、以下の説明では VC++ 6 を前提にしています。

プロジェクトを作成する

VC を起動し、メニューから [ファイル] - [新規作成] を選び、表示される [新規作成] ダイアログボックスから [ATL COM AppWizard] を選択し、適当なプロジェクト名(ここでは howtoatldlg とします)を入力し、[OK] をクリックします。
[ATL COM AppWizard - ステップ 1/1] が表示されます。もし、MFC を使用したい場合は、[MFC のサポート] にチェックを いれておきます。そのまま [終了] をクリックします。
[新規プロジェクト情報] が表示されるので、そのまま [OK] をクリックします。

ATL コントロールクラスを挿入する

メニューから [挿入] - [ATL オブジェクトの新規作成] を選びます。
[ATL オブジェクトウィザード] が表示されるので、[カテゴリ] に [コントロール]、[オブジェクト] に [フルコントロール] を選択して、[次へ] をクリックします。
[ATL オブジェクトウィザードのプロパティ] が表示されるので、ショートネームに適当な名前(ここでは AtlDlg とします)を入力し、そのまま、[OK] をクリックします。
コントロールクラスが生成されますので、[ClassView] などで確認してください。

CDialogImpl から派生させたクラス(ここでは CComDlgCtrl としています)を作成する

VC のエディタでも、その他のエディタでもかまいませんので、以下の内容を入力し、ファイル名を CComDlgCtrl.h として保存します。

/*
 * CComDlgCtrl.h - ダイアログベースのコントロールを作成するために
 * CComControl の代わりに使用するテンプレートクラス CComDlgCtrl を
 * 定義する。
 *
 * マイクロソフト サポート技術情報 - 175503
 * HOWTO: Write a Dialog-based ActiveX Control Using ATL
 * を参照のこと。
 */

#ifndef __CCOMDLGCTRL_H__
#define __CCOMDLGCTRL_H__

#include "resource.h"
#include <atlctl.h>


/////////////////////////////////////////////////////////////////////////////
// CComDlgCtrl

template <class T>
class ATL_NO_VTABLE CComDlgCtrl : public CComControlBase, public CDialogImpl<T>
{
public:
    CComDlgCtrl() : CComControlBase(m_hWnd) {}
    HRESULT FireOnRequestEdit(DISPID dispID) {
        T* pT = static_cast<T*>(this);
        return T::__ATL_PROP_NOTIFY_EVENT_CLASS::FireOnRequestEdit(pT->GetUnknown(), dispID);
    }
    HRESULT FireOnChanged(DISPID dispID) {
        T* pT = static_cast<T*>(this);
        return T::__ATL_PROP_NOTIFY_EVENT_CLASS::FireOnChanged(pT->GetUnknown(), dispID);
    }
    virtual HRESULT ControlQueryInterface(const IID& iid, void** ppv) {
        T* pT = static_cast<T*>(this);
        return pT->_InternalQueryInterface(iid, ppv);
    }
    virtual HWND CreateControlWindow(HWND hWndParent, RECT& rcPos) {
        T* pT = static_cast<T*>(this);
        // CComControl とは、ここが異なる。
        return pT->Create(hWndParent);
    }

    typedef CComDlgCtrl<T>    thisClass;
    BEGIN_MSG_MAP(thisClass)
        MESSAGE_HANDLER(WM_SETFOCUS, CComControlBase::OnSetFocus)
        MESSAGE_HANDLER(WM_KILLFOCUS, CComControlBase::OnKillFocus)
        MESSAGE_HANDLER(WM_MOUSEACTIVATE, CComControlBase::OnMouseActivate)
    END_MSG_MAP()
};

#endif //__CCOMDLGCTRL_H__

作成したファイルは [FileView] で右クリックし、[ファイルをプロジェクトへ追加] で表示される [プロジェクトへファイルを追加] から選択してプロジェクトへ追加します。

CComControl を作成した CComDlgCtrl クラスと置き換える

CAtlDlg クラスの定義が記述された AtlDlg.h を開き、継承している CComControl を CComDlgCtrl に置き換えます。

class ATL_NO_VTABLE CAtlDlg :
    public CComObjectRootEx<CComSingleThreadModel>,
    public IDispatchImpl<IAtlDlg, &IID_IAtlDlg, &LIBID_HOWTOATLDLGLib>,
    //public CComControl<CAtlDlg>,  // ここを
    public CComDlgCtrl<CAtlDlg>,    // これに置換します。
    public IPersistStreamInitImpl<CAtlDlg>,
    public IOleControlImpl<CAtlDlg>,
    public IOleObjectImpl<CAtlDlg>,
    public IOleInPlaceActiveObjectImpl<CAtlDlg>,
    public IViewObjectExImpl<CAtlDlg>,
    public IOleInPlaceObjectWindowlessImpl<CAtlDlg>,
    public IPersistStorageImpl<CAtlDlg>,
    public ISpecifyPropertyPagesImpl<CAtlDlg>,
    public IQuickActivateImpl<CAtlDlg>,
    public IDataObjectImpl<CAtlDlg>,
    public IProvideClassInfo2Impl<&CLSID_AtlDlg, NULL, &LIBID_HOWTOATLDLGLib>,
    public CComCoClass<CAtlDlg, &CLSID_AtlDlg>
{

また、CComDlgCtrl の定義を参照できるように、

#include "CComDlgCtrl.h"

を AtlDlg.h の先頭のあたりに追加してください。

ダイアログを作成する

[Resource View] を右クリックし、[挿入] を選びます。[リソースの挿入] ダイアログが表示されるので、 [Dialog] のところの左横にある + をクリックして、表示されるダイアログ形式一覧の中から、[IDD_FORMVIEW] を選択し、[新規作成] ボタンを押します。
挿入されたダイアログをリソースエディタで編集し、テキストボックスを配置します。 ここでは、[IDC_EDIT1]、[IDC_EDIT2] のリソースIDを持つテキストボックスを2つ挿入しました。 また、ダイアログのリソースIDは挿入されたときのまま、[IDD_FORMVIEW] としています。

CAtlDlg とダイアログを関連付ける

AtlDlg.h を開き、作成したダイアログのリソースIDを定義します。

/////////////////////////////////////////////////////////////////////////////
// CAtlDlg
class ATL_NO_VTABLE CAtlDlg :
    ...
{
public:
    enum { IDD = IDD_FORMVIEW };

    CAtlDlg()
    {
    }
    ...
メッセージマップを変更する

AtlDlg.h を開き、メッセージマップを変更します。

BEGIN_MSG_MAP(CAtlDlg)
    //CHAIN_MSG_MAP(CComControl<CAtlDlg>)    // ここを
    CHAIN_MSG_MAP(CComDlgCtrl<CAtlDlg>)      // これに置換します。
    DEFAULT_REFLECTION_HANDLER()
END_MSG_MAP()

オリジナルコードでは、CHAIN_MSG_MAP を削除し、WM_SETFOCUS、WM_KILLFOCUS、WM_MOUSEACTIVATE のメッセージを CComDlgCtrl のベースクラスである CComControlBase 内のハンドラにマップするようにコードされていますが、 ここでは、CComDlgCtrl 内にメッセージマップを作成し、そこへチェインするようにしてみました(CComDlgCtrl の メッセージマップを参照してください)。なお、この部分はオリジナルとやり方を変えてみただけで、 意味的には同じものです(IME からの日本語入力が化けることへの対策ではありません)。

ダイアログを表示できるようにする

AtlDlg.h を開き CAtlDlg のコンストラクタを変更します。

    CAtlDlg()
    {
        m_bWindowOnly = TRUE; // これを追加します。
    }

これでコントロールの作成と同時にダイアログが作成され表示されるようになります。
また、AtlDlg.h にコードされている OnDraw() メソッドは必要ありませんので、削除してください。

ビルドしてダイアログが表示されるか試してみる

メニューから [ビルド] - [ビルド] を選択して、プロジェクトをビルドします。ビルドするとプロジェクト 内のソースがコンパイル・リンクされ、作成された dll がレジストリに登録されます。 ここではプロジェクトの構成を Win32 Debug にしていますので、プロジェクト下の Debug フォルダの中に howtoatldlg.dll が作成され、レジストリに登録されます。
次に、メニューから [プロジェクト] - [設定] を選び、[プロジェクトの設定] ダイアログを表示します。 [デバッグ] タブで [デバッグセッションの実行可能ファイル] の右横にある矢印をクリックし、 [デフォルト Web ブラウザ] を選択します。[デバッグセッションの実行可能ファイル] 欄にデフォルトの ブラウザへのパスが設定されるので、[OK] をクリックします。
ATL コントロールクラスを挿入する 作業を行ったときに、プロジェクトのフォルダ下に、 ショートネーム.htm の名前でファイルが作成されています。ここではショートネームを AtlDlg としましたので、AtlDlg.htm が作成されています。 このファイルには挿入したコントロールがウェブに貼り付けた形で使用できるようにタグが記述されていますので、 作成したコントロールの表示状態を見るには好都合です。 メニューから [ビルド] - [デバッグの開始] - [実行] を選択すると、ブラウザが起動するので、 この AtlDlg.htm をドラッグ&ドロップして開きます。
コントロールが表示されたでしょうか?。 表示されない場合、以下のことが考えられます。

  • ブラウザが IE ではない。ネットスケープなどではそのままでは ActiveX コントロールを表示できません。IE を使ってください。
  • IE の設定で ActiveX コントロールが実行できないようになっている。この場合、実行できるようにブラウザのオプションを設定してください。

ここで表示されるダイアログ上のテキストボックスでは日本語入力は化けません。けれども、キーメッセージを ハンドリングしていないため、[Tab] を押してもコントロール間のフォーカスの移動ができませんし、 テキストボックス内では、矢印キーでのキャレットの移動が行えません。これらには次のステップで対応します。

キーメッセージをハンドリングする

さきほど表示したコントロールでは、テキストボックスへの日本語入力は化けませんでしたが、その代わりに [Tab] でのフォーカス移動などが行えませんでした。 オリジナルのサポート技術情報 175503 では、これに対応するために、コントロールが継承している IOleInPlaceActiveObjectImpl::TranslateAccelerator() をオーバーライドしています。しかし、 テストコントロールコンテナや VB をコンテナとした場合には、オリジナルのコードで正しく動作するのですが、 コンテナが IE の場合、日本語が化けてしまうようです。IE の問題のような気もするのですが、 化けてしまっては仕方ないので、手を加えます。オリジナルのコードは以下のようになっています。

    STDMETHOD(TranslateAccelerator)(MSG *pMsg)
    {
        if ( ( pMsg->message < WM_KEYFIRST
            || pMsg->message > WM_KEYLAST )
          && ( pMsg->message < WM_MOUSEFIRST
            || pMsg->message > WM_MOUSELAST ) )
            return S_FALSE;
        return ( IsDialogMessage( m_hWnd, pMsg ) ) ? S_OK : S_FALSE;
    }

ハンドリングしたいキーコードのみ IsDialogMessage() に渡してダイアログプロシージャに処理させています。 IE をコンテナとした場合、この IsDialogMessage() の処理がうまく動作せず日本語が化けているようです。
かといって IsDialogMessage() を使わないとすれば、自前で全部コードしなくてはなりません。それでは全然嬉しくないので、 ダイアログ上でのフォーカス移動などに必要なキーメッセージのみ IsDialogMessage() で処理するようにします。 変更後のコードは以下のようになります。メッセージを処理しなかった場合は、S_FALSE を返すようにしています。

    STDMETHOD(TranslateAccelerator)(MSG *pMsg) {

        if (pMsg->message == WM_KEYDOWN) {
            switch (pMsg->wParam) {
            case VK_TAB:
            case VK_HOME:
            case VK_END:
            case VK_PRIOR:
            case VK_NEXT:
            case VK_UP:
            case VK_DOWN:
            case VK_LEFT:
            case VK_RIGHT:
                // ダイアログプロシージャに処理させる。
                BOOL bRC = IsDialogMessage(pMsg);
                if (bRC) {
                    return S_OK;
                }
                break;
            }
        }

        return S_FALSE;
    }
その他

ベースクラスをこっそり変更しても、クラスウィザードなどはきちんと使用できます。 ためしに、[Class View] からウィンドウメッセージハンドラの追加を行っても、正しく反映されました。 以下でダウンロードできるサンプルソースでは、クラスウィザードから WM_INITDIALOG ハンドラを追加してあります。 なお、追加したハンドラからモジュール状態に依存する MFC の関数を呼び出す場合、

AFX_MANAGE_STATE(AfxGetStaticModuleState());
をハンドラの最初で呼び出しておく必要があるようです。
また、ここで作成したコントロールをレジストリから削除するには、コマンドプロンプトを起動し、 作成された dll が存在するフォルダに移動し、
regsvr32 /u howtoatldlg.dll

としてください。

サンプルソース
howtoatldlg.zip (10.7 KB)