WPF Windowが最初に表示されたときに処理を行いたい

## WPFにはShownイベントがない

WPFアプリケーションを開発していて、Windowが最初に表示されたときにある条件が成立した場合に警告メッセージのダイアログを表示するという処理が必要になりました。

Windows Froms であれば、Formが最初に表示されたときに発生する`Form.Shown`というイベントがあるのですが、残念ながらWPFのWindowクラスには同様のイベントは存在しません。
 しかたないので他の方法を考えることにしました。

とりあえず、テストとしてWindowの表示後、無条件にメッセージボックスを表示するプログラムを書いてみることにします。

## Loadedイベントを利用する

まず最初は、Windowクラスの`FrameworkElement.Loaded`イベントを利用することを考えました。

[MSDN:FrameworkElement.Loaded イベント](http://msdn.microsoft.com/ja-jp/library/system.windows.frameworkelement.loaded\(v=vs.110\).aspx)

> 要素の配置、描画、および操作の準備が完了したときに発生します。 

説明を読むとこれでうまくいきそうです。  しかし、

```csharp
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        Loaded += (s, e) => { MessageBox.Show("Loaded", "TEST"); };
    }
}
```

というコードを書いて確認してみると、メッセージボックスが表示された段階ではWindowの中に何も表示されていませんでした。
このタイミングだと Window内のコントロールの描画が完了していないようです。
通常の初期処理であればこのタイミングで問題ないのですが、今回はWindowが表示された後に処理したかったのでこの方法を使えません。

## Activatedイベントを利用する

次に考えたのはWindowクラスの`Window.Activated`イベントを利用する方法です。

[MSDN:Window.Activated イベント](http://msdn.microsoft.com/ja-jp/library/system.windows.window.activated\(v=vs.110\).aspx)

> ウィンドウが前面ウィンドウになるときに発生します。 

とりあえず以下のようなコードを書いて試してみます。

```csharp
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        Activated += (s, e) => { MessageBox.Show("Activated","TEST"); };
    }
}
```

今度はWindowの表示が完了してから、メッセージボックスが表示されました。
しかし、メッセージボックスのOKボタンをクリックしてメッセージボックスをクローズさせてもすぐにまた表示されてしまいます。
`Activated`イベントはWindowがアクティブになるたびに呼び出されます。したがってメッセージボックスが表示されWindowがディアクティベート状態になったのが、メッセージボックスがクローズされることでWindowが再びアクティブになることで`Activated`イベントが無限ループ状態に陥ってしまったのでした。

処理を行いたいのは最初の１回だけなので、フラグとなるメンバを一つ追加してハンドリングすることにします。

```csharp
public partial class MainWindow : Window
{
    private bool flg = true;

    public MainWindow()
    {
        InitializeComponent();
        Activated += (s, e) => 
            {
                if (flg)
                {
                    flg = false;
                    MessageBox.Show("ACTIVATED", "TEST");
                }
            };
    }
}
```

論理型のメンバ変数`flg`を追加し、`Activated`イベントでは`flg`が真の場合のみ処理するようにします。処理するときに`flg`を偽にすることで、次にWindowがアクティブになっても処理は行われなくなります。

このとき面白いことに気づきました。メンバ変数`flg`に偽をセットするタイミングで動作が異なるのです。

```csharp
flg = false;
MessageBox.Show("ACTIVATED", "TEST");
```

メッセージボックスを表示する前に`flg`に偽をセットすると正常に動作します。
しかし、これを

```csharp
MessageBox.Show("ACTIVATED", "TEST");
flg = false;
```

とメッセージボックスの表示後に偽をセットするように変更するとうまく動かず、メッセージボックス表示の無限ループになってしまいます。
これは、メッセージボックスが閉じられたことによって発生する２回目以降の`Activated`イベントが`MessageBox.Show`メソッドより処理が戻るよりも前に発生するのが原因です。うっかりすると犯しがちなミスなので注意が必要です。

閑話休題。とりあえずこれで当初の目標は達成できました。しかし、プログラム実行中何度も呼び出されるであろう`Activated`イベントに最初の１回だけしか処理しない処理を記述したり、そのためにわざわざメンバ変数を一つ追加しているのはなんだか美しくないような気がします。

## 再び Loaded イベントを利用してみる

‘Loaded`イベントを利用したときの問題は、イベントが発生したタイミングではまだWindow内のコントロールの描画が完了していないということでした。
そこで、コントロールの描画が完了するのを待つことにします。

コントロールの描画が完了したかどうかは、Windowがアクティベートになかったかどうかを`IsActivate`プロパティを監視することで判断することにします。

やり方はいろいろあるかと思いますが、ここでは`async/await`を利用した非同期処理でWindowがアクティブになるのを待つことにしました。

```csharp
public partial class MainWindow : Window
{
    private async void LoadedProc(object sender, RoutedEventArgs e)
    {
        // WindowがActiveになるまで待つ
        await Task.Run(() =>
        {
            do
            {
                Thread.Sleep(100);
            } while (!Application.Current.Dispatcher.Invoke(() => { return IsActive; }));
        });
        MessageBox.Show("Loaded","TEST");
    }

    public MainWindow()
    {
        InitializeComponent();
        Loaded += LoadedProc;
    }
}
```

`async/await`については

[MSDN:Async および Await を使用した非同期プログラミング ](http://msdn.microsoft.com/ja-jp/library/hh191443.aspx)

などを参考に。`IsActive`プロパティの参照がUIスレッド以外からはできないため、`Application.Current.Dispatcher` を利用してUIスレッド経由で参照するようにしています。

Activated版と比べてプログラムの構造そのものはこちらの方がスッキリしてますが、コードそのものは複雑化していてどちらが美しいかというと微妙なところでしょうか？

何かもっと良い書き方があるような気もしますが、とりあえず今回はここまでにします。


# 追記

## ContentRendered  を利用する

コメント欄でWPFには`ContentRendered `イベントがあるよと教えてもらいました。

[MSDN:Window.ContentRendered イベント](http://msdn.microsoft.com/ja-jp/library/system.windows.window.contentrendered\(v=vs.110\).aspx)

> ウィンドウのコンテンツが描画された後に発生します。 

こんなイベントが存在するのを完全に見落としていましたが、なるほどこれは使えそうです。

```csharp
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        ContentRendered += (s, e) => { MessageBox.Show("ContentRendered", "TEST"); };
    }
}
```

↑のようなコードを書いて試してみたところ期待通りの動作を確認できました。

目的はこれですっきりした形で果たせそうです。

## なんだか腑に落ちないので調べてみたら余計に混乱した

とりあえず目的は達したのですが、なんだか腑に落ちないところがあるので調べてみたら、余計に混乱する結果になりました。

とりあえずイベントの発生順序を

```csharp
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        Activated += (s, e) => { System.Diagnostics.Debug.WriteLine("Activated");  };
        Loaded += (s, e) => { System.Diagnostics.Debug.WriteLine("Loaded");   };
        ContentRendered += (s, e) => { System.Diagnostics.Debug.WriteLine("ContentRendered");  };
    }
}
```

というコードを書いて確認してみたところ、

1. Activated 
2. Loaded 
3. ContentRendered 

の順番で発生することが判りました。

+ Activated でメッセージボックス表示→Windowのコンテンツは表示されている。
+ Loaded でメッセージボックス表示→Windowのコンテンツは表示されていない。

という状況からてっきり`Loaded`イベント→`Activated`イベントの順番で発生すると思い込んでいましたがとんだ勘違いだったようです。

コンテンツの描画は、`Loaded`イベントから`ContentRendered`イベントの間に行われます。したがって`Activated`イベントの発生時にはコンテンツの描画が行われていません。
`Activated`イベントでメッセージボックスを表示させたとき、コンテンツの描画が完了しているようにみえたのは、`MessageBox.Show` はメッセージループをブロックしないため、メッセージボックスを表示しているその裏で、`Loaded`イベント→`ContentRendered`イベントと処理が粛々と進行していた所為なのでした。

`Loaded`イベントでメッセージボックスを表示させた場合も、裏で`ContentRendered`イベントは発生しています。しかし何故かWindowのコンテンツは表示されません。
メッセージは処理されていても、`Loaded`イベントのハンドラはブロックされているので、それが原因かもと思い、`MessageBox.Show`ではなく、別のWindowの派生クラスのオブジェクトを生成し`ShowDialog`で表示させてみたところ、こんどは正常にWindowのコンテンツが表示されました。また、正常に動作しているようにみえた`async/await`版のコードですが、`Task.Run`に渡すラムダ式の中身を空にしても動くことが判りました。正常に動作しているようにみえたのは別スレッドで処理待ちしていたからではなく、`await`の時点で処理が非同期実行に移り、呼び出し元に処理が返されたからと考えるべきでしょう。

要するに問題は

**`Loaded`イベント内で`MessageBox.Show`でメッセージボックスを表示した場合、Windowのコンテンツの描画がされない。**

ということのようです。

何なのでしょう？　環境依存の問題でしょうか？　他の環境がないので確認できませんが。

ちなみに環境は、Windows8.1 / .Net Framework 4.5 / Visual C# 2012 です。