MASATOの開発日記 2000年6月


6/26

 MFCのドキュメントテンプレート。クラス名でいうとCDocTemplate。いったい何をするクラスなんだろうか。このクラスは、ドキュメント−ビュー アーキテクチャの結構重要な部分を司っている。が、余り表面に出てくることはない。実際に何をやっているかというと、例えばテキストとビットマップを編集出来るエディタの場合は、テキスト用のCDocTemplateのオブジェクトと、ビットマップ用のCDocTemplateのオブジェクトの二つがあるという事である。要は、「ドキュメントの種類」を扱っているクラスなのである。SDIかMDIかによってCSingleDocTemplateとCMultiDocTemplateを使い分ける。CSingleDocTemplateは、一度に一つのドキュメントしか開けず、CMultiDocTemplateは、一度に幾つでもドキュメントを開ける。そして、アプリケーション(SDI、MDI共に)は、幾つでもドキュメントテンプレートのオブジェクトを持てる。つまり、SDIアプリケーションがSDIアプリケーションたる理由は、CSingleDocTemplateを使っているからである。というわけで重要なはずのドキュメントテンプレートなのだが、VisualC++が勝手に作るソースで使われているだけで、これの派生クラスを作って拡張する必要は余り無い。よってユーザーの目にも触れないわけだ。しかし、必要は余りないのだがこのドキュメントテンプレートを拡張することによって多少便利な事が出来る。

(Visual C++)ドキュメントのデフォルト名を実行時に変更する方法。

 新規ドキュメントを作成した時、そのドキュメント名を何にするか。最初の状態だと「無題」となっているはずである。これを変える方法は、過去のトピックにあるが、SDIでもMDIでも通用するスマートな方法を考えてみた。ドキュメントのデフォルト名を決めているのは、実はドキュメントテンプレートである。よって、これをうまく拡張する事によってさくっと変更出来てしまうのだ。手順は以下の通り。幾つか省略してありますのでC++をある程度知らないと難しいです。
  1. CSingleDocTemplate/CMultiDocTemplateを派生させたクラスを作る。どちらを使うかはSDIかMDIかによりけり。この派生クラスを、CMyDocTemplateと仮にしておく。また、基底クラスをCxxxDocTemplateと記述する。必要においてxxxをSingleかMultiに書き換えよう。なお、CMyDocTemplateにデフォルトコンストラクタは必要無い。というかあってはならない。MSDNを引いて基底クラスと同じコンストラクタを用意しておこう。

  2.  
  3. 以下の関数の宣言を追加。

  4. -----
    virtual BOOL GetDocString(CString& rString, enum DocStringIndex index) const;
    -----
     
  5. 定義も同様に追加。

  6. -----
    BOOL CMyDocTemplate::GetDocString(CString& rString, enum DocStringIndex index) const
    {
        if (index == CDocTemplate::docName){
            rString = "文字列";
            return TRUE;
        }else return CxxxDocTemplate::GetDocString(rString, index);
    }
    -----
    この例のままだと、ドキュメントのデフォルト名は「文字列」となる。実行時に変えるには、ここに変数を当てはめよう。その変数を何時設定するかは・・・CxxxAppのInitInstanceあたりだろうか。詳しくは次で。
     
  7. 最後、CxxxAppInitInstanceで、

  8. -----
    CxxxDocTemplate* pDocTemplate;
    pDocTemplate = new CxxxDocTemplate(...);
    -----
    とある所のCxxxDocTemplateを、CMyDocTemplateに変更しよう。この際、CMyDocTemplateの変数か何かを書き換えたいならば、この次に、pDocTemplate->...としてアクセスしよう。
 考えてみると、一般的なアプリケーションではあまり使わないものかもしれませんね(^^;)。

(Visual C++)既に開かれているドキュメントを開く方法。

 MFCのドキュメント−ビューアーキテクチャを使用したSDIやMDIで、すでに開いてあるドキュメントをまた開こうとするとどうなるだろうか。実は別にどうもならない。SDIなら何も起きないし、MDIならばすでに開かれているドキュメントがアクティブになるだけだ。SDIの場合、開きなおす、という意図で同じファイルを開きたい事が良くある。というわけで、既にドキュメントが開かれていようとなかろうと、「開く」で指定したドキュメントを開く方法を紹介しよう。

 その前に、何故開けないのかを考えてみよう。それは、ずばりCDocTemplate::MatchDocTypeのせいである。この関数は、既に同じ名前のドキュメントが開かれているかどうか調べ、もしも開かれているならば、yesAlreadyOpenを返してしまうのである。ということは、この関数をオーバーライドして、yesAlreadyOpenを返さないようにしてしまえば良い。その手順は次の通り。

  1. CSingleDocTemplate/CMultiDocTemplateを派生させたクラスを作る。方法はすぐ上のトピックと同様。

  2.  
  3. 以下の関数の宣言を追加。

  4. -----
    virtual Confidence MatchDocType(LPCTSTR lpszPathName, CDocument*& rpDocMatch);
    -----
     
  5. 定義も同様に追加。

  6. -----
    CMyDocTemplate::Confidence CMyDocTemplate::MatchDocType(LPCTSTR lpszPathName, CDocument*& rpDocMatch)
    {
        Confidence type = CMultiDocTemplate::MatchDocType(lpszPathName, rpDocMatch);
        if (type == yesAlreadyOpen){
            rpDocMatch = NULL;
            return yesAttemptNative;
        }else return type;
    }
    -----
     
これで、既に開いてあるドキュメントを開く事が出来る。その結果、SDIだと、同じドキュメントを開けば、開きなおしたのと同じ効果があり、MDIだと、同じドキュメントを幾つでも開ける。MDIのほうはイマイチ使い道が少ないかもしれませんね(^^;)。でも、SDIならば結構便利になると思います。
 

6/23

 Windowsで、一定時間毎に処理を行う方法として、WM_TIMER、GetTickCount、timeGetTimeを使う方法がある。(他にもあるかもしれないが)だが、これらの方法はみんな精度が違うようなので、精度を測定するツールを作ってみた。結果は、私のマシンではtimeGetTimeが圧倒的な精度ではあった。そして、この結果をHDBENCHのように、クリップボードにコピーしようと思って、その方法を探してみた。エディットコントロールからクリップボードにコピーするのはとても簡単なのだが、任意の文字列をコピーするとなるとどうすればいいのやら。適当にあちこちを検索してみると、どうやらOpenClipboardを使う方法がお手軽にできるようである。

(Visual C++)クリップボードに文字列をコピーする方法。

単純にクリップボードにコピーする関数を作ってみた。なぜかMFC専用である。といってもMFCを使っているのはCSharedFileの部分だけなのでここをGlobalAlloc等に置きかえればSDKでもOKである。
ちなみに、CSharedFileには<afxadv.h>にあるらしいので必要ならばincludeしよう。
-----
BOOL CopyToClipboard(LPCTSTR mes)
{
    if (!::OpenClipboard(NULL))return FALSE;
    if (!::EmptyClipboard())return FALSE;
    CSharedFile sfile;
    sfile.Write(mes, strlen(mes) + 1);
    HGLOBAL hdata = sfile.Detach();
    if (::SetClipboardData(CF_TEXT, hdata) == NULL)return FALSE;
    if (!::CloseClipboard())return FALSE;
    return TRUE;
}
-----
要は、Openして、Empty(空っぽ)にして、DataをSetして、Closeする、というだけの事である。というだけの事であるが、関数名を知らないと出来そうにないっすねヽ(´ー`)ノ。ちなみにこれは、エラーが起きたらFALSEを返すというスグレモノの関数である。
 

6/13

 今回紹介するのは謎のStringTableリソースIDR_MAINFRAME及びIDR_MFCMDITYPEである。\nが幾つか含まれているこの文字列。一体何を指定しているのだろうか?答えはずばり、プロジェクト開始時に設定する関連付ける拡張子やドキュメント名等である。つまり、プロジェクト開始時の設定で拡張子を関連付けするのを忘れた!といったときや、関連付ける拡張子を変えたい!と思ったときは、IDR_MAINFRAME等を変更すれば事足りてしまうのである。ちなみに私は昔はこういった時は最初からプロジェクトを作りなおしていた(笑)。今思うとアホのようである。

(Visual C++)IDR_MAINFRAMEの文字列の意味は?

 実は、MSDNでGetDocStringを見ると大体見当が付いてしまうのである。これは、\nで区切られた7つの文字列で構成されている。\n\nとあったら、間に空の文字列がある、という意味である。では、それぞれの文字列について解説していこう。
  1. アプリケーションのメインフレームのキャプション、つまりはアプリケーションのタイトルバーに表示される文字列である。最初の設定のままだと、タイトルバーは「ドキュメント名 - アプリケーション名」と表示される。このアプリケーション名が、この1番目の文字列である。

  2.  
  3. これは、ドキュメント名のデフォルト名である。つまり、新規ドキュメントを作成した時につけられる名前である。ここが空文字列だと、「無題」と名付けられるようだ。恐らく英語版VisualC++だと、Untitledなんだろう。

  4.  
  5. ドキュメントの型名。そのドキュメントがテキストを扱うならば「テキスト文章」といった感じの名前にすれば良いだろう。アプリケーションが複数のドキュメントを扱う時(つまり、複数のCDocumentクラスの派生クラスを使う場合)に必要となってくる。というかIDR_MAINFRAMEってドキュメントに関連する文字列リソースだったのか。これは驚き。アプリケーションに関連しているわけじゃないんだなぁ。複数のドキュメントを扱う時はもう一つIDR_MAINFRAMEにあたる文字列リソースを作るべきであるようだ。

  6.  
  7. メニューから「ファイルを開く」や「保存する」を選択した時に、ファイルの種類の蘭に表示される文字列。「テキスト文章 (*.txt)」みたい文字列がお勧めである。別に書式は決まってないので自由に書いて良い。「ビットマップファイル (*.bmp)」と書いておいて実はtxtで保存、なんて事も出来る。要は、この文字列はただ表示されるだけ、という事である。

  8.  
  9. 拡張子を指定する。「.txt」のようにピリオドを含めて書こう。「ファイルを開く」や「保存する」コマンドでのデフォルトの拡張子がここで指定したものとなる。

  10.  
  11. 先日の記事で紹介した、レジストリに拡張子を登録する際、レジストリで使うIDである。SetFileTypeの引数でいうならば、docnameである。半角で他のアプリケーションと重ならなければ、何でも良いだろう。

  12.  
  13. これもまた拡張子を登録する際に指定する文字列。エクスプローラー等でファイルの種類の蘭に表示される文字列を指定するので、しっかりと考えよう。SetFileTypeの引数で言えばdoctypeがこれにあたる。

  14.  
これで全部である。良く扱うのは1番目と2番目の文字列だろう。あとはドキュメントが扱う拡張子を変更したいときは4番目と5番目の文字列をいじれば良いだろう。
 

6/7

 さて、今回紹介するのは、エクスプローラー(Win98)で言う、表示->フォルダオプション->ファイルタイプで設定できる拡張子にアプリケーションを対応させる方法である。簡単なようでいて難しいこの方法。今回は、ダブルクリックするとお望みのアプリケーションを開く、という所まで紹介しよう。

(Visual C++)拡張子にアプリケーションを関連付けする方法。

 基本的にはレジストリを書きかえるだけである。MFCを使うと短く書けるので、CRegKeyを使って「ファイルタイプの設定」を行う関数を作ってみた。
-----
BOOL SetFileType(LPCTSTR ext, LPCTSTR docname, LPCTSTR doctype, LPCTSTR exepath)
{
    CRegKey reg;
    // ●extをdocnameに関連付ける●
    reg.SetValue(HKEY_CLASSES_ROOT, ext, docname);
    // ●docname作成●
    CString dname = docname;
    reg.SetValue(HKEY_CLASSES_ROOT, dname, doctype);
    reg.SetValue(HKEY_CLASSES_ROOT, dname + "\\shell", "open");
    reg.SetValue(HKEY_CLASSES_ROOT, dname + "\\shell\\open\\command", exepath);
    // ●関連付けが変更された事をシステムに通知●
    ::SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST, NULL, NULL);
    return TRUE;
}
-----
extは拡張子名(ピリオド含む)で、docnameは、レジストリで使うドキュメントの名前である。(半角のみ、特に何でも良いが、他の人が作ったソフトと重ならないようにする。)doctypeは、エクスプローラー等で表示される「ファイルの種類」を指定する。exepathは、実行ファイルのパスである。この関数のミソは、最後の::SHChangeNotifyである。これを呼ばないとアイコンが変更されないのだ。(ちなみにこの関数は、どこかにあるはずだーーと思ってIShellなんとか、Shellなんとか、SHなんとか、といったあたりをMSDNで探し回った結果見つけた。)

 この関数の使用例を幾つかあげてみる。

  1. -----

  2. SetFileType(".hoge", "Test.Hoge", "Hoge ドキュメント", "\"C:\\Windows\\notepad.exe\" \"%1\"");
    -----
    拡張子.hogeをメモ帳に関連付ける。Windowsディレクトリを勝手にC:\\Windowsとしているので、違う設定の人だと問題が発生する。真面目にやるときはGetWindowsDirectoryを使用する事。
     
  3. -----

  4. SetFileType(".hoge", "Test.Hoge", "Hoge ドキュメント", "\"D:\\Program Files\\Hidemaru\\Hidemaru.exe\" \"%1\"");
    -----
    拡張子.hogeを秀丸に関連付ける。私はDドライブのProgram Filesにインストールしたが、真面目にやる時はなんらかの方法で秀丸がインストールされたディレクトリを調べる事。
     
  5. -----

  6. SetFileType(".hoge", "Test.Hoge", "Hoge ドキュメント", "\"C:\\command.com\" /C del \"%1\"");
    -----
    拡張子.hogeを・・・・。やめましょうこれは。
exepathの値に注目しよう。実行ファイルは"(ダブルクォーテーション)で囲んだ方が良い。(実行ファイルのパスに空白が無いならダブルクォーテーションもいらないが)%1は、必ずダブルクォーテーションで囲もう。でないと、パスに空白が含まれたドキュメントを開いた時にみょーな事になる。



戻る
トップページへ
masato@mb.kcom.ne.jp