マクロにおけるキーボード入力の判定について

  1. Kuro 様、皆様、大変お世話になっております。

    Windows10 Pro, ver 22H2, 64bit
    Mery 3.6.5, 64bit

    を使わせて頂いております。現在、長文テキストを読むときに、自動スクロールができたら良いなと思い、マクロを書いてみました。

    ---------------------------------------
    // 自動的にスクロールするマクロ
    // 2024-01-24, Ver 0.0

    var lastLine = 10000;
    //出来れば、開いているファイルの最大行数、もしくはカーソル位置がEOFかどうかを判定したい
    //途中の行で、安全にマクロを終了できれば、この稚拙なコードでも実用上問題無い

    var speed = 3500;
    //スクロールのスピード、数値が大きくなるほどゆっくりスクロールする。単位 ミリ秒

    for (let i = 1; i < lastLine; i++) {
    document.selection.LineDown(false, 1);
    Sleep(speed);

    //キーボード入力に応じて for ループを抜け、処理を終了したい
    // Quit(); が使えるか?
    }
    ---------------------------------------------

    ところがこのコードでは、いったん起動させると、Mery を強制終了しか止める術がありません。

    キーボード入力に応じて処理を安全に終了させる方法を教えて下さい。

     |  Takeshi  |  返信
  2. 皆様、一応自己解決しました。

    GetKeyState.exe(キー状態取得実行ファイル) のページを参考に、Shift キーを長押しすると、動作を止められるようになりました。下記が修正後のコードです。とりあえず、これで使ってみようと思います。

    // 自動的にスクロールするマクロ
    // 2024-01-24, Ver 0.1

    var lastLine = 10000;
    //出来れば、開いているファイルの最大行数、もしくはカーソル位置がEOFかどうかを判定したい
    //実用上は、この稚拙なコードでも我慢できる

    var speed = 3500;
    //スクロールのスピード、数値が大きくなるほどゆっくりスクロールする。単位 ミリ秒

    for (let i = 1; i < lastLine; i++) {
    document.selection.LineDown(false, 1);
    Sleep(speed);

    //SHIFTキーが押されたら for ループを抜け、処理を終了
    var wshShell = new ActiveXObject("WScript.Shell");

    // カレントディレクトリをマクロディレクトリに変更
    wshShell.CurrentDirectory = editor.FullName.match(/^.*\\/)[0] + 'Macros';

    // SHIFTキー状態を取得
    var shift = wshShell.Run("GetKeyState.exe shift", 0, true);

    if(shift != -1){
    if(shift == 1){
    Quit();
    }
    }

    }

     |  Takeshi  |  返信
  3. Mery をご愛用いただきありがとうございます。

    おぉ、面白いマクロですね!

    Mery のマクロは Windows のスクリプト エンジンに丸投げなので、マクロを実行したら終了するまで、Mery 側から途中で割込み処理は難しいと思いますが、こんな方法があったとは…。勉強になりました。

    自動スクロールについては、マウスのホイール クリックでそれっぽいことができますが、実際にやってみると、スクロール速度が速すぎて読めませんね ^^;

    ホイール クリックの自動スクロール速度を調整できれば、テキストを読むときのような用途にも使えるようになるかもしれませんね。

     |  Kuro  |  返信
  4. 自分の環境では Sleep メソッドが使われてるマクロを Chakra や JScript (5.8) で実行すると、すべての Sleep が終わるまで Mery がフリーズしてしまいます。

    例えば、下記のマクロは1秒間隔で5回スクロールするという動作が期待されます。しかし、実際は1つめのスクロールも描画されずにフリーズしてしまい4秒後に復帰して5回スクロールされた状態で描画されます。

    ++ScrollY;
    Sleep(1000);
    ++ScrollY;
    Sleep(1000);
    ++ScrollY;
    Sleep(1000);
    ++ScrollY;
    Sleep(1000);
    ++ScrollY;

    以下を先頭に追加して V8 で実行すると期待通りに動作します。

    #language = "V8"

    Takeshi さんは上述の「自動的にスクロールするマクロ」が動作してるようなので環境によるのかもしれません。
    面白そうなマクロですが、残念ながら自分の環境ではうまく動作しませんでした。

    【テストした環境】
    ・Mery: 3.6.5 (x64)
    ・OS: Windows 10 22H2 (19045.3930、64 ビット)
    ・CPU: Intel Core i5-6200U
    ・GPU: Intel HD Graphics 520

    少し話がそれますが、マクロの記録で Ctrl + Up/Down でカーソルを動かさずにスクロールしたとき LineUp/LineDown メソッドと記録されるのは少し違和感がありますね。

     |  ucky  |  返信
  5. ご報告ありがとうございます。

    これは、画面が描画されるタイミングに依存しているようですね。

    Mery の仕様において、たとえば…

    カーソルが画面の最終行にある状態で、カーソルの下移動によって画面全体がスクロールする場合ですね。マクロの場合、以下のようになります。

    document.selection.LineDown(false, 1);

    このとき、仕様上、画面が強制的に再描画されます。

    しかし、マウス ホイールによるスクロールやその他多くのシーンでは、常に再描画を行うと動作速度が低下してしまうため、CPU に余裕ができたタイミングで再描画が行われる仕組みになっています。

    そのため、マクロの ScrollY プロパティを変更した場合、再描画のタイミングはマクロが終了して、CPU に余裕ができたときとなります。

    ちなみに、Redraw プロパティも試してみましたが、これは画面の再描画を抑制するためのプロパティだったもので、Redraw = true にしても強制的に再描画される仕様にはなっていないですね。

    そもそも、マクロの実行中にリアルタイムで画面を更新する使い方を想定していなかったため、画面スクロールのマクロには驚きました。

    そういった使い方がある場合、内部的な再描画のタイミングまでは変更できませんが、Redraw = true を呼び出すことで画面を強制的に再描画できるようにすると面白いかもしれませんね。

    > 少し話がそれますが、マクロの記録で Ctrl + Up/Down でカーソルを動かさずにスクロールしたとき LineUp/LineDown メソッドと記録されるのは少し違和感がありますね。

    これは気付いていませんでした。次のバージョンでは対応しておきますね。

     |  Kuro  |  返信
  6. 書き忘れていました。

    #language = "V8"

    V8 エンジンを使う場合、マクロは Mery.exe とは別のプロセスで実行されます。

    簡単に言うと、別のアプリから Mery を操作してる感じですね。

    そのため、再描画は通常、人間が操作しているときと同じタイミングで行われ、ScrollY プロパティでもリアルタイムに再描画されるんじゃないかなと思います。

     |  Kuro  |  返信
  7. >> Kuro さん

    ご確認ありがとうございます。

    なるほど、そういう仕様だったんですね。

    Takeshi さんのマクロもスピードを速めて改めて試したところ、カーソルが画面下部に来たタイミングで描画が再開されてスクロールしました。

    > そういった使い方がある場合、内部的な再描画のタイミングまでは変更できませんが、Redraw = true を呼び出すことで画面を強制的に再描画できるようにすると面白いかもしれませんね。

    既存のプロパティに機能を追加するのは少し怖いですが、面白そうですね。今のところ活用法は思いついてないですが……。

    >> Takeshi さん

    もう解決されているかもしれませんが、開いているファイルの行数は document.GetLines メソッドで取得できます。加えてカーソル位置の行数も document.selection.GetActivePointY メソッドで取得できるので、下記のようにすることで文書の終端の行でマクロを終了できると思います。

    // 文書の終端の行数を表示座標で取得
    const lastLine = document.GetLines(meGetLineView);
    
    // 現在のカーソル位置の行数を表示座標で取得
    const currentLine = document.selection.GetActivePointY(mePosView);
    
    // ループ回数を計算
    const loopCount = lastLine - currentLine;
    
    // 略
    
    for (let i = 0; i < loopCount; i++) {
    // 略
    }
     |  ucky  |  返信
  8. > > 少し話がそれますが、マクロの記録で Ctrl + Up/Down でカーソルを動かさずにスクロールしたとき LineUp/LineDown メソッドと記録されるのは少し違和感がありますね。
    >
    > これは気付いていませんでした。次のバージョンでは対応しておきますね。

    Version 3.7.0 にてスクロールに変更されているのを確認しました。

    修正ありがとうございました。

     |  ucky  |  返信
  9. ご確認ありがとうございます。

    うまく動いているようで安心しました。

    ちなみに、Ver 3.7.0 では、Ctrl + Up/Down 以外にも、Alt や Shift と組み合わせた場合でも、正しいコマンドが記録されるようになっています。

    > 既存のプロパティに機能を追加するのは少し怖いですが、面白そうですね。今のところ活用法は思いついてないですが……。

    それから、前回ご返信をいただいていた、「Redraw = true で再描画する」件については、今のところ使い道が見つからなさそうとのことでしたので、今回は実装を見送りました。

     |  Kuro  |  返信
  10. 他のトピックで話が出ていたので試しに#async = trueを使った自動スクロールマクロを作ってみました。

    #title = "自動スクロール"
    #async = true
    
    // スクロールの間隔 (単位:ミリ秒)
    var interval = 100;
    
    var doc = document;
    var sel = doc.selection;
    
    Redraw = false;
    
    // 初期のスクロールの位置を記録
    var initialScrollX = doc.ScrollX;
    var initialScrollY = doc.ScrollY;
    
    // 選択範囲の位置を記録
    var selections = [];
    var selectionCount = sel.Count || 1;
    var cursorPos = sel.GetActivePos(); // 実際のカーソル (システムキャレット) の位置
    for (var index = 0; index < selectionCount; index++) {
    	var selection = [
    		sel.GetAnchorPos(index),
    		sel.GetActivePos(index)
    	];
    	if (selection[1] !== cursorPos) {
    		selections.push(selection);
    	} else {
    		selections.unshift(selection); // 実際のカーソルを含む選択範囲は配列先頭に追加
    	}
    }
    
    // 文書の終端に移動して最終的なスクロールの位置を記録
    sel.StartOfDocument(); // カーソルが既に文書の終端にある場合を考慮して一度文書の先頭に移動
    sel.EndOfDocument();
    var finalScrollY = doc.ScrollY;
    
    // 選択範囲とスクロールの位置を復元
    sel.SetActivePos(selections[0][1]); // 選択範囲がある場所にカーソルを移動
    sel.SetAnchorPos(selections[0][0]); // 選択範囲のマージによる選択方向の反転を防ぐためアンカーも設定
    selections.push(selections.shift()); // 実際のカーソルを含む選択範囲は最後に追加したいので配列末尾に移動
    for (var i = 0; i < selections.length; i++) {
    	sel.AddPos(selections[i][0], selections[i][1]);
    }
    doc.ScrollX = initialScrollX;
    doc.ScrollY = initialScrollY;
    
    Redraw = true;
    
    // 最終的なスクロールの位置まで interval で設定した間隔でスクロール
    var currentScrollY = initialScrollY;
    while (currentScrollY < finalScrollY) {
    	if (shell.GetKeyState(0x10) < 0) {
    		break;
    	}
    	Sleep(interval);
    	doc.ScrollY = ++currentScrollY;
    }
    
    • Mery Ver 3.7.18 以降用です
    • [マクロ] メニューの [停止] か Shift キー長押しで途中で終了できます

    オーバースクロールとか考慮した結果、最終的なスクロールの位置の取得がちょっと強引な感じになってますが、少し試した感じ期待通り動いてます。

    #async = true、アイデア次第で面白いマクロが作れそうですね!

    ちなみに選択範囲の位置を復元するところの

    for (var i = 0; i < selections.length; i++) {
    	sel.AddPos(selections[i][0], selections[i][1]);
    }
    

    は、本当は

    sel.AddPos(selections);
    

    にしたかったんですが、「型が一致しません。」とエラーになってしまいました。

    #async = trueだとAddPos メソッドの引数に配列を使用できない仕様なのでしょうか?

     |  ucky  |  返信
  11. > 他のトピックで話が出ていたので試しに#async = trueを使った自動スクロールマクロを作ってみました。

    なるほどー。非同期なので自動スクロール中でも GUI が固まらず操作できるのは面白いですね。

    > #async = trueだとAddPos メソッドの引数に配列を使用できない仕様なのでしょうか?

    そのような仕様にしたつもりはないのですが、調べてみたところ、どうやら非同期処理が関係しているようです。

    以下はまだ確実な情報ではなく、私の理解も浅いため、ふわっとした説明になりますがご了承ください。

    #async = trueの仕様としては、GUI 関連の処理はメインスレッド、それ以外はサブスレッドで非同期に動作します。

    ただ、JScript (COM オブジェクト?) は STA (シングルスレッド専用) での動作を前提としているため、スレッドをまたいでオブジェクト (配列など) を直接やり取りすることはできないようです。

    つまり、AddPosは GUI を操作するのでメインスレッドで動きますが、selections配列はサブスレッド側のオブジェクトになるため、直接渡すことはできません。

    数値や文字列などの単純な型 (コピー可能な値) はスレッド間でも問題ありませんが、COM オブジェクトや配列はスレッドをまたいでそのまま渡すことはできない、ということのようです。

    とはいえ、ごにょごにょすれば渡す方法はあるようなので、この件については引き続き調べてみますね。

    ちなみに、現状 Mery のマクロで配列を引数にとる特殊なメソッドはAddPosAddPointくらいだと思いますが、他にも渡せない場合があるかもしれません。

    もし他にお気づきの部分があれば、教えていただけると助かります。

     |  Kuro  |  返信
  12. 調査・ご説明ありがとうございます。

    なるほどスクリプトエンジン側の問題だったんですね。

    > とはいえ、ごにょごにょすれば渡す方法はあるようなので、この件については引き続き調べてみますね。
    お手数をおかけしますが、よろしくお願いします。

     |  ucky  |  返信
  13. Ver 3.7.19 のリリースお疲れ様です。

    #async = true使用時にAddPosメソッドに配列を渡せるのを確認しました。
    ご対応いただきありがとうございます。

    今回追加された [開いている文書から検索] は便利ですね。これから活躍しそうです!

     |  ucky  |  返信
  14. 早速お試しいただき、動作確認もありがとうございます。

    ひとまずうまく動いているようで、ほっとしました。

    > 今回追加された [開いている文書から検索] は便利ですね。これから活躍しそうです!

    そう言っていただけると嬉しいです。(初期のころのご要望なので、実装までに 10 年ぐらいかかってますが… ^^;)

    ちょっとしたポイントですが、[すべて置換] にも適用されるので、その点だけご注意ください。

    また、[すべて検索] にも適用されますが、その状態で直接編集したときに変更が反映されるのは現在のタブだけです。(当然と言えば当然ですが…)

    ぜひご活用ください!

     |  Kuro  |  返信
スポンサーリンク