プログラム講座 中級編17
- 状況に応じたメニューの作成 -
 中級編17です。今回は「状況に応じたメニューの作成」について勉強します。状況に応じたメニューの作成については、今回だけでなく別の機会にでも追加して解説しようかと考えています。
 今回は「オープンされているウィンドウ数に応じてメニュー項目が変化する」というものです。エディタや一部のアプリケーションで行われている処理を実現してみましょう。
◆ウィンドウの存在を示すリスト
 ウィンドウをオープンしたりクローズするといったウィンドウ関係の命令を以下に示します。
| 命  令 | 内  容 | 
| WINDOW ウィンドウ番号,ウィンドウ名 | ウィンドウを開く | 
| WINDOW CLOSE ウィンドウ番号 | ウィンドウを閉じる | 
| WINDOW OFF | 起動時にウィンドウを表示しない | 
| Num = WINDOW(_activeWnd) | アクティブウィンドウ番号を返す | 
| Num = WINDOW(_outputWnd) | 出力先のウィンドウ番号を返す | 
|  Wx = WINDOW(_width) | カレントウィンドウの横幅を返す | 
|  Wy = WINDOW(_height) | カレントウィンドウの縦幅を返す | 
 他にもいろいろありますが今回は上の4つを使用します。本当はこういう表が一覧でマニュアルに載っていれば良いのですがf(^^; やっぱりFuture BASICがメジャーになって売れないとマニュアルも刷新されないのでしょう。
 それは、さておきウィンドウを開いたり閉じたりするのは簡単ですが、どのウィンドウが開かれているのかを求める必要があります。確かウィンドウの構造体(レコード)だったかからもたどれると思いましたが、ここは簡単で無難な配列を使って、「ウィンドウが存在するかどうか」を格納しておくことにします。
 プログラムリスト中ではtheWindow(〜)が、ウィンドウがあるか、ないかを示しています。_trueの場合はウィンドが表示されており、_falseの場合は閉じられている事を表しています。
◆ウィンドウリストへの格納
 開かれるウィンドウの最大枚数はFB 1.0.xとFB IIでは異なります。FB 1.0.xでは最大63枚なのに対しFB IIでは255枚と大幅に増えています。が、あまり多くのウィンドウを使う事は意外と少ない気がします。今回はウィンドウは最大16枚開くことが出来るようにしました。
 ウィンドウを開く場合に、いきなりウィンドウ番号を指定して開くような事をしては駄目です。つまり
WINDOW #1,"Win"
 といった書き方では駄目です。ウィンドウを開く場合、最初に「ウィンドウリストの空き番号を検索する」必要があります。こうしないとすでに開かれているウィンドウを再度開いてしまいます(アクティブウィンドウになるだけですが)。まずは、空き番号を求める必要があり、これはFN getWindowNoという関数で処理をしています。単純に先頭からtheWindowで_falseになっているものがあるかどうか調べているだけです。FOR〜NEXTループから抜け出すためにi=99のように範囲外の数値を指定しています。こういう方法もあるという事で参考までにどうぞ。
 空き番号を求めたらtheWindowを_trueにして「ウィンドウが存在する」というフラグを立てておきます。あとはウィンドウをオープンすればできあがりです。プログラムではオープンされるウィンドウの座標を少しづつ右下にずれるようにしてあります。これは、おまけみたいなもので何回も開いていると、そのうちスクリーンからはみ出てしまいます(^^; SYSTEM(_scrnWidth), SYSTEM(_scrnHeight)でスクリーンサイズを求める事ができますので、はみだしそうになったら加算しないという処理をつけ加えてみるとよいでしょう。
◆メニューの構築
 今回のプログラムのポイントです。ウィンドウのオープンされている枚数に合わせて「ウィンドウ」メニューの項目を変化させなければなりません。例えば

 といった具合になります。Future BASICではメニューを作成する場合または更新する場合「MENU」命令を使います。この命令を使ってウィンドウが開かれたら、どんどん追加していけばできあがりです。
 ウィンドウを閉じた場合はメニューからウィンドウ名を削除しなければなりません。MENU命令で構築していくと以前作成された項目が残ったままになります。つまりメニューを構築し直しても以前作成されたメニュー項目は削除されず残ったままになります。
 という事は不要な項目を削除すればできあがりです。ところがFuture BASICにはメニュー項目を削除する命令がありません。Toolboxにはメニュー項目削除の呼び出しがあってFuture BASICからも使えますが、メニューハンドルを求めて項目番号を指定する必要があります。となると面倒な事になってしまいます。そこで、メニュー項目を削除するのではなく「ウィンドウメニュー全体を削除」するようにします。全体を削除して再構築し直せばプログラムも簡単にできます。
 メニューを削除するには
CALL DELETEMENU(メニュー番号)
 のようにします。ウィンドウメニューの番号(今回は2)を指定すればごっそり消してくれます。後は再構築するだけです。
◆どのウィンドウがメニューに登録されているか
 ウィンドウメニューから指定ウィンドウが選択されたら指定ウィンドウをアクティブ(最前面)にしなければなりません。選択されたメニュー番号から、どのウィンドウをアクティブにするか求める必要があります。単純にメニュー項目番号をウィンドウの番号と1対1で対応させる方法もあります。今回は真面目に動的に対応できるようにmenuList配列を作成し現在のウィンドウメニューに、どのウィンドウが登録されているかどうかを格納しています。
 この配列をキーにして以下のようにウィンドウをアクティブにしています。
  No = menuList(count)
  WINDOW No
 ウィンドウを単純にアクティブ(最前面)にするにはWINDOW ウィンドウ番号とするだけです。特にウィンドウ番号の先頭に#を付加しなくても大丈夫です。もちろん付けても動作します。
◆終わりに
 この講座では初めてマルチウィンドウ表示するプログラムになってしまいました(^^; 本当はエディタを作るという講座でやろうと思ったのですが。状況に応じてメニューを変化させるのであれば、今回の場合ウィンドウが開くことが出来ない場合は「ウィンドウを開く」というメニューは選択禁止にしなければいけません。同様にウィンドウが1枚も表示されていない場合、ウィンドウを閉じるメニューは選択禁止でなければなりません。ここらへんは勉強がてら挑戦してみると良いと思います。
 次こそは、エディタかな(笑)
◆今回のプログラムリスト
'
' "状況に応じたメニューの追加、削除"
'
OUTPUT FILE  "addMenu Sample"
' "----------------------- 定数の定義 ----------------------"
_winMax = 16:                                     ' "一度にオープンできるウィンドウの最大数"
_fileMenu = 1
_windowMenu = 2
_newWindows = 1
_closeWindows = 2
_quit = 3
gQuit_flag = _false:                              ' "プログラム終了フラグ"
gWinX = 0:                                        ' "ウィンドウの表示X座標"
gWinY = 40:                                       ' "ウィンドウの表示Y座標"
' "------------------ グローバル変数の定義 -----------------"
DIM theWindow(_winMax):                           ' "ウィンドウの存在の有無(0=なし、0以外=あり)"
DIM menuList(_winMax):                            ' "メニューバーに登録されているウィンドウの番号"
winNo = 0:                                        ' "オープンしているウィンドウの数"
END GLOBALS
'--------------------------------------------------------
' "ウィンドウの空き番号を求める"
'--------------------------------------------------------
CLEAR LOCAL
LOCAL FN getWindowNo
  No = 0
  FOR i = 1 TO _winMax
    IF theWindow(i) = _false THEN No = i:i = 99
  NEXT
END FN = No
'--------------------------------------------------------
' "ウィンドウを構築する"
'--------------------------------------------------------
CLEAR LOCAL
LOCAL FN makeWindow
  count = FN getWindowNo:                         ' "ウィンドウリストに空きがあるか調べる"
  IF count < 1 THEN EXIT FN:                      ' "すでに全部開かれてしまった場合は何もしない"
  theWindow(count) = _true:                       ' "ウィンドウの存在を設定"
  w$ = "Window No. "+STR$(count):                 ' "タイトル文字列を構築"
  WINDOW count,w$,(gWinX,gWinY)-(gWinX+320,gWinY+240)
  gWinX = gWinX + 4:                              ' "次に表示するウィンドウの座標を右下にずらす"
  gWinY = gWinY + 4
END FN
'--------------------------------------------------------
' "ウィンドウを閉じる"
'--------------------------------------------------------
CLEAR LOCAL
LOCAL FN closeWindow
  count = WINDOW(_activeWnd):                     ' "アクティブウィンドウを求める"
  IF count = 0 THEN EXIT FN:                      ' "0の場合はウィンドウはなし"
  theWindow(count) = _false:                      ' "ウィンドウを無にする"
  WINDOW CLOSE count:                             ' "ウィンドを閉じる"
END FN
'--------------------------------------------------------
' "選択されたウィンドウを前面にする"
'--------------------------------------------------------
CLEAR LOCAL
LOCAL FN toFront(count)
  No = menuList(count):                           ' "メニューリストからウィンドウ番号を求める"
  WINDOW No:                                      ' "ウィンドウを一番手前にする"
END FN
'--------------------------------------------------------
' "ウィンドウメニューを構築する"
'--------------------------------------------------------
CLEAR LOCAL
LOCAL FN makeMenu
  CALL DELETEMENU(_windowMenu):                   ' "ウィンドウメニュー自体を消去"
  MENU _windowMenu,0,_enable,"ウィンドウ":        ' "新しくウィンドウメニューを作成"
  
  menuNo = 1:                                     ' "登録するメニュー項目は1から"
  FOR i=1 TO _winMax:                             ' "最大ウィンドウ分繰り返す"
    LONG IF theWindow(i) = _true:                 ' "ウィンドウが存在する場合はメニューリストと項目を追加する"
      w$ = "Window No. "+STR$(i):                 ' "項目名を構築する"
      MENU _windowMenu,menuNo,_enable,w$:         ' "項目を追加"
      menuList(menuNo) = i:                       ' "メニューリストにウィンドウ番号を格納"
      menuNo = menuNo + 1
    END IF
  NEXT
END FN
'--------------------------------------------------------
' "メニューを構築する"
'--------------------------------------------------------
CLEAR LOCAL
LOCAL FN initMenu
  '"ファイルメニュー"
  MENU _fileMenu,0,_enable,"ファイル"
  MENU _fileMenu,_newWindows,_enable,"/Oウィンドウを開く"
  MENU _fileMenu,_closeWindows,_enable,"/Wウィンドウを閉じる"
  MENU _fileMenu,_quit,_enable,"/Q終 了"
  
  ' "ウィンドウ名をメニューに追加します"
  MENU _windowMenu,0,_enable,"ウィンドウ"
END FN
'---------------------------------------------
' "メニューの選択"
'---------------------------------------------
CLEAR LOCAL
LOCAL FN doMenus
  menuID = MENU(_menuID):                         '"選択されたメニューバー項目の番号"
  itemID = MENU(_itemID):                         '"プルダウンメニューで選択された項目番号"
  
  SELECT menuID
    CASE _fileMenu :                              ' "ファイルメニュー"
      SELECT itemID
        CASE _newWindows:                         ' "ウィンドウを開く"
          FN makeWindow:                          ' "ウィンドウを作成"
          FN makeMenu:                            ' "ウィンドウメニューを作成"
        CASE _closeWindows:                       ' "ウィンドウを閉じる"
          FN closeWindow:                         ' "ウィンドウを閉じる"
          FN makeMenu:                            ' "ウィンドウメニューを作成"
        CASE _quit:                               ' "終了が選択された"
          gQuit_flag = _true
      END SELECT
    CASE _windowMenu:                             ' "ウィンドウメニュー"
      IF itemID > 0 THEN FN toFront(itemID):      ' "選択されたウィンドウを手前にします"
  END SELECT
  MENU:                                           ' "これがないとメニューバーの項目が強調表示されたままになってしまいます"
END FN
WINDOW OFF
ON MENU FN doMenus:                               '"メニューが選択された時の飛び先"
FN initMenu:                                      '"メニューの初期化"
FOR i = 1 TO _winMax:                             ' "ウィンドウは全然開いていない設定にする"
  theWindow(i) = _false
NEXT
DO
  HANDLEEVENTS:                                   ' "イベント処理は自動で行ってくれます"
UNTIL gQuit_flag