MASATOの開発日記 2000年4月


4/28

 今日もMFCのSDIについての話題です。最近こればっかですね。といってももうMFCを手放せない体質になっちゃいました。便利ですね〜これ。Win32ApplicationプロジェクトでもMFCを使いまくりです。Win32APIのラッパーにすぎなくても、変数定義の数を減らせたり、呼び出す関数の引数が少なかったりと、細かい所で便利なのが魅力です。
 さて、今日のお話は、メインフレームのキャプション、つまり、アプリケーションのタイトルバーの部分に表示される文字列の扱い方です。

メインフレームのキャプション(タイトルバー)に、ドキュメント名が入らないようにする方法。

 MFCのSDIが対象です。MFCのアプリケーションのタイトルバーは、ドキュメントを開くと、勝手に「ドキュメント名 - アプリケーション名」となりますが、それを自由に変えたいと思ったら、この方法を試してみましょう。結構簡単で、手順は以下の通りです。
  1. クラスウィザードより、CMainFramePreCreateWindowをオーバーライドします。(普通は、最初からオーバーライドされています。)
  2. // TODO: うんぬんかんぬん

  3. とある場所に、以下の1行を追加します。
    -----
    cs.style &= ~(FWS_PREFIXTITLE | FWS_ADDTOTITLE);
    -----
    これだけでタイトルバーからドキュメント名が消えうせます。つまり、「アプリケーション名」だけになる訳です。
  4. これはおまけですが、この後に、

  5. -----
    cs.lpszName = "お好きなタイトル名";
    -----
    とすれば、アプリケーション名も変えられます。
この先、ウィンドウを変更したければ、AfxGetMainWnd()->SetWindowText()を呼んでいくらでも変えられます。
応用としては、FWS_PREFIXTITLEだけ消すという方法があります。こうすると、「アプリケーション名 - ドキュメント名」となります。つまりさっきとは前後逆になる訳です。
 ちなみに、これでもタイトルが気に入らない!という方には、さらに奥深い方法があります。

メインフレームのキャプション(タイトルバー)のフォーマットを、自在に変える方法。

 この方法を使えば、タイトルは変えたい放題です。「ドキュメント名 + アプリケーション名」といった、-を+に変えることさえ出来ます。手順は次の通りです。
  1. CMainFrameに、以下の関数を追加します。

  2. -----
    virtual void OnUpdateFrameTitle(BOOL bAddToTitle);
    -----
    これが、フレームのタイトルが変更されるときに呼び出される関数のようです。
  3. 次は、もうあなたの思う通りにタイトルを変えて良いのですが、例として、-を+に変える関数のコードを載せておきます。

  4. -----
    void CMainFrame::OnUpdateFrameTitle(BOOL bAddToTitle)
    {
        CDocument* pDocument = GetActiveDocument();
        if (bAddToTitle && pDocument != NULL){
            CString title = pDocument->GetTitle();
            title += " + ";
            title += m_strTitle;
            SetWindowText(title);
        }else SetWindowText("");
    }
    -----
    m_strTitleは、CFrameWndprotectedメンバ変数で、そのフレームのタイトルが入ります。これは大概アプリケーション名と一致します。
これでめでたくフレームのタイトルは、「ドキュメント名 + アプリケーション名」となります。CDocument::SetTitleで変えるとちゃんとフレームのタイトルも変更されます。
(10/20) 間違えがあったので修正。
 

4/26

 だいぶ日が空いていたが、開発はサボっていたわけではない。日記を書くのをサボっていただけだ。・・・。すみません。威張れるような事じゃないですね。
 今日は、静的分割ウィンドウの使い方を書いてみます。SBookで使われている、左がCTreeViewで右がCListViewといった分割が行えます。2つじゃなくて、縦や横にいくつも分割出来るようです。最大の分割個数は、縦16横16が最高のようです。

静的分割ウィンドウの使い方。

 対象は、MFCのMDIではなくSDIとします。手順は以下の通りです。
  1. CMainFrameクラスに、CSplitterWnd型のメンバ変数を追加します。

  2. -----
    CSplitterWnd m_wndSplitter;
     
  3. クラスウィザードより、CMainFrameクラスのOnCreateClient関数をオーバーライドします。

  4. -----
    BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext)
    {
        m_wndSplitter.CreateStatic(this, 1, 2);
        m_wndSplitter.CreateView(0, 0, RUNTIME_CLASS(CxxxView1), CSize(100, 100), pContext);
        m_wndSplitter.CreateView(0, 1, RUNTIME_CLASS(CxxxView2), CSize(100, 100), pContext);
        return TRUE;
    }
    -----
    CreateStaticで分割する数や、Viewのクラス名、それぞれのViewのサイズは場合に応じて変えて下さい。それぞれのViewのヘッダファイルをインクルードしておかないとコンパイルエラーが出ると思います。
 これだけで完了です。簡単ですね(^^)v。それぞれのViewからGetDocument()しても、ちゃんとドキュメントクラスのポインタが返ってきます。こういったドキュメントとの関連がうまく付けられているのは、pContextのおかげのようです。ここにドキュメント情報等が入っているんでしょうね。

 ちなみに、このままだと、実行時カーソルを分割ウィンドウの分割部分に持って行くと、
Warning: Could not find splitter cursor - using system provided alternative.
というワーニングが出ます。分割部分では、マウスカーソルの形状が変わるのですが、その変えるべきカーソルが無いため、デフォルトのカーソルを使う、という意味のワーニングだと思います。気にすることは無いのですが、この警告を出したく無い方は、自分で、IDがAFX_IDC_HSPLITBARであるカーソルと、IDがAFX_IDC_HSPLITBARであるカーソルを作成しましょう。分割の方法によっては、どっちか片方のカーソルは要らないかもしれません。
 

4/16

 最近はいくつも平行して開発しているような気がする・・・。頭がこんがりそうです。今日の話題は、MFCで作るゲーム。それの、タイマー関係の処理について書きます。
 シューティングゲーム等、様々なゲームで、一定時間毎にある関数を実行する、という処理をします。例えば、一秒間に30回描画関数を実行すると言った事です。こういった処理を実現する為の簡単な方法としては、WM_TIMERを使用する方法は、非常に精度が悪いです。私のマシンでは、30ms毎にWM_TIMERを送ってもらうようにしても、55ms毎に送られてきます。60ms毎に送られてくるようにすると、110ms毎に送られてきます。つまりは、精度は55msという事になります。これでは、一秒間に18回しか呼び出せません。これではキャラクターを滑らかには動かせないでしょう。では、もっと精度の良いタイマーを使うにはどうすればよいのでしょうか。

MFCで、高い精度でタイマーを使う方法。

 以下は、timeGetTimeで時間を測り、指定した時間が過ぎたらWM_TIMERを送る、というプログラムです。これは高い精度でタイマーを扱う一つの方法です。他にもマルチメディアタイマーを使う方法等、色々な手法があります。わざわざTIMEPROCWM_TIMERを使わなくても良い、という意見もあると思いますが、これは一例という事で・・・。

 最初に、CxxxAppクラスにメンバ変数を追加します。
-----
protected:
    HWND m_hTimeWnd;
    TIMERPROC m_tpTimeProc;
    DWORD m_dwPeriodTime;
    DWORD m_dwPeriodPrevTime;
    BOOL m_bUsePeriod;
-----
 次は、CxxxAppにOnIdle関数をオーバーライドします。これはクラスウィザードから行えます。
-----
BOOL CxxxApp::OnIdle(LONG lCount)
{
    if (m_bUsePeriod){
        DWORD now = ::timeGetTime();
        if (now - m_dwPeriodPrevTime >= m_dwPeriodTime){
            m_dwPeriodPrevTime = now;
            if (m_tpTimeProc == NULL)::SendMessage(m_hTimeWnd, WM_TIMER, 0, (LPARAM)m_tpTimeProc);
            else m_tpTimeProc(m_hTimeWnd, WM_TIMER, 0, now);
        }
        return TRUE;
    }
    return CWinApp::OnIdle(lCount);
}
-----
 そして次に、CxxxAppに以下の2つのメンバ関数StartPeriod及びEndPeriodを追加します。
-----
void CxxxApp::StartPeriod(HWND hwnd, DWORD milisecond, TIMERPROC func)
{
    m_hTimeWnd = hwnd;
    m_dwPeriodTime = milisecond;
    m_dwPeriodPrevTime = ::timeGetTime();
    m_tpTimeProc = func;
    m_bUsePeriod = TRUE;
}

void CxxxApp::EndPeriod()
{
    m_bUsePeriod = FALSE;
}
-----
 これでCxxxAppクラスの変更は終わりです。
あとは、ウィンドウクラスから、
-----
((CxxxApp*)AfxGetApp())->StartPeriod(GetSafeHwnd(), 秒, NULL);
-----
とすれば、そのウィンドウクラスのOnTimerが指定した時間毎に呼び出されます。SetTimerと動作は似ています。
第3引数を、TIMEPROC型の関数にすれば、OnTimerは呼び出されずに、直接その関数が呼び出されます。
-----
((CxxxApp*)AfxGetApp())->EndPeriod();
-----
とすれば、タイマーは止まります。KillTimerのようなものです。

 注意点として、2回StartPeriodを呼び出すと、最初に呼んだ時の設定は無効となります。よってアプリケーション中で呼び出されるタイマールーチンは一つだけです。複数のタイマーを使いたい時は、このプログラムを改良するなりなんなりして下さい。
(5/8) 推敲。
 

4/13

 Windowsで簡単に文字列を入力/表示する方法として、エディットボックス(CEdit)を使う方法があります。しかし、これの背景色は常に白です。これを変えたいと思った時はどうすればいいのでしょうか。また、出力専門のエディットボックスという事で、読取専用にしたエディットボックスを使おうと思っても、これの背景は灰色。文字が白では読みにくいです。これの背景を白にするにはどうすればいいのでしょうか。その方法を書いてみます。

エディットボックスの背景色を変える方法。

 エディットボックスの背景色を変更するには、まずはエディットボックスがあるダイアログクラスに、CBrush型のメンバ変数を追加します。
-----
protected:
    CBrush m_bEditBack;
-----
 次に、初期化時(OnCreateやOnInitDialog等)でこのブラシハンドルを初期化します。
-----
m_bEditBack.CreateSolidBrush(RGB(赤, 緑, 青));
-----
 白にしたいのならば、RGB(255, 255, 255)とすれば良いです。
そして、クラスウィザードでOnCtlColorハンドラ関数を追加しましょう。そこには以下のように記述します。(エディットボックスのIDを仮にIDC_EDITとしておきます)
-----
HBRUSH CxxxDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
    if (pWnd->GetDlgCtrlID() == IDC_EDIT){
        LOGBRUSH brush;
        m_bEditBack.GetLogBrush(&brush);
        pDC->SetBkColor(brush.lbColor);
        return (HBRUSH)m_bEditBack;
    }
    return CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
}
-----
 これで、無事背景の色が変わります。
後はOnDestroyでこのブラシを解放しても良いですが、何もしないでデストラクタに任せて解放しても構いません。
  なお、時々OnCtlColorで、return ::CreateSolidBrush(RGB(赤, 緑, 青));としてあるのを見ますが、これはやめましょう。ここで作成されたブラシはどこでも削除されてません。何回も呼ばれているうちにリソースが尽きてしまいひどい目に会うでしょう。
(4/16) せっかくMFCを使っているので、CBrushを使ったプログラムに書きなおした。
(4/24) 変数名のミスを修正。

4/12

  今日は、非MFCなプロジェクトでMFCを使う方法をやってみます。Win32アプリケーションで開発をはじめたが、CStringを使いたい、CArrayを使いたい、CFileDialogを使いたい、と思った時。あるいはConsoleアプリケーションで開発をはじめた時。どうすれは使えるようになるか?実は結構簡単なのである。手順は以下の通り。

非MFCプロジェクトでMFCのクラスライブラリを使用する方法。

  1. プロジェクトの設定で、MFCのスタティックライブラリを使用、あるいは共有DLLを使用、のどちらかを選択する。これをしなと当たり前だがMFCライブラリは使えない。

  2. 本来ならば、#include <windows.h>が入るべき場所に、次の2行を記述しよう。MFCのヘッダファイルは内部でwindows.hをインクルードしているようなので、#include <windows.h>はいらない。
    -----
    #include <afx.h>
    #include <afxwin.h>
    -----
    そもそも#include <windows.h>はどこに入れるべきなの?という疑問を持っている人は、まずはMFCを使わないWindowsSDKでの開発方法を勉強する事をお勧めする。

  3. さらに、アプリケーション中でMFCの初期化を行う必要がある。WinMainの先頭あたりに、次の1行を入れておこう。これを入れないと、Afxなんとか関数が使えないのである。他にも使えない関数はあるかもしれない。
    -----
    AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0);
    -----
    本当にWinMainに書くならば、WinMainの引数をそのまま渡した方が良い。どこに書いても良いように、こんな呼び方にしてあるだけなので。
    ちなみに失敗すると0を返すらしいのでその場合は終了した方が良いかもしれないが終了しなくても何も問題が発生しないかもしれない。

 ちなみに、CStringを使うだけならば、最後のステップは必要無い。他にも最後のステップを必要としないクラスは色々あるだろう。けれど、念の為やっておいても悪くないのではないだろうか。1度しか呼び出さないし大して時間もかからないので。備え有れば憂い無し。(全然違うにゃ!)
(11/7) 結構使われそうなお話なので、分かりやすく書きなおす。

4/11

 新しいマシンを買ったので色々いじくっているうちに結構日付が経ってしまった・・・。
 今日は、チェックボックス付きのリスト特集という事でやってみます。チェックボックス付きのリストというのはどういうものかというと、VC++のメニューからビルド->バッチビルドと選択すると出てくるダイアログを見ると分かると思います。リストの一行一行にチェックボックスがついているのです。 一体こういったチェックボックス付きのリストはどうやって作るのでしょうか?

チェックボックス付きのCListCtrlの使い方。

 まずは、ダイアログ等で使用するリストビューにチェックボックスをつける方法を書きます。
InitDialog等、初期化時に、CListCtrl::SetExtendedStyle(LVS_EX_CHECKBOXES)を呼び出せばOKです。
例えば、リストビューのIDがIDC_LIST_VIEWの場合は、
-----
((CListCtrl*)GetDlgItem(IDC_LIST_VIEW))->SetExtendedStyle(LVS_EX_CHECKBOXES);
-----
です。チェック状態の取得、設定は、CListCtrl::GetCheck()及びCListCtrl::SetCheck()を用いて行えます。

チェックボックス付きのCListBox(CCheckListBox)の使い方。

 お次は、リストボックスにチェックボックスをつける方法を書きます。
この方法は、CListCtrlにチェックボックスをつけるよりもチェックボックスが大きなです。^^
まずはダイアログのリストボックスのプロパティを変えます。
プロパティのスタイルのオーナー描画を固定とし、文字列ありにチェックを付けます。
そして、そのダイアログにCCheckListBoxのメンバ変数を追加します。
-----
protected:
    CCheckListBox m_clbList;
-----
 次は、リストボックス宛に送られてきたメッセージをこのm_clbListに送るように設定します。
リストボックスのIDを、IDC_LIST_BOXとしますと、InitDialogあたりに次のように記述します。
-----
m_clbList.SubclassDlgItem(IDC_LIST_BOX, this);
-----
 これでばっちりチェックボックス付のリストボックスとなるはずです。

 チェックボックス付CListCtrlは、元はCListCtrlなので、複数のサブアイテムを表示したりとできます。それに対し、CCheckListBoxは、チェックボックス一つあたり、文字列は一つだけですが、チェックボックスの一つだけを灰色表示にしたりと、細かい小細工が出来ます。状況に応じて使い分けて下さい。
 

4/3

 本日はSBookの改良を色々と行っていた。ありそうでなかった機能を付けたり、バグの解消をしたりして、多少はまともになった感じだ。
 OnKeyDownで受け取るショートカットキーを色々と作っていた所、Ctrl+Returnが押された事をOnKeyDownで判定する必要になったので、Ctrlキーが押されているかどうかを調べる方法を探してみた。

CtrlキーやShiftキーの押化状態を調べる方法。

 使用するAPIはGetKeyState。使い方は以下の通り。
-----
BOOL bctrl = (GetKeyState(VK_CONTROL) & 0x8000) == 0;
-----
 コントロールキーが押されていれば、bctrlはTRUEとなる。GetKeyStateの引数は、仮想キーコードなので、VK_SHIFTとすればシフトキー判定も出来る。他にも様々なキー判定が出来るだろう。
キーが押されていれば、GetKeyStateの最上位ビットが1となるので、この方法で判定できる。コントロールキー等の「トグル状態(押されたまんまの状態)」が無いキーも、実はトグル状態になる事があるようなのだ。よって、押されてなくても最下位ビットが1となる事もある。 最初はこれに気づかず、GetKeyState(VK_CONTROL)!=0としていたのだが、この方法ではコントロールを押してなくてもしっかり反応してしまった。



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