検索
連載

第7回 WPF UI要素の基礎とレイアウト用のパネルを学ぼう連載:WPF入門(1/2 ページ)

WPFが標準提供する各ユーザー・インターフェイス要素を解説。今回は、コントロール共通の基底クラスと、柔軟なレイアウトを実現するパネルを説明する。

Share
Tweet
LINE
Hatena
連載:WPF入門
業務アプリInsider/Insider.NET

powered by Insider.NET

「連載:WPF入門」のインデックス

連載目次

2011/01/21 更新

 前回までで、XAML/WPFの基礎的な仕組みについての説明を終え、今回からは個々のUI(ユーザー・インターフェイス)要素の説明に入っていく。まず今回は、WPFのUI要素を表すクラスの継承階層と、レイアウト用のパネルについて説明する。

■UI要素の継承階層

 WPFのUI要素についてMSDNライブラリで調べて見ると、クラスの継承階層の深さに驚くかもしれない。例えば、Buttonクラス(System.Windows.Controls名前空間)のページを見てみると、Figure 1に示すような継承階層が書かれている。


Figure 1: Buttonクラスの継承階層

 WPFのUI要素も.NET Frameworkのクラスとして作られているので、Objectクラス(=すべてのクラスに共通の基底クラス)を継承しているのは当然として、その下に多くのクラスが並んでいる。コントロールの共通基底クラスであるControlクラス(System.Windows.Controls名前空間)より上に5つのクラスが並んでいるが、これらの5つについて簡単に説明しておこう。

(1)DisptacherObjectクラス

 DispatcherObjectクラス(System.Windows.Threading名前空間)は、単一のスレッドのみが直接操作可能なオブジェクトを表すクラスで、ほかのスレッドからは「ディスパッチャ(Dispatcher: 配送係り)」と呼ばれるオブジェクトを介してアクセスしなければならない。

 WPFでは(WPFに限らず、グラフィックを扱うほとんどのフレームワークでは)、パフォーマンス上の理由から、UI要素に対する操作を単一のスレッド上で行わなければならない(このようなスレッドを「UIスレッド」と呼ぶ)。

 ところが一方で、UIスレッド上で時間のかかる(描画以外の)処理をしてしまうと、アプリケーションそのものをフリーズさせてしまうことになる。このような処理はUIスレッドとは別のスレッドで行わなければならない。

 実際に時間のかかる処理を別スレッドで行い、その結果に応じてUI要素の状態を変更したい場合には、別スレッドからUIスレッドに処理を戻す必要があり、この作業を担うのがディスパッチャである(つまり、「作成元のスレッドでこの処理を実行してほしい」というメッセージを配送する)。Windowsフォームでは、コントロール自身がこの役割を担っていたが、WPFではDispatcherクラス(System.Windows.Threading名前空間)に役割を分離している(DispatcherObjectクラスのDispatcherプロパティを通して利用)。

 例えば、ボタンが押されたときに何か時間のかかる処理を開始し、処理中はボタンを押せなくしたいというような場合には、List 1に示すようなコードを書く必要がある。

private void StartButton_Click(object sender, RoutedEventArgs e)
{
  var dispatcher = this.Dispatcher;
 
  Action asyncWork = () =>
  {
    HeavyWork();
 
    dispatcher.Invoke(new Action(() =>
    {
      // 処理が完了したらボタンを利用可能に戻す
      this.StartButton.IsEnabled = true;
    }));
  };
 
  // いったんボタンを利用不可にする
  this.StartButton.IsEnabled = false;
 
  asyncWork.BeginInvoke(null, null);
}
 
private static void HeavyWork()
{
  // 時間のかかる処理を Sleep を使って疑似的に作る
  System.Threading.Thread.Sleep(3000);
}

Private Sub StartButton_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles StartButton.Click

  Dim dispatcher = Me.Dispatcher

  Dim asyncWork As Action =
    Sub()
      HeavyWork()

      dispatcher.Invoke(
        New Action(
           Sub()
             ' 処理が完了したらボタンを利用可能に戻す
             Me.StartButton.IsEnabled = True
           End Sub))
    End Sub
  ' いったんボタンを利用不可にする
  Me.StartButton.IsEnabled = False

  asyncWork.BeginInvoke(Nothing, Nothing)

End Sub

Private Shared Sub HeavyWork()

  ' 時間のかかる処理を Sleep を使って疑似的に作る
  System.Threading.Thread.Sleep(3000)

End Sub

List 1: ディスパッチャの利用例(上:C#、下:VB)
XAMLコード中にStartButtonという名前の<Button>要素があり、ClickイベントにButton_Clickメソッドが結び付けられているものとする。

(2)DependencyObjectクラス

 連載第3回で説明したように、WPFでは「依存関係プロパティ」という独自の「値の保持機構」を持っている。DependencyObjectクラス(System.Windows名前空間)は、この依存関係プロパティを利用するための共通基底クラスとなる。

(3)Visualクラス

 Visualクラス(System.Windows.Media名前空間)は、画面への描画にかかわる要素の共通基底クラスとなる。

 WPFの理念の1つに、データ駆動によるUIの作成というものがある。すなわち、データとしてUI記述を行い、フレームワークの内部でデータを実際の描画命令に置き換えてUIを表示する。

 例えば、画面上に円を描画したい場合、WindowsフォームではList 2に示すように、(基本的に)ペイント・イベントを拾って描画のための手続きを書いていた。

protected override void OnPaint(PaintEventArgs e)
{
  var g = e.Graphics;
  var pen = new Pen(Color.Blue);
  g.DrawEllipse(pen, 0, 0, 100, 100);
}

Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
  Dim g = e.Graphics
  Dim pen = New Pen(Color.Blue)
  g.DrawEllipse(pen, 0, 0, 100, 100)
End Sub

List 2: Windowsフォームでの円の描画(上:C#、下:VB)

 これに対してWPFでは、List 3のC#/VBコード、もしくは、List 4のXAMLコードに示すように、データの宣言によって描画を(単に表示だけでなく、ヒット・テストなども)制御する。

this.Content = new Ellipse
{
  Width = 100,
  Height = 100,
  Stroke = new SolidColorBrush(Colors.Blue),
  StrokeThickness = 1,
};

Me.Content = New Ellipse With {
  .Width = 100,
  .Height = 100,
  .Stroke = New SolidColorBrush(Colors.Blue),
  .StrokeThickness = 1
}

List 3: WPFでの円の描画(分離コード中での記述)(上:C#、下:VB)

<Ellipse Width="100" Height="100"
         Stroke="Blue" StrokeThickness="1" />

List 4: WPFでの円の描画(XAMLでの記述)

 このような描画に関連するデータに応じて、以下のような処理を行うのがVisualクラスの役割である。

  • 画面への実際の表示
  • (拡大・縮小、回転などの)変形
  • クリッピング
  • ヒット・テスト(=要素がクリックされたかどうかの判定)

(4)UIElementクラス

 UIElementクラス(System.Windows名前空間)はUI要素として必要な最低限の機能を備える共通基底クラスで、以下のような処理を行う。

  • レイアウト(=要素のサイズや配置の決定)用の抽象メンバ
  • ユーザー入力への応答
  • ルーティング・イベントの発生
  • 最低限の(一般的な)アニメーション

(5)FrameworkElementクラス

 FrameworkElementクラス(System.Windows名前空間)は、UIElementクラスに以下のようなWPF固有の機能を追加したものである。

  • レイアウトの具体的な実装
  • スタイル
  • プロパティ値の包含継承
  • WPF固有のアニメーション(ストーリーボードなど)

 WPFのUI要素となるクラスのほとんどは、このFrameworkElementクラスを継承している。UIElementクラスとFrameworkElementクラスは、将来的な拡張や第三者によるフレームワーク開発を想定して2層に分けられている。このため、アセンブリも、汎用的な利用を想定したPresentationCoreアセンブリ(UIElementクラスを含む)と、WPF固有の機能を集めたPresentationFrameworkアセンブリ(FrameworkElementクラスを含む)の2層に分かれている。

【コラム】画面描画の共通基底クラスに関する補足: Freezableクラス

 ブラシやアニメーションのタイムラインなど、描画に関連はするが、直接画面に描画されるわけではないもの(つまり、Visualクラスには適さないもの)もあり、このような要素の場合はFreezableクラス(System.Windows名前空間)を継承する。

 Freezableクラスは、オブジェクトの状態を固定(freeze)して読み取り専用にできる(書き込み可能なものを途中から読み取り専用に切り替えられるので、不変(immutable)ではなく固定可能(freezable)と呼ぶ)。

 DispatcherObjectクラスの項で説明したとおり、パフォーマンス上の理由から、グラフィックに関連する要素は単一のスレッドからのみアクセス可能になっている場合が多いが、読み取り専用であればほかのスレッドからアクセスされても問題は生じない。Freezableクラスの場合、オブジェクトが固定された状態(frozen)のときだけ、ほかのスレッドからのアクセスを許可している。


 FrameworkElementクラスの下にはさまざまな派生クラスが存在する。今回は、まず、レイアウト用のパネルについて説明していこう。

Copyright© Digital Advantage Corp. All Rights Reserved.

       | 次のページへ
ページトップに戻る