1,プログラミングのやり方 |
2,ポート入出力 |
3,ポップアップメニュー |
ここでは、プログラミングの方針について自作Soft「othello」を例として説明します。
VisualC++に限らずほかの言語でも同じなのですがアプリケーションを作ろうとしたら
どのようなものを作るかというのを明確に決める必要があります。
それが決まったら、Windowの具体的な形を決めます。
これは、SDI形式にするとかMenuの項目、ダイアログを使うか、
最大化、最小化ボタンを取り入れるか、Windowのサイズを可変にするか
外部にデータファイルをおくか、ツールバーを使うかというものです。
このように、最初はVisualなものから初めます。
ここまでできたら、早速AppWizardを使って「なにもしないプログラム」という
内容がないアプリを作ります。(あくまでもVisualにすすみます。)
そして、Menuを編集したり、著作権情報を記入します。
また、アイコン、ツールバーの絵などを描きます。
(このころが一番スムーズに進みます。)
次に、プログラムの流れを考え、それによって変数や関数を作ります。
このとき、ビュークラスにすべての関数、変数をおくという手もありますが、
多少、計算をするものはDocクラスにおいた方がいいと思います。
以上でアプリが完成するのですが、最後が一番メインで時間がかかります。
FreeSoftの「Othello」は単純明快で、自分が「黒」で相手(パソコン側)が白です。
自分がコマを打つと自動的にひっくり返ります。
すると相手は、全マスでそれぞれ何コマ返せるかを計算しもっとも多く返せる手を
打ちます。このため、このothelloは「初め強いが、後が弱い」という特徴を持ちました。
SDIかMDIかというのはOthelloゲームなのでSDIです。
Menuや著作権情報も設定しました。
ツールバーやタスクバーはないので編集の必要はありません。
一般にはViewクラスでユーザの操作を受けて、計算はDocクラスでやります。
Docクラスではコマの計算をやるので、コマの配置データ変数はDocにおきます。
ViewクラスのOnDraw関数で、現在のコマ状態が描画できるようします。
Mouseがクリックされたら、Viewクラスを中心に考えるとDocクラスの
・白を黒にひっくり返す関数 ・全マスでもっとも多く返るマスを探す関数 ・その場所に白コマを打って、白を黒にひっくり返す関数
以上を順次実行するだけです。 では、Docクラスを中心に考えると、各関数は
計算をして、コマの配置データ変数を操作したり、適当な戻り値を返します。
以上の操作が実現できるように各関数をプログラミングします。
しかし、これらを仕上げた後で、バグが発見されることが多いです。
そのときは、
1,どこが原因なのか 2,どうすればいいのか 3,2をしたことによって新たなバグが発生しないか
以上を考えます。2と3は繰り返し考えることになります。
先ほどのようにバグは結構発生してしまいます。
このバグが深刻だと、プログラムそのものを再構築しなければならなくなってしまいます。
そのバグを少なくするためにも以下の2つを完璧にする必要があります。
・変数 ・流れ
この2点は非常に重要で、これを完璧にする必要があります。
プログラミングで「壁にあたる」とき、たいていはこの2つによるものです。
変数は、その存在意義を明確にする必要があり、データを更新するときは
更新していいのかというのを注意する必要があります。
流れは、ある操作に対して、命令がどの順にに実行されていくのかというものです。
分岐コマンドで、変数の初期化を忘れたり、宣言が抜けてしまうというのは
よく起こります。だから、常にあらゆる分岐を考える必要があります。
コンピュータを使って外部のデジタル機器を操作するときはIOポートを操作する必要があります。
IOポートとはコンピュータと拡張ボードをつなぐようなものです。
ここでは簡単のため、PC98用のIOインターフェイスボードを使って説明します。
このボードは「××番地に○○を出力しなさい。」という命令を出すと
その通りの出力をします。○○は0〜255までの数です。
例えば○○が32のとき、32は2進法で00100000なので
8本の出力のうち6番目の線だけが1(電圧では5V)を出力するというものです。
これで外部の電子機器が操作でき、著者も天体望遠鏡の自動制御にこれを用いています。
逆に外部のデータを入力することができます。
このように研究する上では結構重宝するボードです。
(但し、特殊なボードなので一般にはPC98用のものを買う方法しかありません。)
さて、IOポートに出力するという動作ですがDOSの時代ではアプリケーションが
コンピュータを直接操作していたので簡単でした。
n88basicでは、OUT,INP命令でできました。
ところがWindowの時代ではOSがアプリを実行するので直接ポートは操作できません。
Windowが間に入って操作することになります。
具体的な操作は次のようにします。ポートへのデータ入出力は
int _inp ( unsigned short port ); int _outp( unsigned short port, int databyte );
を使います。ここで、portは、ポート番号(0〜65535)で、
databyteは符号なしの8bitのデータです。
これだけでは動作せず
#include <conio.h> #pragma intrinsic(_inp)
というのを関数の前に置きます。intrinsic関数内の_inpで、_inp関数が
使えるようになります。
ポップアップメニューは右Clickで現れるメニューで、主に設定をするのに用います。
これは、Windowsアプリケーションではよく用いられます。
ここでは右Clickするとメニューが現れ、描画する図形を指定すると
右Clickした場所に図形を書くようなものを作ります。さらに
実用的なものにするために、Windowの大きさを論理座標上で1000×750にします。
では、実際にポップアップメニューを作ってみましょう。
まず、コンポーネントギャラリーを開きます。
その中から「ポップアップメニュー」を選択し、「挿入」を押します。
するとどのクラスに追加するか聞いてくるので、ここではViewクラスに追加します。
また、このときのメニューのリソースはIDR_POPUP_NAMAE_VIEWという名前で
用意されます。(普段使っているメニューにもリソースIDがあります。)
すると、下の方に次のような関数が追加されます。
void CNamaeView::OnContextMenu(CWnd *pWnd, CPoint point){ UNUSED_ALWAYS (pWnd); CMenu menu; VERIFY(menu.LoadMenu(IDR_POPUP_NAMAE_VIEW)); CMenu* pPopup = menu.GetSubMenu(0); ASSERT(pPopup != NULL); if (pPopup != NULL) pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this); } BOOL CNamaeView::PreTranslateMessage(MSG* pMsg){ // Shift + F10 でポップアップ メニューを表示します。 ( Word、Excel と同様) if ((((pMsg->message == WM_KEYDOWN || pMsg->message == WM_SYSKEYDOWN) & (pMsg->wParam == VK_F10) && (GetKeyState(VK_SHIFT) & ~1)) != 0) || (pMsg->message == WM_CONTEXTMENU)) { CRect rect; GetClientRect(rect); ClientToScreen(rect); CPoint point = rect.TopLeft(); point.Offset(5, 5); OnContextMenu(NULL, point); return TRUE; } return CView::PreTranslateMessage(pMsg);}
1番目の関数はメニューオブジェクトを作って、ポップアップメニューを
作る関数に渡しています。
2番目の関数は、WordやExcelのように、Shift+F10でも
ポップアップメニューを作れるようにしています。(なくても可)
これで、コンパイル−実行すると、確かにポップアップメニューができます。
しかし、メニューの項目は選択不可能になっています。
これは、そのIDに対する関数が作られていないからです。
ここでは、メニューの項目を「点」「円」にしました。
(ふつうのメニューを同じようにリソースViewをみると
普通のメニューリソース以外にポップアップメニューのリソースがあります。)
さらに、メッセージハンドラも作成します。
void CNamaeView::Onpie() { mode=1; InvalidateRect(NULL,TRUE);} void CNamaeView::Onpixel() { mode=2; InvalidateRect(NULL,TRUE);}
この時点で実行すると次のようになります。
ただし、ローカルにint mode,CPoint mouseを宣言してください。
次に、OnDraw関数を次のようにします。
(これは、ポップアップメニュー制作とは何の関係もありません。
ここでは、論理座標を説明するために次のようなものを作りました。)
void CNamaeView::OnDraw(CDC* pDC) { CNamaeDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); CRect r; int i; GetClientRect(&r); pDC->SetMapMode(MM_ANISOTROPIC); pDC->SetWindowExt(1000,750); pDC->SetViewportExt(r.right,r.bottom); for (i=0; i<1000; i+=100){ pDC->MoveTo(i,0);pDC->LineTo(i,750);} for (i=0; i<750; i+=100){ pDC->MoveTo(0,i);pDC->LineTo(1000,i);} }
これでWindowは常に(0,0)〜(1000,750)の論理座標を移すようになります。
Windowのサイズを変更しても画面全体が縮小するようになります。
ポップアップメニューを表示したとき、Clickされた場所がスクリーン座標で渡されます。
これを次のようにして論理座標に変換します。
void CNamaeView::OnContextMenu(CWnd *pWnd, CPoint point){ UNUSED_ALWAYS (pWnd); CMenu menu; VERIFY(menu.LoadMenu(IDR_POPUP_NAMAE_VIEW)); CMenu* pPopup = menu.GetSubMenu(0); ASSERT(pPopup != NULL); mouse=point; ScreenToClient(&mouse); CRect r;GetClientRect(&r); mouse.x=1000*mouse.x/r.right; mouse.y=750*mouse.y/r.bottom; if (pPopup != NULL) pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this); }
ただし、CPoint mouseをクラス内に対して宣言しておきます。
ScreenToClientは、スクリーン座標をクライアント座標(Windowの左上を原点)
に変換します。次に、GetClientRectでWindowの大きさを得て
クライアント座標から論理座標に変換しています。
Ondraw関数も次のように変更します。
void CNamaeView::OnDraw(CDC* pDC) { CNamaeDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); CRect r; int i; GetClientRect(&r); pDC->SetMapMode(MM_ANISOTROPIC); pDC->SetWindowExt(1000,750); pDC->SetViewportExt(r.right,r.bottom); for (i=0; i<1000; i+=100){ pDC->MoveTo(i,0);pDC->LineTo(i,750);} for (i=0; i<750; i+=100){ pDC->MoveTo(0,i);pDC->LineTo(1000,i);} switch (mode){ case 1: pDC->Ellipse(mouse.x,mouse.y,mouse.x+10,mouse.y+10); break; case 2: pDC->SetPixel(mouse,RGB(0,0,0)); break; } }
このように大変面倒になってしまったのでもう一度整理をします。
ポップアップメニューで一番重要なのはOnContextMenu関数です。
右Clickされると、その座標が論理座標に変換されてmouseオブジェクトに格納されます。
さらに、メニューを選択すると選択されたメニューの番号がmodeに格納されます。
そこからOnDrawを呼び出し、論理座標上に描画するというわけです。
C++のPageに戻る