Freezable オブジェクトの概要

ここでは、アプリケーションのパフォーマンス向上のための特別な機能を備えた Freezable オブジェクトの、効率的な使用方法および作成方法について説明します。 フリーズ可能オブジェクトには、ブラシ、ペン、変換、ジオメトリ、アニメーションなどがあります。

このトピックは、次のセクションで構成されています。

Freezable (フリーズ可能) とは

Freezable は、非フリーズとフリーズの 2 つの状態を持つ特殊な型のオブジェクトです。 フリーズしていない Freezable は、他のオブジェクトと同様に動作しているように見えます。 Freezable をフリーズすると、変更できなくなります。

Freezable には、オブジェクトの変更を監視者に通知するための Changed イベントがあります。 Freezable をフリーズすると、変更通知のためのリソースが不要になるので、パフォーマンスが向上します。 また、フリーズされた Freezable はスレッド間で共有できますが、フリーズされていない Freezable は共有できません。

Freezable クラスはさまざまな目的に使用できますが、Windows Presentation Foundation (WPF) の Freezable オブジェクトのほとんどは、グラフィックス サブシステムに関連するものです。

Freezable クラスを使用すると、特定のグラフィックス システムのオブジェクトが使いやすくなり、アプリケーション パフォーマンスを向上させることができます。 Freezable から継承するクラスには、BrushTransformGeometry などがあります。 このようなクラスにはアンマネージ リソースが格納されているので、システムはオブジェクトが変更されたかどうかを監視する必要があります。元のオブジェクトに変更があった場合は、対応するアンマネージ リソースを更新します。 実際にはグラフィックス システムのオブジェクトが変更されることがなくても、万一の変更に備えて、システムはオブジェクトの監視用にリソースの一部を消費しなければなりません。

たとえば、SolidColorBrush ブラシを作成して、ボタンの背景の塗りつぶしに使用するとします。

            Dim myButton As New Button()
            Dim myBrush As New SolidColorBrush(Colors.Yellow)
            myButton.Background = myBrush
Button myButton = new Button();
SolidColorBrush myBrush = new SolidColorBrush(Colors.Yellow);
myButton.Background = myBrush;  

ボタンがレンダリングされるとき、WPF のグラフィックス サブシステムは指定された情報を使用してピクセルのグループを塗りつぶし、ボタンの外観を作成します。 ボタンの描画方法を示すために単色のブラシを指定したとしても、この単色のブラシが実際に塗りつぶしを行うわけではありません。 グラフィックス システムによって、ボタンおよびブラシの高速かつ低レベルのオブジェクトが生成され、実際に画面に表示されます。

ブラシに変更を加える場合は、このような低レベルのオブジェクトの再生成が必要です。 Freezable クラスとは、ブラシに、自身に対応する生成済みの低レベル オブジェクトを検出して自身が変更されたときにそのオブジェクトを更新するという能力を与えるものです。 この機能が有効化されているときは、ブラシは "フリーズされていない" といいます。

フリーズ可能オブジェクトの Freeze は、この自己更新機能を無効化するためのメソッドです。 このメソッドを使用すると、ブラシを "フリーズ" 状態、つまり変更不可能にすることができます。

メモメモ

すべての Freezable オブジェクトをフリーズできるわけではありません。InvalidOperationException がスローされることを避けるには、Freezable オブジェクトをフリーズする前に CanFreeze プロパティの値を調べて、フリーズ可能かどうかを判断します。

            If myBrush.CanFreeze Then
                ' Makes the brush unmodifiable.
                myBrush.Freeze()
            End If
if (myBrush.CanFreeze)
{
    // Makes the brush unmodifiable.
    myBrush.Freeze();
}

フリーズ可能オブジェクトを変更する必要がなくなった場合は、フリーズすることによってパフォーマンスが向上します。 この例では、ブラシをフリーズすると、グラフィックス システムがブラシの変更を監視する必要はなくなります。 グラフィックス システムはこのブラシが変更されないことを認識しているので、その他の最適化も実行できるようになります。

メモメモ

便宜を図るために、フリーズ可能オブジェクトは、明示的にフリーズされない限り非フリーズ状態を維持します。

Freezable の使用

フリーズされていないフリーズ可能オブジェクトの使用は、その他の種類のオブジェクトの使用に似ています。 次のコード例では、SolidColorBrush の色をボタンの背景塗りつぶしに使用した後で、黄色から赤に変更します。 グラフィックス システムが内部的処理を行い、画面が次回更新されたときにボタンを黄色から赤に自動的に変更します。

            Dim myButton As New Button()
            Dim myBrush As New SolidColorBrush(Colors.Yellow)
            myButton.Background = myBrush


            ' Changes the button's background to red.
            myBrush.Color = Colors.Red
Button myButton = new Button();
SolidColorBrush myBrush = new SolidColorBrush(Colors.Yellow);
myButton.Background = myBrush;  


// Changes the button's background to red.
myBrush.Color = Colors.Red;

Freezable のフリーズ

Freezable を変更不可にするには、そのFreeze メソッドを呼び出します。 フリーズ可能オブジェクトを格納するオブジェクトをフリーズすると、格納されているオブジェクトもフリーズされます。 たとえば、PathGeometry をフリーズすると、格納されている数字およびセグメントもフリーズされます。

次のいずれかに当てはまる場合は、Freezable をフリーズすることはできません。

  • アニメーション化されたプロパティ、またはデータ バインドされたプロパティがある。

  • プロパティが動的リソースによって設定されている (動的リソースの詳細については、「リソースの概要」を参照してください)。

  • Freezable に格納されているサブオブジェクトがフリーズ不可能である。

これらの条件に該当しない場合に、それ以降 Freezable を変更しないことが明らかならば、既に説明したように、パフォーマンスを向上させるためにオブジェクトをフリーズしてください。

フリーズ可能オブジェクトの Freeze メソッドをいったん呼び出すと、そのオブジェクトは変更できなくなります。 フリーズされたオブジェクトを変更しようとすると、InvalidOperationException がスローされます。 次のコードでは、ブラシをフリーズした後に変更しようとしているので、例外がスローされます。


            Dim myButton As New Button()
            Dim myBrush As New SolidColorBrush(Colors.Yellow)

            If myBrush.CanFreeze Then
                ' Makes the brush unmodifiable.
                myBrush.Freeze()
            End If

            myButton.Background = myBrush

            Try

                ' Throws an InvalidOperationException, because the brush is frozen.
                myBrush.Color = Colors.Red
            Catch ex As InvalidOperationException
                MessageBox.Show("Invalid operation: " & ex.ToString())
            End Try


            Button myButton = new Button();
            SolidColorBrush myBrush = new SolidColorBrush(Colors.Yellow);          

            if (myBrush.CanFreeze)
            {
                // Makes the brush unmodifiable.
                myBrush.Freeze();
            }

            myButton.Background = myBrush;  

            try {

                // Throws an InvalidOperationException, because the brush is frozen.
                myBrush.Color = Colors.Red;
            }catch(InvalidOperationException ex)
            {
                MessageBox.Show("Invalid operation: " + ex.ToString());
            }

この例外がスローされるのを避けるには、IsFrozen メソッドを使用して、Freezable がフリーズされているかどうかを判断します。


            Dim myButton As New Button()
            Dim myBrush As New SolidColorBrush(Colors.Yellow)

            If myBrush.CanFreeze Then
                ' Makes the brush unmodifiable.
                myBrush.Freeze()
            End If

            myButton.Background = myBrush


            If myBrush.IsFrozen Then ' Evaluates to true.
                ' If the brush is frozen, create a clone and
                ' modify the clone.
                Dim myBrushClone As SolidColorBrush = myBrush.Clone()
                myBrushClone.Color = Colors.Red
                myButton.Background = myBrushClone
            Else
                ' If the brush is not frozen,
                ' it can be modified directly.
                myBrush.Color = Colors.Red
            End If



            Button myButton = new Button();
            SolidColorBrush myBrush = new SolidColorBrush(Colors.Yellow);

            if (myBrush.CanFreeze)
            {
                // Makes the brush unmodifiable.
                myBrush.Freeze();
            }            

            myButton.Background = myBrush;


            if (myBrush.IsFrozen) // Evaluates to true.
            {
                // If the brush is frozen, create a clone and
                // modify the clone.
                SolidColorBrush myBrushClone = myBrush.Clone();
                myBrushClone.Color = Colors.Red;
                myButton.Background = myBrushClone;
            }
            else
            {
                // If the brush is not frozen,
                // it can be modified directly.
                myBrush.Color = Colors.Red;
            }


前のコード例では、フリーズされたオブジェクトの変更可能なコピーを、Clone メソッドを使用して作成しました。 複製の詳細については、次のセクションで説明します。

メモ   フリーズされたフリーズ可能オブジェクトはアニメーション化できないので、Storyboard を使用してアニメーション化しようとすると、フリーズされた Freezable オブジェクトの変更可能な複製がアニメーション システムによって自動的に作成されます。 複製によって生じるパフォーマンス オーバーヘッドを回避するために、オブジェクトをアニメーション化する場合は非フリーズ状態のままにしてください。 ストーリーボードを使用したアニメーションの詳細については、「ストーリーボードの概要」を参照してください。

マークアップからのフリーズ

マークアップで宣言された Freezable オブジェクトをフリーズするには、PresentationOptions:Freeze 属性を使用します。 次の例では、SolidColorBrush を、フリーズされたページ リソースとして宣言します。 次に、このブラシを使用してボタンの背景を設定します。

<Page 
  xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:PresentationOptions="https://schemas.microsoft.com/winfx/2006/xaml/presentation/options" 
  xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
  mc:Ignorable="PresentationOptions">

  <Page.Resources>

    <!-- This resource is frozen. -->
    <SolidColorBrush 
      x:Key="MyBrush"
      PresentationOptions:Freeze="True" 
      Color="Red" />
  </Page.Resources>


  <StackPanel>

    <Button Content="A Button" 
      Background="{StaticResource MyBrush}">
    </Button>

  </StackPanel>
</Page>

Freeze 属性を使用するには、表示オプション名前空間 https://schemas.microsoft.com/winfx/2006/xaml/presentation/options に割り当てる必要があります。 この名前空間を割り当てるときの推奨プレフィックスは PresentationOptions です。

xmlns:PresentationOptions="https://schemas.microsoft.com/winfx/2006/xaml/presentation/options" 

すべての XAML リーダーがこの属性を認識するとは限らないので、mc:Ignorable 属性を使用して、Presentation:Freeze 属性が無視可能であると指定することをお勧めします。

  xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
  mc:Ignorable="PresentationOptions"

詳細については、mc:Ignorable 属性のページを参照してください。

Freezable の "フリーズ解除"

Freezable をフリーズすると、変更やフリーズ解除はできなくなりますが、Clone メソッドまたは CloneCurrentValue メソッドを使用すると、フリーズされていない複製を作成できます。

次の例では、ブラシを使用してボタンの背景を設定してから、そのブラシをフリーズします。 Clone メソッドを使用して、ブラシのフリーズされていない複製を作成します。 この複製を変更し、使用して、ボタンの背景を黄色から赤に変更します。

            Dim myButton As New Button()
            Dim myBrush As New SolidColorBrush(Colors.Yellow)

            ' Freezing a Freezable before it provides
            ' performance improvements if you don't
            ' intend on modifying it. 
            If myBrush.CanFreeze Then
                ' Makes the brush unmodifiable.
                myBrush.Freeze()
            End If


            myButton.Background = myBrush

            ' If you need to modify a frozen brush,
            ' the Clone method can be used to
            ' create a modifiable copy.
            Dim myBrushClone As SolidColorBrush = myBrush.Clone()

            ' Changing myBrushClone does not change
            ' the color of myButton, because its
            ' background is still set by myBrush.
            myBrushClone.Color = Colors.Red

            ' Replacing myBrush with myBrushClone
            ' makes the button change to red.
            myButton.Background = myBrushClone
Button myButton = new Button();
SolidColorBrush myBrush = new SolidColorBrush(Colors.Yellow);

// Freezing a Freezable before it provides
// performance improvements if you don't
// intend on modifying it. 
if (myBrush.CanFreeze)
{
    // Makes the brush unmodifiable.
    myBrush.Freeze();
}


myButton.Background = myBrush;  

// If you need to modify a frozen brush,
// the Clone method can be used to
// create a modifiable copy.
SolidColorBrush myBrushClone = myBrush.Clone();

// Changing myBrushClone does not change
// the color of myButton, because its
// background is still set by myBrush.
myBrushClone.Color = Colors.Red;

// Replacing myBrush with myBrushClone
// makes the button change to red.
myButton.Background = myBrushClone;
メモメモ

使用する Clone メソッドにかかわらず、アニメーションが新しい Freezable にコピーされることはありません。

Clone メソッドと CloneCurrentValue メソッドは、フリーズ可能オブジェクトの深いコピーを生成します。 フリーズ可能オブジェクトに、他のフリーズされたフリーズ可能オブジェクトが格納されている場合は、格納されるオブジェクトも複製されて、変更可能になります。 たとえば、フリーズされた PathGeometry を複製して変更可能にする場合は、格納されている数字およびセグメントもコピーされ、変更可能な状態になります。

独自の Freezable クラスの作成

Freezable から派生するクラスには、次の機能が与えられます。

  • 特殊な状態 : 読み取り専用 (フリーズ) 状態と書き込み可能状態。

  • スレッド セーフ : フリーズされた Freezable は、スレッド間で共有できます。

  • 詳細な変更通知 : 他の DependencyObject とは異なり、Freezable オブジェクトは、サブプロパティの値が変更されたときに変更通知を提供します。

  • 容易な複製 : Freezable クラスは、深い複製を作成するためのメソッドを既に実装しています。

Freezable は、DependencyObject の一種であるので、依存関係プロパティ システムを使用します。 作成するクラスのプロパティが依存関係プロパティである必要はありませんが、Freezable クラスは依存関係プロパティを想定して設計されているので、依存関係プロパティを使用すれば、プログラミングに必要なコードの量が少なくて済みます。 依存関係プロパティ システムの詳細については、「依存関係プロパティの概要」を参照してください。

Freezable のすべてのサブクラスで CreateInstanceCore メソッドをオーバーライドする必要があります。 作成するクラスで、すべてのデータについて依存関係プロパティを使用する場合は、これで完了です。

クラスに依存関係プロパティ以外のデータ メンバーが含まれている場合は、次のメソッドもオーバーライドする必要があります。

また、依存関係プロパティではないデータ メンバーへのアクセスおよび書き込みについては、次のルールに従う必要があります。

  • 依存関係プロパティ以外のデータ メンバーを読み取る API の開始時に、ReadPreamble メソッドを呼び出します。

  • 依存関係プロパティ以外のデータ メンバーを書き込む API の開始時に、WritePreamble メソッドを呼び出します (API の中で WritePreamble を呼び出した後は、依存関係プロパティ以外のデータ メンバーを読み取る場合も、ReadPreamble を改めて呼び出す必要はありません)。

  • 依存関係プロパティ以外のデータ メンバーに書き込みを行うメソッドを終了する前に、WritePostscript メソッドを呼び出します。

作成するクラスに含まれる、依存関係プロパティ以外のデータ メンバーの中に DependencyObject オブジェクトがある場合は、そのメンバーの値を変更するたびに OnFreezablePropertyChanged メソッドも呼び出す必要があります (そのメンバーを null に設定する場合も)。

メモメモ

オーバーライドする Freezable の各メソッドの先頭で基本実装を呼び出すことが非常に重要です。

カスタム Freezable クラスの例については、カスタム アニメーションのサンプルを参照してください。

参照

参照

Freezable

概念

依存関係プロパティの概要

カスタム依存関係プロパティ

その他の技術情報

カスタム アニメーションのサンプル