やる気駆動型エンジニアの備忘録

WPF(XAML+C#)の話題を中心に.NET/Android/CI やたまに趣味に関するブログです

WPFパフォーマンス改善その1. 初期表示(描画)速度の改善策

初期表示(描画)速度の改善策
ここでは初期表示、つまり画面をインスタンス化してから描画されるまでの時間を短縮するための改善策や
画面の再描画処理時間の短縮方法を記述します。


【前提条件】
初期表示が遅くなる原因は複数存在するのが普通です。
このページに記述している原因もそうですが、開発環境独自の原因もあります。
改善策を試す前に以下の点に注意して下さい。


・Binding Exception を解消する。
 ⇒Visual Studioの[出力]ウィンドウに表示されます。
  このException は、データバインドの失敗を意味していてそれ自体は例外を発行しません。
  気が付きにくいため見落としがちですが、これを放置しておくと予期せぬ事象が発生します。
  また、アプリケーションの動作が重くなる原因にもなります。
  見つけ次第解決して下さい。


・初期表示時にアセンブリがロードされる。
 ⇒こちらもVisual Studio の[出力]ウィンドウに表示されます。
  .Net アプリケーションを実行する際に必要なアセンブリは、そのアセンブリが必要になった際にロード処理が走ります。
  そのため画面の初期表示時に多くのアセンブリが必要になれば、必然的にロードに時間がかかり遅くなります。
  画面を2回表示し、2回めの方が早いことがあるのはこれが原因です。

1.再配置処理回数を削減する。

  WPFは、基本的に画面上の各要素は相対的に座標・サイズを決定します。
  コントロールの"HorizontalAlignment" や"Width" に"Auto" を設定できるのがその証拠です。
  こうすることでユーザーが画面サイズを変更したり、解像度がことなるディスプレイでも
  画面の表示内容が見切れない作りにすることが可能になっています。

  しかし、この際配置の仕組みは、Xaml 上の親要素から子要素へ何度も行き来してサイズを調整し、
  最終的に描画されるというものです。
  (詳しくは前頁の[WPFチューニング戦記]を御覧ください。)
  この再配置処理に時間がかかれば、当然表示するまでの時間も遅くなります。

  これを回避するには以下の方法を検討して下さい。


・サイズを固定にする。
  ⇒Width/Height は固定値にします。


・親パネルをCanvas にする。
  ⇒絶対座標で指定することで描画座標の再計算をしなくなります。


・文字や図形をコードで描画する。
  ⇒Xaml ではなく、コード側で直接描画する方法です。
   詳細は、[コードで文字や図形を描画]を参照して下さい。
  

2.独自コントロールをUserControl からカスタムコントロールへ変更する。

  WPFでコントロールを作成する方法は、主に以下の2つです。
  (1) UserControl でコントロールを複合的に使用する。
  (2) カスタムコントロールで外観と仕組みを自分で実装する。
    (あるいは既存のコントロールを継承して機能を追加する。)
  他にも、表示のみであればDataTemplate を使用する事もできます。

  基本的なことですが、コントロールクラスは派生するほど機能が増加・複雑化し、
  1.の再配置処理に時間がかかるようになります。
  UserControl は、Window と同様にView を持つため再配置処理に時間がかかるようです。
  UserControl の数が増えるほど、内部の要素が複雑になるほどそれは顕著になります。

  これを防ぐにはカスタムコントロールを実装して下さい。
  カスタムコントロールを実装するためのプロジェクトはVisual Studio に用意されています。
  (プロジェクトの追加メニューから"WPF カスタム コントロール ライブラリ" という項目があります。)
  間違っても通常のプロジェクトにカスタムコントロールを追加しないようにして下さい。

3.依存関係プロパティを正しく使用する。

< 依存関係プロパティの実装について >
  依存関係プロパティはデータバインドを使用するための仕組みで、
  ユーザーコントロールのプロパティは基本的にこれを実装します。
  通常のプロパティでも初回のみデータバインドは働きますが、
  以降のプロパティ値の変化には対応できません。
  また、通常のプロパティは実行時にデータバインドを使用する際に、
  リフレクションを利用しているため依存関係プロパティよりも動作が遅い(?)らしいです。
  (参考: https://msdn.microsoft.com/ja-jp/library/bb613546.aspx

  ⇒コントロールのプロパティは依存関係プロパティで実装して下さい。


< 通知方向(Mode)について >
  
  View にデータバインドを設定する際に方向(Mode)という項目があります。
  通常、コントロールの入力をViewModel に通知したい場合は"TwoWay"、
  ViewModel の値を受け取るだけの場合は"OneWay"、
  初回だけの場合は"OneTime"、コントロールの入力のみViewModel に通知する場合は"OneWayToSource" を設定します。

  このMode に不要な設定を施すことで、データバインドに時間がかかる場合があります。
  例えば、ViewModel の値を受け取るだけなのに"TwoWay" を設定していると、
  コントロールからの入力もデータバインドの動作対象となり、処理時間が余計にかかります。
  1つ1つは大した時間ではありませんが、コレクション(ObservableCollection)の場合は
  無視できないレベルで動作が遅くなる可能性があります。

  ⇒データバインドの方向(Mode)が正しく設定されているか検討して下さい。

4.描画速度を向上させることのできるプロパティ

  ユーザーコントロールの基底クラス"Control" や"FrameworkElement" には、
  値を設定することで描画速度を向上させることのできるプロパティがいくつか存在します。
  Xaml の相対的レイアウト設定のメリットを無くすものもありますが、
  以下のプロパティを設定することを検討して下さい。


・IsHitTestVisible(デフォルト:false)
   true を設定するとコントロールの操作ができなくなります。
   マウスオーバー及びクリックなどの操作を一切受け付けません。
   操作が不要なコントロールにはこれを設定することで不要なイベントが着火しなくなり、
   動作速度の改善が見込めます。


・ClipToBounds(デフォルト:false)
   true を設定するとコントロールの子要素が、自分自身の描画範囲を超えた領域に描画されなくなります。
   以下のページの"クリッピング"の章を参照して下さい。
   (参考:http://www.kanazawa-net.ne.jp/~pmansato/wpf/wpf_layout.htm
   描画領域が限定されるため、動作速度の改善が見込めます。
   ただし、再配置処理は発生する点には注意して下さい。


・Width/Height(デフォルト:Auto)
   固定値に設定すると再配置処理回数を削減することができます。
   同様に"MaxWidth", "MinWidth", "MaxHeight", "MinHeight" も固定値に設定することを推奨します。


・ZIndex(デフォルト:0)
   画面に描画する順番を設定するためのプロパティです。
   0~255 までの値を設定し、数値が大きいほど全面に描画されます。
   WebBrowser やWindowsFormsHost など、一部のコントロールには効き目がありません。
   WPF は、Xaml で記述した順番(上から下)に描画されるため、あまり使用する機会は無いはずです。
   このプロパティを安易に使用する場合は、レイアウト方法が間違っている可能性が殆どです。

5.コードで文字や図形を描画

< 文字を描画 >
  WPF で文字列を表示する方法は主に以下の2種類です。
   ・Label を使用する。
   ・TextBlock を使用する。
  描画速度はTextBlock > Label です。(Label はTextBlock を内部に持っているコントロールだからです。)
  しかし、Xaml を使用している以上、再配置処理が発生するためTextBlock にもそれなりにコストがかかります。

  これを回避するには、コード側で文字を描画します。
  文字の描画にはFormattedText を使用します。
  FormattedText クラスのリファレンスは以下を参照して下さい。
  (参考: https://msdn.microsoft.com/ja-jp/library/system.windows.media.formattedtext(v=vs.110).aspx
  FormattedText の実装方法は以下を参照して下さい。
  (参考: https://msdn.microsoft.com/ja-jp/library/ms752098(v=vs.110).aspx
  また、自前で描画することにより、レイアウト再配置処理が発生しません。
  動作環境に依存しますが、FormattedText はTextBlock の約8倍の速度で描画が可能です。
  

< 図形を描画 >
  WPF のコントロールクラスにShape クラスというものがあります。
  これは図形を描画するためのクラスですが、非常にコストが高いです。
  Shape の派生クラスには以下の様な物があります。
   Ellipse, Rectangle, Triangle, Path ...
  インスタンスの生成から描画まで、最も時間のかかるのがこのShape 系コントロールです。
  これを数百個描画しようとするとあっという間にCPU 使用率が100%に到達します。

  これを回避するには、コード側で図形を描画します。
  描画方法については、以下のページが非常に参考になります。
  (参考: http://www.wisdomsoft.jp/430.html
  また、自前で描画することにより、レイアウト再配置処理が発生しません。
  おそらく描画速度が最も早くなる要因はこれになることが最も多いはずです。