ポップアップメニューを「n*十件ずつ」のサブメニューに自動分割する
分割サブメニュー[編集]
ポップアップメニューの アイテム数が非常に多いとき、メニューの上下に配置されたスピンハンドル ▲ ▼ でスクロールさせるのは効率が悪いので、「1 - 20 件目 ▸
」「21 - 40 件目 ▸
」のかたちでサブメニュー項目に分割して表示するためのサンプルコードです。
アイテム数にあわせて「1 - 20 件目 ▸
」「21 - 40 件目 ▸
」の分割サブメニュー項目を自動的に生成しますので、ポップアップメニューのアイテム数が増減する「検索」「抽出」系のマクロに仕込むと便利かとおもいます。
- 「
1 - 20 件目 ▸
」「21 - 40 件目 ▸
」の項目は、サブメニュー項目「20 件ずつ表示 ▸
」配下の "孫" メニュー項目にすることもでき、分割サブメニュー内の行数「20 件目」の部分は設定項目で任意の数値に変更できます(10 件ごとにセパレータを挿入するので、10 の倍数を推奨)。
- 「分割サブメニュー」を使用するかしないかは設定項目で選択できますが、サンプルコードでは「使用する」に設定した場合でもアイテム数が「分割する単位」の 2 倍以下のときは「分割サブメニュー」で表示せずに、メインメニューの直下にアイテムを列挙させるようにしています。
ポップアップメニューに列挙するアイテムは
PopupMenu.Add( "文字列", id [, flags ] )
メソッドの記法にあわせて
[ ["アイテム1",id_1], ["アイテム2",id_2], ["アイテム3",id_3], ["アイテム4",id_4] ... ]
の形式の配列で用意してください。 コードを改造できるなら、任意でメニューの定数 flags
や、アイテムを選択したときの個別動作 function()
などを配列に入れてもよいでしょう。
※ サンプルコードでは、for()
文のループ処理で Array[i][0]
と Array[i]1]
をポップアップメニュー項目 Add( "アイテム1",id_1 )
に変換しています。
- サンプルコードのポップアップメニュー画像
組み込み関数 DevidedSubMenu()[編集]
ポップアップメニューに列挙するアイテムとして用意した配列から「分割サブメニュー」を生成する関数コードです。
- 設定項目の変数
subMenuHeight
- 分割サブメニュー内の行数の指定。
※ 10 の倍数で指定してください。
- 設定項目の変数
subMenuEnable
- 分割サブメニュー「1 - 20 件目 ▸」「21 - 40 件目 ▸」... の項目を表示する/表示しない
- メニュー項目「20 件ずつ表示 ▸」の配下に置く /メインのメニュー直下に置く
- を指定します。
- 0: 分割サブメニューを使用しない(全アイテムを列挙)
- 1: 「20 件ずつ表示 ▸」の配下に分割サブメニュー「1 - 20 件目 ▸」... を表示する (サンプル画像:左)
- 2: 分割サブメニュー「1 - 20 件目 ▸」「21 - 40 件目 ▸」... だけを表示する (サンプル画像:右)
※ 設定変数や、ポップアップメニューに列挙するアイテムの配列は、関数から参照できる階層のスコープにおいてください(または、引数として関数に渡す)。
おまけ関数コード
- AddCancelLines() 関数: 「セパレータ」「キャンセル」行の自動挿入
- Pad() 関数: 連番の「ケタ埋め」(右揃え)
- ※ ケタ埋めの空白文字は EN SPACE「 」(U+2002) にしてありますが、PCのコントロールパネル設定で指定した「メニュー」用のフォント種によっては半角数字との幅が合わないことがあります。
半角数字の幅に等しい空白文字として、 MS UI Gothic (MeiryoKe_UI Gothic) では 「2分アキ = EN SPACE: "\u2002"」、 Meiryo UI では「和字間隔 = 全角空白: "\u3000"」、 Segoe UI では 「図形間隔 = FIGURE SPACE: "\u2007"」 にすると具合がよさそうです。
サンプルコード1[編集]
アイテム数が多い/アイテム数が変動するサンプル として、アクティブなタブのすべての論理行をポップアップメニューに表示する【仮】マクロです。
「キャンセル」行の表示や、行番号の「ケタ埋め」するためのコードも入れているで読みづらいかも知れませんが、 /* ... */
で説明をつけた部分が「分割サブメニュー」用のコードです。
配列の中身や、menu.Track()
以降の動作コード【仮】の部分を、任意のコードに差し替えてお使いください。
- ※「分割サブメニュー」に表示される「キャンセル」のアイテムは Space キーをアクセラレータにしてあるので、Esc キーで多階層のメニューを繰り上がりをせずにポップアップメニューを一発キャンセルできます。
メインメニュー直下に全アイテムを列挙しているときは、Space キーで「キャンセル」アイテムをトグル移動しながらメニュー全体をスクロールできます(Esc キーでポップアップメニューをキャンセル)。
#title = "論理行をポップアップメニューにリストアップ"
// ( function() {
var start = new Date(); // 所要時間計測(開始)
// ---------- ▼ 設定項目 ▼ ----------
/* ■「分割サブメニュー」を使用するか? */
var subMenuEnable = 1;
// 0: 使用しない(メインメニュー直下に全アイテムを列挙する)
// 1: メインメニュー直下には「20 件ずつ表示」と全アイテムの列挙
// 「20 件ずつ表示」の配下に「1-20 件目」「21-40 件目」の"孫"メニューを置く
// 2: メインメニュー直下に「1-20 件目」「21-40 件目」のサブメニューを置く
/* ■ 分割する単位 (初期値: 20 >> 20件ずつに分割) */
// ※ subMenuHeight の 2 倍より多いアイテムがあれば分割サブメニューを適用する
var subMenuHeight = 20;
// ---------- ▼ メインのコード ▼ ----------
// ポップアップメニューに表示する「アイテム」の配列
var itemArray = new Array();
// 【仮】 論理行数の連番アイテム(文字列と行番号)を生成する
var d = editor.ActiveDocument;
var lineText, lines = d.GetLines( 0 );
for ( var y = 1; y <= lines; y++ ) {
// 空白文字を詰め、「&」記号を保守する
lineText = d.GetLine( y, 0 )
.replace( /\t+|[ ]{4,}|[ ]{2,}/g, " › " )
.replace( /&/g, "&&" ).slice( 0, 30 );
itemArray.push( [ ": " + lineText, y ] );
}
// 「アイテム」数
var len = itemArray.length;
// 「アイテム数」のケタ数(※ケタ埋めに使用する)
var lenWidth = len.toString().length;
if ( len > 0 ) {
// ポップアップメニューの準備
var menu = CreatePopupMenu();
/**
* 「アイテム数」が「分割単位」の 2 倍より多いなら
* 「分割サブメニュー」を生成する
*/
if ( subMenuEnable && len > subMenuHeight * 2 ) {
DevidedSubMenu();
}
// subMenuEnable = 1 またはアイテム数が「分割単位」の 2 倍以下なら
// メインメニュー直下に全アイテムを列挙する
if ( subMenuEnable < 2 || len <= subMenuHeight * 2 ) {
for ( var i = 0; i < len; i++ ) {
// 10 件ごとにセパレータ、20* 件ごとに「キャンセル」行を追加する
AddCancelLines( menu, i, subMenuHeight );
// 配列の「アイテム」をメインメニュー直下に追加する
menu.Add( Pad( itemArray[i][1], 2 )
+ itemArray[i][0]
, itemArray[i][1] );
}
// 最下行に「キャンセル」をピン止めする(Space キーでスクロール可)
menu.Add( "", 0, meMenuSeparator );
menu.Add( Pad() + "キャンセル\t& ", 0 );
}
// ステータスバーにメニューの件数と処理時間を表示する
var elapsedSec = ( ( new Date() - start ) / 1000 ).toFixed( 3 );
Status = " メニューのアイテム: " + len + " 件 "
+ " [ " + elapsedSec + " 秒 ]";
// ポップアップメニューを表示する
var yy = menu.Track( mePosMouse );
// 【仮】 メニューの連番の論理行にジャンプする
if ( yy > 0 ) {
d.selection.SetActivePoint( mePosLogical, 1, yy );
}
} // if( len > 0 ) 閉じ
// ---------- ▼ 関数 ▼ ----------
// 関数は呼び出し元のスコープに配置する
// ※ 呼び出し元のスコープが閉じていると変数を参照できなくなる
/**
* 関数 DevidedSubMenu()
* サブメニュー「※ 20* 件ずつ表示 ※ ▶」/「1 - 20* 件目 ▶」に
* 配列の「アイテム」を分割表示する
* ※「20*」の部分の数値は設定用変数 subMenuHeight で指定
*
* ※呼び出し元のスコープが閉じている場合は
* ふたつの設定変数と menu , itemArray オブジェクトを引き渡すこと
* e.g. DevidedSubMenu( objPopupMenu, items, mode, height )
* len = itemArray.length と lenWith = len.toString().length は
* 関数のなかで再定義できる
*/
function DevidedSubMenu() {
var subMenu = CreatePopupMenu();
var popupMenu = ( subMenuEnable == 1 ) ? subMenu : menu;
if ( subMenuEnable == 1 ) {
// 「※ 20* 件ずつ表示 ※ ▶」をピン止め
menu.AddPopup( " ※ " + subMenuHeight
+ " 行ずつ表示(&D) ※"
, subMenu );
}
// 「キャンセル」をピン止め
popupMenu.Add( Pad() + "キャンセル", 0 );
popupMenu.Add( "", 0, meMenuSeparator );
// 配列のアイテムをポップアップメニューに追加していく
var smArray = []; // SubMenu Array
var smId , _from , _to;
for ( var i = 0; i < len; i++ ) {
if ( i % subMenuHeight == 0 ) {
// 配列にメニュー項目「_from - _to 件目 ▶」を生成する
smArray.push( CreatePopupMenu() );
// 「_from - _to 件目」メニューの index
smId = smArray.length - 1;
// _from と _to の値
_from = smId * subMenuHeight + 1;
_to = Math.min( smArray.length * subMenuHeight, len );
// 「_from - _to 件目」メニュー項目
popupMenu.AddPopup( Pad( _from, 1 ) + " - "
+ Pad( _to ) + " 件目"
, smArray[smId] );
}
// 「_from - _to 件目」メニュー配下の 10件 ごとにセパレータと
// 先頭に「キャンセル」行(Space キーでキャンセル可)
AddCancelLines( smArray[smId], i, subMenuHeight );
// 「アイテム」の配列から「_from - _to 件目」メニュー配下に追加する
// ※ smArray[smId] は CreatePopupMenu オブジェクト
smArray[smId].Add( Pad( itemArray[i][1], 2 )
+ itemArray[i][0]
, itemArray[i][1] );
// 「_from - _to 件目」のさいごに「キャセル」行を追加する
if ( i == len - 1 ) {
popupMenu.Add( "", 0, meMenuSeparator );
// (Space キーでキャンセル可)
popupMenu.Add( Pad() + "キャンセル\t& ", 0 );
}
}
}
/**
* 関数 AddCancelLines( objPopupMenu, id, distance )
* 10 件ごとにセパレータと
* 20* 件ごとに「キャンセル」行を追加する
* ※Space キーでキャンセル可(またはスクロール)
*
* 第1引数は ポップアップメニューオブジェクト
* 第2引数は 呼び出し元の for() 文の「 i 」
* 第3引数は 「キャンセル」行を表示する間隔*
* (省略した場合は設定項目の変数 subMenuHeight)
*/
function AddCancelLines( objPopupMenu, id, distance ) {
var distance = distance || subMenuHeight || 30;
// 10 件ごとにセパレータを追加 && id % distance != 0
if ( id % 10 == 0 ) {
objPopupMenu.Add( "", 0, meMenuSeparator );
}
// 30* 件(distance)ごとに「キャンセル」行を追加
if ( id % distance == 0 ) {
objPopupMenu.Add( Pad() + "キャンセル\t& ", 0 );
objPopupMenu.Add( "", 0, meMenuSeparator );
}
}
/**
* 関数 Pad( num, amp )
* EN SPACE (U+2002)「 」でケタ埋めして右揃えにする
* ※ケタ数にはグローバルスコープの変数 lenWidth を使用する
*
* 第1引数は ケタ埋め対象の数字
* 第2引数は アクセラレータ用の「&」の指定
* (1 なら先頭の数字、2 なら末尾の数字をアクセラレータにする)
*/
function Pad( num, amp ) {
num = num || "";
var spc = " ";
var pad = ( amp == 1 ) ? ( spc + "&" + num )
: ( amp == 2 ) ? ( spc + num ).replace( /(\d)$/, "&$1" )
: ( spc + num );
amp = amp ? "&" : "";
return pad.slice( - lenWidth - amp.length );
}
// }() );
サンプルコード2[編集]
ふたつめのサンプル画像のパターンに限定するなら、より単純なコードで分割サブメニューを生成できるようですので、サンプルコードを貼っておきます。
- 関数化せずにグローバルスコープ内にベタ書きしてありますが、
function()
にする場合でも引数として渡す必要があるのはfor()
文よりもうえでハイライトされた3つだけです(len = itemArray.length
は関数内で再定義できるので省略可)。 - 連番部分の末尾の数字をアクセラレータにする処理
Number.toString().replace( /\d$/, "&$&" )
だけ入れてありますが、番号の桁埋め(右寄せ)やセパレータの挿入コードなどを省いてあります。
インラインコメントや空行を除いて圧縮すれば、分割メニュー生成の for()
文はたったの十数行で済むという...。
// ---------- ▼ 設定項目 ▼ ----------
// ■ サブメニューに分割する単位 (初期値: 20 >> 20件ずつに分割)
var subMenuHeight = 20;
// ---------- ▼ 前処理部分 ▼ ----------
// ポップアップメニューに表示する「アイテム」の配列
var itemArray = [];
// ※ 【仮】 アイテムとして 1 ~ 250 の数値を放り込んでおく
for ( var i = 0; i < 250; i ++ ) {
itemArray.push( i + 1 );
}
// 「アイテム」数
var len = itemArray.length;
// ---------- ▼ 自動分割サブメニュー ▼ ----------
// メインのポップアップメニューオブジェクト
var mainMenu = CreatePopupMenu();
// 配列からポップアップメニューを生成する
for ( var i = 0, k = 1, from = 1, to = subMenuHeight,
subMenu, num, id, label;
i < len; i ++, from ++, to ++ ) {
// アイテムを 20 件ずつ分割収容するサブメニューを自動生成する
// ※ ループ処理 20 回ごとに subMenu を新しいサブメニュー項目として更新する
if ( i % subMenuHeight == 0 ) {
// 「サブメニュー 1」からの連番つき定型文字列
num = ( k ++ ).toString().replace( /\d$/, "&$&:\t " );
to = ( to < len ) ? to : len;
// サブメニューを生成
mainMenu.AddPopup(
"サブメニュー " + num + from + " - " + to + " 件目"
, subMenu = CreatePopupMenu() );
}
// 「アイテム 1」からの連番つき定型文字列
id = i + 1;
label = "アイテム " + id.toString().replace( /\d$/, "&$&" )
// サブメニューに配列のアイテムを収容する
subMenu.Add( label, id );
}
// ---------- ▼ ポップアップメニューを表示 ▼ ----------
var r = mainMenu.Track( mePosMouse );
// 【仮】 選択したアイテムの名前をメッセージボックスに表示
var msg = ( r > 0 )
? mainMenu.GetText( r ).replace( /&/, "" ) + " を選択"
: "キャンセル"
Alert( msg );
サブメニューの名前が連番的な定型文だったり、アイテムを配列内のインデックスどおり素直に並べているだけなので単純化できていますが、各サブメニューの名前が任意のフォルダ名で各アイテムがファイル名のようなパターンだと、フォルダオブジェクトやファイルオブジェクトを取得したり、それぞれごとにソート(並べ替え)したりの処理が必要になるかとおもいます。