「最小化」しているエディタウインドウをマクロで「前面化」する方法は?

  1. 質問です。

    【状況】
    Mery のエディタウインドウが複数開いていて、非アクティブなウインドウが最小化しているとき

    // とりあえずエディタウインドウがふたつあるという仮定で…
    
    // Mery のエディタウインドウが複数のときは、アクティブじゃない方のウインドウ
    var ee = ( Editors.Count > 1 && editor == Editors.Item( 0 ) ) ? 1 : 0;
    
    // ふたつ以上のタブがあれば、ふたつめのタブ
    var dd = ( Editors.Item( ee ).Documents.Count > 1 ) ? 1 : 0;
    
    // 指定したウインドウ、タブをアクティブ化
    // =>  最小化しているウインドウは「前面化」せず、タスクボタンにフォーカスが入る
    Editors.Item( ee ).Documents.Item( dd ).Activate();
    
    	// アクティブなペイン  =>  意味なし
    	Editors.Item( ee ).ExecuteCommandByID( MEID_WINDOW_ACTIVE_PANE = 2189 );
    
    	// 描画再開  =>  意味なし
    	Redraw = true;
    
    	// ※Error オブジェクトでサポートされていないプロパティまたはメソッドです
    	try         { Editors.Item( ee ).Activate(); }
    	catch ( e ) { ; }
    
    // "ほげほげ" と書き込んでみる  =>  成功
    Editors.Item( ee ).ActiveDocument.Write( "ほげほげ" );

    このようなテストマクロでは最小化しているエディタウインドウがアクティブ化 …というかウインドウが「前面化」してくれませんでした。 :(
    非アクティブウインドウを最小化せず、並べたり重ねたりで表示している場合は問題ないのですが…。
    動作確認した環境は、例によって Windows XP 32bit SP3 上で、Mery 3.0.1 と 2.7.4( /sp で起動)のポータブルモード、タブ有効(TDI)です。

    【質問】
    1.マクロから最小化しているエディタウインドウでも復帰/前面化させることができるような方法はあるでしょうか?
    2.Win 7 以降だとタスクバー/タスクボタンの仕様がちがうようですから、これは Win XP だけの問題なのでしょうか?

    もしも Win XP だけの OS 依存的な問題でしたら、対策とかはナシでも構いませんが、現行 OS むけのサンプル
    コードを教えていただければとおもいます。 :D
    (とりあえず、コマンドライン引数 /sp でべつの Mery.exe プロセスが起動している状況は考慮しないでよいかと)

    ※ 上のサンプルコードは単純化してありますから、実際は Editors/Documents のループ処理でターゲットのタブのファイルパス Editors.Item(n).Documents.Item(m).FullName を取得してから

    WshShell.Run('"mery.exe" "' + Editors.Item(n).Documents.Item(m).FullName + '"');

    のような処理で無理やりアクティブ化・前面化させることもできるのですが、この場合でも、既存の『無題-x』タブを指定してアクティブ化させることはできません。

    ちなみに、利用目的は「コンパクトメニュー」マクロで、[ウインドウ] メニューのタブ指定での移動の再現です。
    現在公開中のバージョンでは複数ウインドウ状態のときにほかのエディタウインドウのタブの取得/アクティブ化に対応できていなかったので、Mery 3.0 用コマンドの追加とあわせて更新しようとおもったのですが…。 (『無題-X』のタブの連番部分の番号も、マクロからは取得できないままでいます)

    p.s.
    通常では省略可能なので明示的に使用することのない Window オブジェクトについてですが、JavaScript マクロのソースコードにタイトルケースで Window と記述するとエラーになりますよね。
    ブログ記事や CHANGELOG では window.BeginUndoGroup や window.AddUndo と説明されていますが、マクロリファレンスで "Window インターフェイス" となっているという… 罠? XD

     |  sukemaru  |  返信
  2. 上掲のテストマクロでも Editors.Item(ee) のタブを Activate() したときにタスクボタンにフォーカスが入りはしますので、以下のキー送信用コードを追加すると最小化しているウインドウが前面に復帰してくれるよです(うちの Win XP では)が…

    if ( Editors.Item(ee) != editor ) {
      // [Alt+Space]: ウインドウ/タスクボタンのシステムメニュー
      // [R]: 元のサイズに戻す
      new ActiveXObject( "WScript.Shell" ).SendKeys( "%{ }R" );
    }

    Win 7 以降のシェル(エクスプローラ)側でタスクボタンをグループ化させる設定にしているときや、タスクバーを非表示にする(自動的に隠す)設定にしているときに Activate() されたタブのあるウインドウが前面化してくれるかどうかが分かりません。
    それに、SendKeys() はターゲットのウインドウを指定してキー送信するわけではありませんから、できれば使いたくないところです。 :|
    あらためて、ちゃんとしたエレガントな方法がありましたら、ご教示くださいませ。

    ※ なお、この追加コード × Win XP でも「アウトラインやアウトプットなどにフォーカスがある状態で最小化したウインドウ」の場合は前面に復帰しませんでした。

    … 一応「コンパクトメニュー」マクロ for Mery 3.0.1 の暫定版も置いておきます。
    https://www.haijin-boys.com/wiki/images/4/4c/コンパクトメニュー_for_Mery3.zip
    GetKeyState.exe を利用した改造とかが入ったままの自家用コードですが、単独運用できるようにしてあります。
    ※ [ウインドウ] メニューの 「タブ一覧」 のリストを複数ウインドウに対応させ、マクロライブラリ 2019/11/21 版の「選択したタブをアクティブ化」 でのフォーカスの不具合解消とあわせてムリヤリ修正。
    「タブ一覧」の準備 → 165行目~
    「タブ一覧」をポップアップメニュー内に列挙 → 728行目~
    「選択したタブをアクティブ化」 → 929行目~

     |  sukemaru  |  返信
  3. > 1.マクロから最小化しているエディタウインドウでも復帰/前面化させることができるような方法はあるでしょうか?

    ちょっとすぐには思い浮かばないですね。

    > 2.Win 7 以降だとタスクバー/タスクボタンの仕様がちがうようですから、これは Win XP だけの問題なのでしょうか?

    document.Activate() は Editor (エディターウィンドウ) の中の Document (タブ) をアクティブにするためのものなので、ウィンドウを最小化から復元するものではありません。

    > ブログ記事や CHANGELOG では window.BeginUndoGroup や window.AddUndo と説明されていますが、マクロリファレンスで "Window インターフェイス" となっているという… 罠? XD

    罠ではないですが、JavaScript の変数が先頭大文字だとイヤじゃないですか?そしてマクロリファレンスの目次が先頭小文字だとイヤじゃないですか?そこは察してください (w

     |  Kuro  |  返信
  4. XP の powershell で動くかどうかは不明。
    無理やり1つの js ファイルにしてますが ps1 ファイルと js ファイルに分けた方がきっと高速。

    /*
    1つの js ファイルのみでパワーシェル実行
    */
    
    //PoweShell スクリプトをヒアドキュメントで記述する
    //PowerShell用のコメントはブロックコメントにすること
    var heredoc = (function () {/*
    $ps = Get-Process -Name Mery
    $Win32 = &{
    $cscode = @"
    [DllImport("user32.dll")]
    public static extern int ShowWindow(IntPtr hWnd,IntPtr nCmdShow);
    "@
      return (add-type -memberDefinition $cscode -name "Win32ApiFunctions" -passthru)
    }
    $Win32::ShowWindow($ps.MainWindowHandle,9)
    */}).toString().match(/(?:\/\*(?:[\s\S]*?)\*\/)/).pop().replace(/^\/\*/, "").replace(/\*\/$/, "");
    
    // 「文字列」から「UNICODE-byte配列」を経由して「Base64」に変換
    function EncodePSCommand(string) {
        var encoder = new ActiveXObject("System.Text.UNICODEEncoding");
        var xmldoc = new ActiveXObject("Msxml2.DOMDocument");
        var base64 = null;
        var element = xmldoc.createElement("base64");
        element.dataType = "bin.base64";
        element.nodeTypedValue = encoder.GetBytes_4(string);
        base64 = element.text;
        element = null;
        return base64;
    }
    var enccommand = EncodePSCommand(heredoc);
    
    var ee = ( Editors.Count > 1 && editor == Editors.Item( 0 ) ) ? 1 : 0;
    var dd = ( Editors.Item( ee ).Documents.Count > 1 ) ? 1 : 0;
    // =>  最小化しているウインドウは「前面化」せず、タスクボタンにフォーカスが入る
    Editors.Item( ee ).Documents.Item( dd ).Activate();
    
    var sh=new ActiveXObject("WScript.Shell");
    sh.Run("powershell.exe -sta -executionpolicy remotesigned -encodedcommand " + enccommand,0,true);
     |  通りすがり  |  返信
  5. これは大掛かりですね。

    その後、私も考えてみたのですが難しいですね。

    Mery.exe /act ていうオプションで Mery をアクティブ化するだけっていうコマンドラインオプションがありますが、これは最後にアクティブだったタブがあるウィンドウしか復元されませんし…。

    Mery 側でやるとしたら sukemaru さんのコードに書かれている、editor.Activate() を実装してウィンドウ単位でアクティブ化・復元する仕組みを作る感じが一番スマートかなと思いました。

     |  Kuro  |  返信
  6. ありがとうございます。 :D
    幸いながら PowerShell を XP (ローエンド機) にインストールしてあったので、いただいたコードを試してみました。
    そのままの JS 単独のコードでも、PS1 を呼び出すコードでも4秒ぐらいかかるようです(初回の呼び出しでは6~8秒かかりますね ← JS 単独のほうが遅かったです)。

    var ee = ( Editors.Count > 1 && editor == Editors.Item( 0 ) ) ? 1 : 0;
    var dd = ( Editors.Item( ee ).Documents.Count > 1 ) ? 1 : 0;
    // =>  最小化しているウインドウは「前面化」せず、タスクボタンにフォーカスが入る
    Editors.Item( ee ).Documents.Item( dd ).Activate();
    
    // 別途 PS1 ファイルを用意して PowerShell で実行
    var psCommand = "powershell -STA -ExecutionPolicy RemoteSigned -File ";
    var psScript = "\"" + ScriptFullName.replace( /[^\\]+$/, "TEST.ps1" ) + "\"";
    var WshShell = new ActiveXObject( "WScript.Shell" );
    WshShell.Run( psCommand + psScript, 0, true );

    すでに「コンパクトメニュー」マクロは WshShell.SendKeys() を使用するかたちで3月29日付けで更新してしまいましたが、今後の参考にさせていただきます。 :)
    …といっても、自分で PowerShell 用のコードを書けないですし、PS1 ファイルと分けて実行するさいのコマンドラインパラメータの書き方も papagoat さんの「日付と時刻を挿入(カレンダー版)」マクロがなければ解決できなかったぐらいなんですよね。

    むしろ、コメントブロックを toString() するコーディングがすごすぎて魂消ちゃいました。 グレートです! 8)

     |  sukemaru  |  返信
  7. > その後、私も考えてみたのですが難しいですね。
    > Mery 側でやるとしたら sukemaru さんのコードに書かれている、editor.Activate() を実装してウィンドウ単位でアクティブ化・復元する仕組みを作る感じが一番スマートかなと思いました。
    色々とご検討いただき、ありがとうございます。
    editor.Activate() については、じつはこっそり実装されていたりしないかな? とかおもって書いてみただけなのですが、自分が試したことはちゃんと全部かかないと… みたいな(WshShell.AppActivate() メソッドもダメだったのを書き漏らしましたが)。 :D
    「コンパクトメニュー」マクロ以外にも最小化しているウインドウを前面化させたいという需要があるのかわかりませんが、「コンパクトメニュー」を右クリックメニューに入れると Mery の非表示コマンドや非表示のマクロを手軽にあつかえて便利ですので、個人的に出番がふえてきてます。 調子に乗って独自拡張コマンドをマシマシに盛りこんで遊んだりとかも。 X)

    > Mery.exe /act ていうオプションで Mery をアクティブ化するだけっていうコマンドラインオプションがありますが、これは最後にアクティブだったタブがあるウィンドウしか復元されませんし…。
    /act オプションを知りませんでしたので、Run( "mery.exe" ) でフォーカスの入ったエディタウインドウに空タブを追加して前面化させられないか( Close() メソッドで空タブを閉じれば… )とか、WshShell.SendKeys( "{Enter}" ) で前面化( 最小化してなくて背面にあるときに不都合あり…)とか、私も無駄にがんばってみたんですけどね。 :(

     |  sukemaru  |  返信
  8. >そのままの JS 単独のコードでも、PS1 を呼び出すコードでも4秒ぐらいかかるようです
    やっぱり PowerShell は起動が遅いですよね。Win32API を利用する一つのアイデアとして
    提示しました。
    他に Excel を使うとか SFC mini を使うとかありますし、いっそのこと全部 Python マクロ
    で書き直す方法もありますよ(無茶ぶり)。

     |  通りすがり  |  返信
  9. マクロの全部書き直しではなく、ウインドウ復帰のみ Python を使う方法

    var ee = ( Editors.Count > 1 && editor == Editors.Item( 0 ) ) ? 1 : 0;
    var dd = ( Editors.Item( ee ).Documents.Count > 1 ) ? 1 : 0;
    var doc = Editors.Item( ee ).Documents.Item( dd )
    
    var windowtitle = doc.name + ( (doc.Saved)?" ":" * " ) + "- Mery";
    var sh = new ActiveXObject("WScript.Shell");
    sh.Run('python.exe -c "import win32gui;win32gui.ShowWindow(win32gui.FindWindow(None,\'' + windowtitle + '\'),9);"',0,true);
     |  通りすがり  |  返信
  10. ありがとうございます。

    「コンパクトメニュー」マクロだと拡張コマンドの「プロパティ」も初回呼び出しがもっさりなんですけど、Mery の標準コマンドを再現している部分についてはなるべくサクサク反応してくれないとカッコ悪いかな? とおもっちゃうのです。 :P

    Run( 'python ...' ) のコードも試させていただきまして、こちらの環境では1秒ぐらいで前面化してくれました(初回は2~3秒ぐらい)。
    けっこう速い! これは達人のスゴ技ですね。 :o

    ただ、拙作マクロごときのために Python を要求するのは、マクロライブラリ所収の include ライブラリや GetKeyState.exe を要求する以上にムリがあるような気もするのですが、どうなんでしょう?
    こちらのコードの Python 用コマンドライン引数とか、先の PowerShell 用コードの関数部分とか全然理解できていませんので、自分でメンテできないコードを採用してよいものかどうか。

    -----
    Python2.7 をインストールしてあったはずなのですぐに試せるかなとおもっていたら何故かうごかず、Python2.7 や pywin32 を更新してもダメ(XP にインストールできる pygtk2 の最新版がわからず古いまま)。
    なんでだろ? とおもったらセキュリティ的に不安だったからか環境設定から Python 関係の PATH をはずしてあっただけというオチでした。 X(
    -----
    Pluguin.h では「1.最近のファイルの履歴」「2.開いているタブのリスト」「3.クリップボード履歴」「4.フォントの履歴」「5.スペルチェックの提案」の各アイテムを指定するための ID が定義されていませんので、「コンパクトメニュー」マクロでは
    1. → 「最近閉じたファイルの履歴」で代替
    2. → ムリヤリ独自実装(試行錯誤中)
    3. → ver 2.8.1 以降のバージョン制限つき
    4. → スポイル …
    5. → スポイル(メインメニューやキーボードショートカット設定にないから気がつかなかったフリ)
    としていますが、「フォントの履歴」が再現できないのがつらいところです。

    フォントの履歴の取得だけなら Mery.ini の [View] セクションから "FontNameX=hoge\nFontHeightX=-##" を抽出すればよいのですが、マクロからフォントの切り替えはできませんし、[表示] メニューからフォントを変更しているときに表示順の並べ替えもできないんですよね (FontHeight の値の pt への換算のしかたも分かりません)。

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