だるろぐ

明日できることは、今日しない。

Tonjiru v1.2.0 + WPF での起動オプション、ジャンプリスト、トースト

f:id:daruyanagi:20170608192727p:plain

  • 起動オプションの追加(/g で GUI 付きの起動)
  • ジャンプリストへの対応(GUI 付きの起動を追加)
  • ウィンドウ情報のクリップボードコピー(JSON 形式)
  • ウィンドウ情報のファイル保存(JSON 形式)
  • 通知機能
  • 安定性の向上

github.com

WPF と起動オプション

起動時に[Shift]キーが押されていたら GUI を起動するという挙動は

  • App.xaml の StartupUri を削除
  • App.Startup でキーの押し下げ確認と MainWindow の自前生成

という感じで実現していたんだけど、起動オプションを付けたら破綻したので、

  • App.xaml の StartupUri を元に戻す
  • App.xaml のビルドアクションを Page にして、main 関数を自分で書く

という感じに変えた。

[System.STAThreadAttribute()]
public static void Main()
{
    var args = Environment.GetCommandLineArgs();

    if (args.Contains("/g") || (Control.ModifierKeys & Keys.Shift) == Keys.Shift)
    {
        var app = new Tonjiru.App();
        app.InitializeComponent();
        app.Run();
    }
    else // UI less mode
    {
        CloseAllWindowsAndExit();
    }
}

/h スイッチで CUI ヘルプを出そうかなーと思ったけど、そっちはちょっと面倒くさいのでやめた。AttachConsole() などを使えば行けるのだけど、ちょっと挙動が変。ちゃんとやろうとすると CUI と GUI で EXE を分けないといけないみたいだが、そこまでやる気はないかな。

ジャンプリスト

起動オプションを付けた副産物として、ジャンプリストへの対応が簡単になった。App.xaml に以下のように記述。

<Application x:Class="Tonjiru.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:Tonjiru"
             StartupUri="Views\MainWindow.xaml">
    <Application.Resources>
         
    </Application.Resources>

    <JumpList.JumpList>
        <JumpList>
            <JumpTask Title="Launch with GUI"
                Description="Launch with GUI" 
                Arguments="/g" />
        </JumpList>
    </JumpList.JumpList>
</Application>

ジャンプリストは最近忘れられてる気がするけど、割かし便利だと思う。対応アプリが増えるといいな。

なお、ジャンプリストから起動するとワーキングディレクトリが System フォルダーになった気がする。設定ファイルなどをロードするとき、パス検索をいい加減にしていると痛い目にあう(あった)。

ValueTupple と DataContractJsonSerializer

ウィンドウ情報の保存は手抜きで DataContractJsonSerializer を使ったんだけど、Model をそのままシリアライズするとサイズがすごく大きくなってしまった。そこで情報を間引いたんだが、ここでタプルが使えるのではないかと気づいた。

list.Select(_ => (title: _.Title, process: _.ProcessName)); // これをシリアライズ

試しにこれを

DataContractJsonSerializer(typeof(ValueTupple<string, string>));

DataContractJsonSerializer(typeof((string title, string process)));

みたいに使ってみたところ――とりあえずコンパイルは通り、普通に使えた。けれど、出力される JSON がどっちも

[{ "Item1": "hoge", "Item2": "fuga" }]

みたいな感じになる(ほんとは Item1 のところが title や process になってほしいよね)ので、これを使うのはあきらめた。JSON.net のシリアライズだったら対応していた(or 対応してくれる)かもしれない?

通知

WPF でトーストを出そうと思うと WindowsRuntime を使わなくちゃーってなりがちだけど、ただ出したいだけであれば NotifyIcon で ShowBalloonTip() するのが楽でいい。Windows 7 だとバルーンだが、Windows 10 ではトーストになる。

using (var notify_icon = new System.Windows.Forms.NotifyIcon())
{
    notify_icon.Icon = System.Drawing.Icon.ExtractAssociatedIcon(System.Reflection.Assembly.GetEntryAssembly().Location);
    notify_icon.Visible = true;

    notify_icon.BalloonTipTitle = System.Reflection.Assembly.GetExecutingAssembly().GetName().Name;
    notify_icon.BalloonTipText = message;
    notify_icon.ShowBalloonTip(3000);
}

簡単だね!

f:id:daruyanagi:20170608194803p:plain

ポイントは Visible を true にしておくこと(じゃないとトーストが出てこない)、最後に Visible を False にするか Dispose() すること(でないとトレイにアイコンがゾンビ)ぐらい。