MASATOの開発日記 2000年3月


3/29

やたらと高性能なフリーソフトのバックアップツール「みやばっく」を発見。 バックアップしない拡張子等を設定できるので、pchやpdbやobj等余計なファイルを 除いて簡単にバックアップできるようになった。こりゃ便利。かゆい所に手が届く大量のオプション設置もイカス。 これでソースファイルがだいぶ安全になった。

SBookは、このホームページで公開し、Vectorにも登録した。 あとは今後自分でも使っていって、使い難い所を直して行くつもりだ。 けれど、とりあえずこれで一段落だろう。開発期間は一ヶ月半だった。

MFCでゲームを作ろう!

SBookの次は何を作ろうかな・・・。このおかげでだいぶMFCに慣れたので、 次はMFCを使ってゲームを作ってみる事にしました。なんて無謀な・・・。 というのもMFCはゲーム向きのライブラリでは無いのです。 適当にMFCプロジェクトを作って実行してみれば分かると思いますが、 どーみてもツール向けのライブラリです。しかし、それでもゲームを作る事によって 学ぶ事は一杯あるだろうと思います。・・・きっと。

クライアントサイズを指定したウィンドウを作る。

ゲームでは、「クライアントサイズ」はとても重要です。 クライアントサイズとは、ウィンドウのうち描画できる部分です。 これに枠の幅やタイトル(キャプション)の高さやメニューの高さ等を加えたものが ウィンドウサイズとなります。
けれども、ウィンドウを作成する時に指定できるのは、このウィンドウサイズだけなのです。 640x480の表示領域を確保したい!と思っても、直接クライアント領域を指定できません。 ではどうするのか?そこで活躍するのがAdjustWindowRectExです。 これは、クライアントサイズとウィンドウスタイルから指定するべきウィンドウサイズを 計算してくれるAPIなのです。
SDIアプリケーションでの、Viewのクライアントサイズを指定する方法を 以下に示します。MDIでは通用しないらしいので注意。 また、クライアントサイズを指定しても、枠を引っ張ってウィンドウサイズが 変更出来てしまったり、最大化ボタンで大きくなってしまったらあまり意味が無いので、 これらが出来ないようにもしました。
CMainFramePreCreateWindowを、 以下のように変更します。これで640x480のクライアントサイズを持ったViewが 作成されます。
-----
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
    if( !CFrameWnd::PreCreateWindow(cs) )
        return FALSE;
    cs.style = WS_OVERLAPPED | WS_MINIMIZEBOX | WS_CAPTION | WS_SYSMENU;
    RECT r = {0, 0, 640, 480};
    ::AdjustWindowRectEx(&r, cs.style, cs.hMenu != NULL, cs.dwExStyle);
    cs.cx = r.right - r.left;
    cs.cy = r.bottom - r.top;
    return TRUE;
}
-----
なお、ステータスバー及びツールバーは使ってません。 これらを使った時はまた別の方法で計算する必要があるようです。

(2000/7/29) 推敲&MDIについて追記。

3/27

ブックマーク編集ツールSBookは、ヘルプ用HTMLファイルや、readme.txtを書いてもうじき公開。 某S氏より、IE等を開いているとSBookのウィンドウが隠れて、 ドラッグ&ドロップが行えないとゆーことで、ドロップ用の小さな最上位ウィンドウを 表示できるようにする。このウィンドウは、モードレスダイアログとして実装する事にしたが、 どうやって最上位ウィンドウにするか?という問題が発生した。

モードレスダイアログを最上位ウィンドウにする方法。

ダイアログのプロパティには、残念ながら「最上位ウィンドウ」というチェックは無い。 PreCreateWindowで、cs.dwExStyle |= WS_EX_TOPMOST;としてみたが、 そもそもPreCreateWindow自体が呼ばれて無いようだ。 OnInitDialogModifyStyleExを使ってみたが、 これも最上位ウィンドウにはならなかった。そこで使ったのがSetWindowPos。 これをOnInitDialogに書く事によって簡単に最上位ウィンドウにする事が出来た。ソースは以下の通り。
-----
SetWindowPos(&CWnd::wndTopMost, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
-----
ウィンドウの場所や大きさも一緒に設定したいならば、SWP_NOMOVEフラグ等を外し、 場所や大きさを指定すれば良い。SBookでは、前回ウィンドウを閉じた位置を記録しておき、 その場所にウィンドウを置くようにしている。

(2001/01/07) モーダルダイアログとモードレスダイアログを勘違いしていたことを発見したので修正。

3/22

 ブックマーク編集ツールは、細かい操作(右クリックメニューでしか出来なかった事をWindowのメニューから出来るようにした)等を固めて、公開まで後一歩の所まで来ました。CSplitterWndを使って複数のViewを表示しているのだが、このViewがTABキーで移動できれば便利だと思い、その操作を実現してみた。関数一発で出来そうな気もするが、探してみたが見つからなかった。(どなたか知っていたら教えて下さい。)よって自分で作る事にした。

分割ウィンドウのアクティブなビューの移動方法。

 要は、次のViewへ移動する、という方法である。色々な方法があると思うが、以下の方法で実現した。なお、これはSDI限定である。MDIの場合は少し変えなければ動かないだろう。
-----
void CxxxDoc::OnViewMove()
{
    POSITION pos = GetFirstViewPosition();
    CView* active = ((CMainFrame*)AfxGetMainWnd())->GetActiveView();
    BOOL act = FALSE;
    CView* next = NULL;
    if (pos != NULL){
        CView* first = GetNextView(pos);
        if (first == active)act = TRUE;
        while (pos != NULL){
            CView* pview = GetNextView(pos);
            if (act){
                next = pview;
                break;
            }
            if (pview == active)act = TRUE;
        }
        if (next == NULL)next = first;
        ((CMainFrame*)AfxGetMainWnd())->SetActiveView(next);
    }
}
-----
と、ドキュメントの関数として実現した。あとはビュークラスから、GetDocument()->OnViewMove();で移動できる。
リストビューでは、LVN_KEYDOWNを受け取ってTABを判別したあとこの関数を呼び出せばすんなりと移動出来たが、ツリービューのTVN_KEYDOWNで同じようにした時に問題が生じた。それはずばり、ビープ音が鳴るのである。ビューの移動自体は問題なく出来るのだが、TABを押すたびにぴっぴっぴぴとうるさい。
(4/3)
 もっと簡単な方法で実現する事が出来た。F6を押した場合、MFCの機能ですぱすぱViewを移動してくれたのだ。この機能がTABを押したとき実現出来ればよい。コードは以下の通り。
-----
OnNextPaneCmd(ID_NEXT_PANE);
-----
 一つ前のViewに戻るならば、ID_PREV_PANEを使えば良い。
 

ツリービューでキーを押した時にビープ音がなる問題の解決方法。

 これは半分予想だが、カーソルキーやスクロールキー等、ツリービューが使うキー以外を押した時に、そのキーは使われておりません!という意味でぴっと鳴るのである。ありがたい時もあるのかもしれないが、私はTABキーを使いたいのである。色々調べた結果、どうやらWM_CHARを受け取った時に鳴っているようだ。よって解決方法は、WM_CHARメッセージハンドラで、ビープ音を鳴らしたく無いキーが押された時は、デフォルトのメッセージハンドラを呼び出さない。という事であろう。コードは簡単なので書かない。TABキーの場合は、キーコードがVK_TABならば、そのままreturnするだけだ。

 ちなみに、なぜアクセラレーターを使わずに、KEYDOWNを受け取った時に操作しているのかというと、ラベルをインプレイス(InPlace、その場所で)編集しているときにも反応してしまうからだ。現在、エクスプローラーでバックスペースを押したときに親フォルダに戻るような操作ができるようになっているのだが、ラベル編集中にバックスペースを押すと親フォルダに戻ってしまうのだ。これはとことん不便だったので、KEYDOWNを使った方法を取った。
 

3/20

 やはり日記形式だと書きやすい。ホントに徒然なるままにって感じでしょうか。見る方から見たら見にくいことこの上ないっすね。そのうち目次を作る予定です。
 今日は、例のブックマーク整理ツールで、NetScapeやIEからブックマークの内容をインポートできるようにしようと思って色々調べてました。NetScapeの方は、ユーザープロファイルフォルダの場所をどうやって取得するか分からず(レジストリを検索しても無いのだ!)インストールされたフォルダから、Usersフォルダを求めるようにした。Usersを標準とは別の所に作っている人はちょこっと不便するかもしれない。すまん。
 NetScapeはブックマークの読み込み自体は簡単である。なにしろhtmlになっているのだから。これを開き、<A HREF="等で囲まれた場所を調べて、Netscapeのブックマークのインポートは無事できるようになった。次はIEのブックマークの読み取りである。普通、IEのブックマークはWindowsフォルダ以下のFavoritesフォルダに入っているようだが、ここ以外からもインポート出来るようにしたいので、入力フォルダを指定できるようにした。これが今回のお話である。

特殊なフォルダを求めるのはどうしたら良いか?

 このFavoritesフォルダは、特殊なフォルダである。デスクトップフォルダも同様である。単純にGetWindowsDirectoryでWindowフォルダを求め、それにFavoritesを足せば良い、という物では無い。いや大半の人はこれでうまく行くだろうが。
 一体なにが特殊なのか?というと、私も全て知っている訳では無いが、まずフォルダの名前を変えてみよう。IEのブックマークは普通に付いてくる。デスクトップフォルダの名前を変えても、デスクトップが無くなったりしない。結構とんでもないフォルダである。こういったフォルダのパスは次の方法で求まる。
-----
char path[MAX_PATH];
::SHGetSpecialFolderPath(AfxGetMainWnd()->GetSafeHwnd(), path, CSIDL_FAVORITES, FALSE);
-----
なんでウィンドウハンドルを必要とするのかは謎だが、とりあえずこれでFavoritesフォルダの場所がpathに入る。デスクトップのフォルダが知りたければ、第三引数をCSIDL_DESKTOPとしよう。他にもMSDNを見れば様々な特殊フォルダがある。ちなみに、第四引数をTRUEにすると、フォルダがなかったら作ってくれるそうだ。
 

ディレクトリ(フォルダ)指定ダイアログを開くにはどうしたら良いか?

 フォルダを求めたのは良いが、それをうまく使って、フォルダを指定するダイアログを開くにはどうしたらよいのだろうか?これが意外と難しい。というか実はSHGetSpecialFolderPathは使わなかった。^^
 以下に、フォルダ指定のダイアログを開き、かつ最初に開いている場所がFavoritesフォルダであるプログラムを書く。
-----
BROWSEINFO bi;
TCHAR dirbuf[MAX_PATH];
LPMALLOC lpmalloc;
CString folder;

// ●IMallocオブジェクトを取得●
if (::SHGetMalloc(&lpmalloc) == NOERROR){

    // ●lpinitに、FavoritesフォルダのIDを取得●
    LPITEMIDLIST lpinit;
    ::SHGetSpecialFolderLocation(AfxGetMainWnd()->GetSafeHwnd(), CSIDL_FAVORITES ,&lpinit);

    bi.hwndOwner = AfxGetMainWnd()->GetSafeHwnd();
    bi.pidlRoot = NULL;
    bi.pszDisplayName = NULL;
    bi.lpszTitle = _T("タイトル");
    bi.ulFlags = BIF_RETURNONLYFSDIRS;
    bi.lpfn = BrowseCallbackProc;
    bi.lParam = (LPARAM)lpinit;
    LPITEMIDLIST pidl = ::SHBrowseForFolder(&bi);
    // ●lpinitはもう使わないのでここで解放●
    lpmalloc->Free(lpinit);

    if (pidl != NULL){
        // ●フォルダ取得●
        if (::SHGetPathFromIDList(pidl,dirbuf)){
            folder = dirbuf;
        }
        lpmalloc->Free(pidl);
    }
    // ●IMallocオブジェクトを解放●
    lpmalloc->Release();
}

if (!folder.IsEmpty()){
    // ●フォルダ取得。さあ解析だ!●
}else {
    // ●フォルダ取得失敗(恐らくはキャンセルを押した)●
}
-----
これには、コールバック関数が必要で、以下のようなstaticまたはグローバルな関数を用意しなければならない。
-----
int CALLBACK BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData)
{
    switch (uMsg){
    case BFFM_INITIALIZED:
        // ●初期化。Favoritesフォルダを開く●
        ::SendMessage(hwnd, BFFM_SETSELECTION, FALSE, lpData);
        break;
    }
    return 0;
}
-----
これでばっちりだ。(たぶん)
ヘッダファイルが必要ならば、shlobj.hをインクルードすれば良い。
 ちなみに、bi.pidlRootに、FavoritesフォルダのIDを指定すると、確かに最初にFavoritesが開いているのだが、Fovoritesがルートとなっているため、それ以上上のフォルダを参照できない。つまりFavorites以下のフォルダしか指定できなくなってしまう。別にそれで構わないなら良いのだが、どのフォルダも指定できるようにした上で、最初にFavoritesを開いていてほしかったので上記のようになった。
(4/3)  ヘッダファイルについて追記。

3/19

 今日はShell系関数について少し書いてみます。WindowsでShellといったら・・・なんなんでしょうね。まあ、エクスプローラーが出来るような事を実現できます。

任意のファイル(データーファイル含む)を実行。

 エクスプローラーで言えば、ダブルクリックした時の動作です。実行ファイルならば実行しますし、データーファイルならば関連付けされた実行ファイルを実行します。下の方でURLからブラウザを開いているのもその応用です。
-----
ShellExecute(AfxGetMainWnd()->GetSafeHwnd(), "open", "開きたいファイル名", NULL, NULL, SW_SHOW);
-----
これでOKです。

任意のファイルのアイコンの取得方法。

 これもやっぱりエクスプローラーが普通に使ってますね。あのファイルに対応するアイコンはどうやって表示するのでしょうか?調べてみたところSHGetFileInfoを使うようです。
-----
SHFILEINFO shfi;
::SHGetFileInfo(filename, 0, &shfi, sizeof(shfi), SHGFI_ICON);
// ●これでshfi.hIconにアイコンハンドルが入ります●
-----
アイコンハンドルを求めるだけならこれだけで出来ます。他にも、SHGetFileInfoの戻り値は、システムイメージリスト(ってなんだろ)らしいですし、shfi.iIconは、そのイメージリストのインデクッスのようです。詳しくは調べてませんが他にも色々と出来そうですね。
 ちなみにSHGetFileInfoはかなーり多機能です。アイコンを求めるなんていうのはほんの1機能のようです。
 

EXEファイルのアイコンはどれになるの?

 と、アイコンを取得する上で疑問に思ったのがこれ。MFCだったらIDR_MAINFRAMEのようだが、WindowsSDKでは?Windowsはどのような法則で実行ファイルのアイコンを取得しているのか?と色々調べて見たところ、どうやら、EXEファイル内のアイコンリソースの一番先頭のようだ。どれが先頭になるかというのは、以下の条件で決まる。
  1. 文字列でアイコンIDを指定してある場合、辞書順で文字列が一番小さなアイコンが先頭。
  2. 文字列指定のアイコンIDが無い場合、数値が一番小さなアイコンが先頭。数値はresource.hかシンボルブラウザで書き換えられます。
 ホントにこの通りにアイコンが並ぶかは不明だが、少なくとも私がリソースファイルを編集しまくって何種類か調べた所これを否定する結果は出なかった。だからMFCでもやりようによってはIDR_MAINFRAME以外のアイコンを実行ファイルのアイコンと出来るのだ。というか不用意にアイコンIDを文字列にすると勝手にすりかわってしまうので注意。
 Windowを開いた時、キャプションの左に表示されるアイコンは、また別の方法で指定する。この方法は、あくまでエクスプローラー等から見た実行ファイルのアイコンについてだけである。
(3/29) 微細変更。
 

3/15

 ブックマーク整理ツールは、現在、ブックマークをリンク集のhtmlファイルとして出力する為の部分を組んでます。このシステムは、アドインとして誰でも後から作れるようにします。リンク集のフォーマットなんてホントに星の数ほどあるでしょうし。
 さて、私用のリンク集作成アドインで、textやbgcolorの色を指定出来るようにしようと思ったらこれがまた手間かかる事。RGB値をそれぞれスライダーバーで選択でき、またブラウザのデフォルトの値も使用可能、さらにはそれをiniファイルに保存する、というようにしましたが、K氏から提供されたサンプルがあってさえ結構時間かかりました。小さなダイアログでも手間かかるもんですね〜。Wordのツールのオプションのような色々コントロールが載っているダイアログってすごいな〜と思います。

リストビューコントロールのアイテムに選択マークを付ける方法。

 リストビューを扱っていて、最初に一番先頭のアイテムが選択されていると操作しやすそうだなと思って、SetSelectionMarkを使って選択させようとしたのですが、何故か選択できない。なんでだろ・・・。というわけで別の方法を探してみました。どうやらSetItemStateで出来るようです。
-----
SetItemState(index, LVIS_FOCUSED | LVIS_SELECTED, LVIS_FOCUSED | LVIS_SELECTED);
-----
これでばっちり。indexが指すアイテムが選択されました。
LVIS_FOCUSEDがいらなければ外しても良いでしょう。

リストビューコントロールのアイテムの選択マークを外す方法。

 上とは逆に、選択マークを消す方法です。
-----
SetItemState(index, 0, LVIS_FOCUSED | LVIS_SELECTED);
-----
これでindexで指定したアイテムの選択マークが消えます。

(5/25) 選択マークを外す方法について追記。

3/11

 DPS_RPGをちょっと改良してみましたが、でかいプログラムになるとわずかな変更も難しいです。変更にかかる時間のほとんどが、過去のプログラムを見直している、という感じです。以前学校で行った授業では、C++だとコーディング、動作チェックをしっかりと行った場合、165行あたり一ヶ月かかる、という計算方法がありました。その時はいくらなんでも一ヶ月に165行は無いだろう、そのくらいは一日もかからずに書けるぞ、と思っていたのですが、ここまででかいシステムになると、変更点について、既存のプログラムと合わせてあらゆるパターンの動作チェックを行えば、すごい時間がかかります。一ヶ月165行という数字も現実味を帯びてきました。

MFCで、レジストリの代わりにiniファイルを使用する方法。

 それはそれとして、ブックマーク整理ツールの方ですが、起動時に開くファイルを選択できるようにするため、その情報をレジストリかどこかに保存したいと思いました。が、システムを書き換える、というのが実は気に入らないので、実行ファイルがあるディレクトリにiniファイルを置いて、そこに各種データーを全部保存しようと思いました。それ自体はWritePrivateProfileString()等でできるのですが、(ファイル名としてフルパスを指定すれば)もっと簡単にできないかと調べた所、どうやらMFCのCWinAppクラスのWriteProfileString()が使えるようです。ただ、そのまま使うとこれはレジストリに書いてしまうので、以下の変更が必要です。
  1. CxxxApp::InitInstance()中の、SetRegistryKey()呼び出しを削除(あるいはコメントアウト)します。これにより、レジストリではなくiniファイルに保存するようになります。
  2. しかし、このままでは、ウィンドウズディレクトリの中にiniファイルを作ってしまいます。

  3. そこで、iniファイルの場所をフルパスで指定します。SetRegistryKey()があった所に以下のソースを挿入します。
    -----
    // ●iniファイルに保存するようにする。●
    TCHAR path[MAX_PATH];
    TCHAR drive[_MAX_DRIVE];
    TCHAR dir[_MAX_DIR];
    ::GetModuleFileName(AfxGetInstanceHandle(), path, sizeof(path));
    _tsplitpath(path, drive, dir, NULL, NULL);
    _tmakepath(path, drive, dir, m_pszAppName, "ini");

    free((void*)m_pszProfileName);
    m_pszProfileName = _tcsdup(path);
    -----
    これで、ばっちり実行ファイルと同じ所にiniファイルを作ってくれます。MRUファイルリスト(最近開いたファイル)も、このiniファイルに保存してくれます。これでシステムに迷惑をかけずに済みますね!

(3/15)DLLでも使えるようにと、AfxGetInstanceHandleを使いました。
(2000/5/8) 使う必要のなかったfname及びextを削除しました。
(2000/5/14) 推敲。
(2001/1/7) 結構頻繁に使うコードなので、UNICODEにも対応できるよう書き直す。
 

3/10

ウィンドウにドロップされた色々なオブジェクトを取得する方法。

 ウィンドウには、ファイル以外にもテキストやURLへのショートカットをドロップする事が出来ます。では、ドロップされた情報をどうやって取得すれば良いのでしょうか? 実は意外と難しい。
 まずはCViewの派生クラスの場合。
  1. メンバ変数にCOleDropTargetクラスのオブジェクトを追加します。とりあえずこれをm_odtTargetとしておきます。
  2. CViewの初期化時に、

  3. m_odtTarget.Register(this);
    を実行しておきます。
  4. OnDropOnDragEnterOnDragOverOnDragLeaveをオーバーライドし、そこに処理を記述します。
 次は、CViewの派生クラスでない、CWndの派生クラスの場合。その時はこれではうまく行きません。OnDrop等は、CViewの仮想関数ですので、CWndからはオーバーライドできないのです。ドロップされた事を最初に察知するのは、COleDropTargetクラスです。そして、このクラスは、もしもRegisterされているのがCViewクラスならば、CView::OnDrop等を呼び出す、となっているのです。よって、COleDropTargetから派生させて、新しいクラスを作り、そこでOnDrop等をオーバーライドしなければなりません。
 OnDropOnDragEnter等でどんな記述をすれば良いのかはまた次回〜
 

3/7

 今回はドラッグ&ドロップをまとめてみます。下の方で色々やってましたが、そもそもどうやってドロップされたオブジェクトを取得するのかを書いていませんでしたね。

ウィンドウにドロップされたファイルを取得する方法。

 ファイルだけならば、話は簡単です。受け取りたいCWndクラス(あるいはその派生クラス、CViewあたりが適当かも。)の初期化時等に、
DragAcceptFiles(TRUE);
とします。これだけでファイルを受け取ってくれます。試しにエクスプローラーからドラッグ&ドロップしてみましょう。ドラッグしてウィンドウ内にカーソルが入ると、ちゃんとドロップ可能カーソルになりますね。
 では、ドロップされたデーターをどうやって取得すれば良いのでしょうか。ドロップされた時、どうやらWM_DROPFILESメッセージが送られてくるようです。クラスウィザードでこれに対応した関数を作りましょう。OnDropFilesという関数が出来るはずです。ドロップされたファイル名を解析する方法は以下の通り。
-----
// ●nにドロップされたファイル数が入ります。●
int n = ::DragQueryFile(hDropInfo, 0xffffffff, NULL, 0);
for (int i = 0; i < n; i++){
    char path[MAX_PATH];
    ::DragQueryFile(hDropInfo, i, path, sizeof(path));
    // ●pathにファイル名が入ります。●
    // ●ここでpathを使って色々と・・・●
}
::DragFinish(hDropInfo);
-----
 最後のDragFinishは無くても動くようですが、とりあえず入れておきます。これでファイルのドロップはばっちりです。ファイル以外のオブジェクトをドロップした時どうやって取得するかはまた次回〜ということで。
 

3/6

ウィンドウを作る前に画面のDCを取得する方法。

 ブックマークエディタとは関係無いけど、画面のDC(デバイスコンテキスト)を取得する方法をS氏に教えてもらった。CClientDCでいいじゃん、と思うかもしれないが、ウィンドウを作る前だったらどうだろうか?CreateCompatibleBitmap()CreateDIBitmap()を、プログラム開始時に呼びたいと思ったらどうすればいいのだろうか?つまり、他のDCを必要とせずに、標準のDCを取得する方法である。解答はずばりこれ。
::CreateCompatibleDC(NULL);
なんと引数にNULLがとれるのだ。だが、私が試した所、32bitカラー環境なのに、なぜか白黒のものが出来てしまった。代案はこれ。
::GetDC(::GetDesktopWindow());
つまりはデスクトップのウィンドウのDCを使わせてもらうのだ。ちなみにこのDCに対して描画関数を呼ぶとデスクトップに直接書いてしまうはず。注意しましょう。
(3/11)
メインウィンドウが作成された後ならば、
AfxGetMainWnd()->GetDC()->GetSafeHdc();
もいいかもしれませんね。使いおわったらReleaseDC();で適切に解放しましょう。

ビット列からビットマップを作成する方法。

 入力として、ビットマップのビット列が与えられた。幅も分かる。高さも分かる。パレットも分かった。ではこれからビットマップを作成して、そのハンドルを求めたい。どうすればいいのだろうか?と思って色々調べてみた。どうやらCreateDIBitmap()で行けるようだ。ソースリストはこんな感じ。
-----
int ncolor = パレット数;
void* lpbuffer = ビット列;
hdc = 画面のデバイスコンテキスト;
width = ビットマップの幅;
height = ビットマップの高さ;

BITMAPINFO* lpinfo = (BITMAPINFO*)new BYTE[sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * ncolor];
BITMAPINFOHEADER* lpinfoheader = &lpinfo->bmiHeader;

// ●BITMAPINFOHEADER初期化●
lpinfoheader->biSize = sizeof(*lpinfoheader);
lpinfoheader->biWidth = width;
lpinfoheader->biHeight = height;
lpinfoheader->biPlanes = 1;
lpinfoheader->biBitCount = 1ピクセルあたりのビット数;
lpinfoheader->biCompression = BI_RGB;
lpinfoheader->biSizeImage = 0;
lpinfoheader->biXPelsPerMeter = 0;
lpinfoheader->biYPelsPerMeter = 0;
lpinfoheader->biClrUsed = ncolor;
lpinfoheader->biClrImportant = ncolor;

for (int i = 0; i < ncolor; i++){
    lpinfo->bmiColors[i].rgbBlue = i番目のパレットの青色;
    lpinfo->bmiColors[i].rgbGreen = i番目のパレットの緑色;
    lpinfo->bmiColors[i].rgbRed = i番めのパレットの赤色;
    lpinfo->bmiColors[i].rgbReserved = 0;
}

hbitmap = ::CreateDIBitmap(hdc, &m_lpInfo->bmiHeader, CBM_INIT, lpbuffer, lpInfo, DIB_RGB_COLORS);
-----
ウィンドウを作る前にビットマップを作ろうと思ったら上記の方法でhdcを求めよう。ちなみに、ビット列の幅は、4バイト境界である。例えば256色ビットマップならば、1ピクセルあたりのビット数は8、つまり1バイト。横幅3ピクセルのビットマップならば、lpbufferには一行あたり4バイト確保する事。横幅が5ピクセルならば、一行あたり8バイトだ。
 モノクロビットマップで良いというならば、CreateBitmap()を使えば良い。なんかCreateBitmapでカラービットマップも作れそうだが、ヘルプには「それは止めた方がいいです。」と書いてある。ちなみにCreateBitmapのビット列は2バイト境界。CreateDIBitmap用のデーターをそのまま流用しようとすると思いっきり斜めに傾いたビットマップが出来る事がある。例を上げるならば、モノクロビットマップは1ピクセル1ビット。横幅が40ピクセルならば、40ビットすなわち5バイトになる。CreateDIBitmapで作るならば、1行あたり8バイト必要だが、CreateBitmapならば、一行あたり6バイトで済む。なんで統一されてないんだろう。不思議不思議。
 

3/4

 開発も中盤に差し掛かり、特に目新しい事もなくなってきた。方法の大体分かっている機能を実装しつつデバッグを何度も繰り返す。このあたりからアプリケーションは見た目が変わらなくなってくる。特に日記に書くべき目新しい事はほとんど無いのだが、CTreeCtrlにアイテムを挿入する方法についてちょっと書いておく。

ツリービューコントロールのリストの最後や最初、さらにはアルファベット順の場所に挿入する方法。

CTreeCtrlにアイテムを挿入するとき、どこに挿入するかを指定できるが、これは指定したアイテムの後に挿入される事になる。一番最後に挿入するのはTVI_LASTを指定すれば良いのだが、一番最初に挿入するにはどうするのだろうか。これは一見MSDNに載っていないのだ。けど良く見れば載っていたので、どうやら私の目が節穴だっただけのようだが。(結局K氏に教えてもらった) である。
 しかしツリービューコントロールには、挿入マークがあるのになぜリストビューコントロールには無いのだろうか?SetInsertMarkで指定するあれである。だれか簡単に実現できる方法があったならば教えて欲しい。
CTreeCtrl::SetDropTargetと同等の機能をCListCtrlに付けるのも結構大変だった。ツリーコントロールはまさにドロップされるためのコントロールという印象を受けた。
(3/29) 誤字脱字訂正。



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