効果のパラメーターを添付プロパティとして渡す

添付プロパティは、実行時のプロパティの変更に応答するエフェクトのパラメーターの定義に使用できます。 この記事では、添付プロパティを使用してエフェクトにパラメーターを渡す方法と、実行時にパラメーターを変更する方法を示します。

実行時のプロパティの変更に応答するエフェクトのパラメーターを作成するプロセスは、次のとおりです。

  1. エフェクトに渡される各パラメーターの添付プロパティが含まれる static クラスを作成します。
  2. 追加の添付プロパティをクラスに追加します。そのクラスは、添付されるコントロールに対するエフェクトの追加または削除をコントロールするために使用されます。 この添付プロパティが、そのプロパティの値が変更されるときに実行される propertyChanged デリゲートを登録することを確認します。
  3. 各添付プロパティの static getter と setter を作成します。
  4. エフェクトを追加または削除する propertyChanged デリゲートにロジックを実装します。
  5. エフェクトから命名された static クラス内に、入れ子になったクラスを実装します。これは RoutingEffect クラスをサブクラス化します。 コンストラクターについては、解決グループ名の連結を渡す基底クラスのコンストラクターと、各プラットフォーム固有のエフェクトのクラスで指定された一意の ID を呼び出します。

その後、添付プロパティとプロパティの値を適切なコントロールに追加することで、パラメーターをエフェクトに渡すことができます。 さらに、パラメーターは実行時に新しい添付プロパティの値を指定することで変更できます。

Note

添付プロパティは特殊な型のバインド可能なプロパティで、1 つのクラスで定義される一方で他のオブジェクトに添付され、XAML 内でピリオドで区切られたクラスとプロパティ名が含まれる属性として認識されます。 詳しくは、「Attached Properties」(添付プロパティ) をご覧ください。

このサンプル アプリケーションは、Label コントロールによって表示されるテキストに影を追加する ShadowEffect を示しています。 さらに、実行時に影の色を変更できます。 次の図に、サンプル アプリケーション内の各プロジェクトの役割と、それらの関係を示します。

影エフェクト プロジェクトの責任

HomePage 上の Label コントロールが、各プラットフォーム固有のプロジェクト内の LabelShadowEffect によってカスタマイズされます。 パラメーターは ShadowEffect クラス内の添付プロパティを通じて各 LabelShadowEffect に渡されます。 各プラットフォームの PlatformEffect クラスから、各 LabelShadowEffect クラスが派生します。 これにより、次のスクリーンショットに示すように、Label コントロールによって表示されるテキストに影が追加されます。

各プラットフォーム上の影エフェクト

エフェクトのパラメーターを作成する

次のコード例に示すように、エフェクトのパラメーターを表すには static クラスを作成する必要があります。

public static class ShadowEffect
{
  public static readonly BindableProperty HasShadowProperty =
    BindableProperty.CreateAttached ("HasShadow", typeof(bool), typeof(ShadowEffect), false, propertyChanged: OnHasShadowChanged);
  public static readonly BindableProperty ColorProperty =
    BindableProperty.CreateAttached ("Color", typeof(Color), typeof(ShadowEffect), Color.Default);
  public static readonly BindableProperty RadiusProperty =
    BindableProperty.CreateAttached ("Radius", typeof(double), typeof(ShadowEffect), 1.0);
  public static readonly BindableProperty DistanceXProperty =
    BindableProperty.CreateAttached ("DistanceX", typeof(double), typeof(ShadowEffect), 0.0);
  public static readonly BindableProperty DistanceYProperty =
    BindableProperty.CreateAttached ("DistanceY", typeof(double), typeof(ShadowEffect), 0.0);

  public static bool GetHasShadow (BindableObject view)
  {
    return (bool)view.GetValue (HasShadowProperty);
  }

  public static void SetHasShadow (BindableObject view, bool value)
  {
    view.SetValue (HasShadowProperty, value);
  }
  ...

  static void OnHasShadowChanged (BindableObject bindable, object oldValue, object newValue)
  {
    var view = bindable as View;
    if (view == null) {
      return;
    }

    bool hasShadow = (bool)newValue;
    if (hasShadow) {
      view.Effects.Add (new LabelShadowEffect ());
    } else {
      var toRemove = view.Effects.FirstOrDefault (e => e is LabelShadowEffect);
      if (toRemove != null) {
        view.Effects.Remove (toRemove);
      }
    }
  }

  class LabelShadowEffect : RoutingEffect
  {
    public LabelShadowEffect () : base ("MyCompany.LabelShadowEffect")
    {
    }
  }
}

ShadowEffect には 5 つの添付プロパティと、各添付プロパティの static の getter と setter が含まれています。 このうちの 4 つのプロパティは各プラットフォーム固有の LabelShadowEffect に渡されるパラメーターを表します。 また、ShadowEffect クラスは、ShadowEffect がアタッチされるコントロールに対するエフェクトの追加または削除をコントロールするために使用される、HasShadow 添付プロパティを定義します。 この添付プロパティは、そのプロパティの値が変更されるときに実行される OnHasShadowChanged デリゲートを登録します。 このメソッドは、HasShadow 添付プロパティの値に基づいてエフェクトを追加または削除します。

入れ子になった LabelShadowEffect クラスは RoutingEffect クラスをサブクラス化し、エフェクトの追加と削除をサポートします。 RoutingEffect クラスは、通常はプラットフォーム固有となる内部のエフェクトをラップするプラットフォームに依存しないエフェクトを表します。 これは、プラットフォーム固有のエフェクトの型情報へのコンパイル時アクセスがないため、エフェクトの削除プロセスを簡略化します。 LabelShadowEffect コンストラクターは、解決グループ名の連結を渡す基底クラスのコンストラクターと、各プラットフォーム固有のエフェクトのクラスで指定された一意の ID を呼び出します。 これは、次に示すように、OnHasShadowChanged メソッド内のエフェクトの追加と削除を有効にします。

  • エフェクトの追加 - LabelShadowEffect の新しいインスタンスがコントロールの Effects コレクションに追加されます。 エフェクトの追加に Effect.Resolve メソッドを使用する代わりにこれが使用されます。
  • エフェクトの削除 - コントロール内の Effects コレクションの LabelShadowEffect の最初のインスタンスが取得され、削除されます。

エフェクトの使用

各プラットフォーム固有の LabelShadowEffect は、次の XAML コード例に示すように、添付プロパティを Label コントロールに追加することで使用できます。

<Label Text="Label Shadow Effect" ...
       local:ShadowEffect.HasShadow="true" local:ShadowEffect.Radius="5"
       local:ShadowEffect.DistanceX="5" local:ShadowEffect.DistanceY="5">
  <local:ShadowEffect.Color>
    <OnPlatform x:TypeArguments="Color">
        <On Platform="iOS" Value="Black" />
        <On Platform="Android" Value="White" />
        <On Platform="UWP" Value="Red" />
    </OnPlatform>
  </local:ShadowEffect.Color>
</Label>

C# での同等の Label を次のコード例に示します。

var label = new Label {
  Text = "Label Shadow Effect",
  ...
};

Color color = Color.Default;
switch (Device.RuntimePlatform)
{
    case Device.iOS:
        color = Color.Black;
        break;
    case Device.Android:
        color = Color.White;
        break;
    case Device.UWP:
        color = Color.Red;
        break;
}

ShadowEffect.SetHasShadow (label, true);
ShadowEffect.SetRadius (label, 5);
ShadowEffect.SetDistanceX (label, 5);
ShadowEffect.SetDistanceY (label, 5);
ShadowEffect.SetColor (label, color));

ShadowEffect.HasShadow 添付プロパティを true に設定すると、Label コントロールに対して LabelShadowEffect を追加または削除する、ShadowEffect.OnHasShadowChanged メソッドを実行します。 両方のコード例で、ShadowEffect.Color 添付プロパティはプラットフォーム固有の色の値を提供します。 詳しくは、「Device Class」(デバイス クラス) をご覧ください。

さらに、Button は実行時に影の色を変更できます。 Button がクリックされると、次のコードは ShadowEffect.Color 添付プロパティを設定することで、影の色を変更します。

ShadowEffect.SetColor (label, Color.Teal);

スタイルでのエフェクトの使用

添付プロパティをコントロールに追加することで使用できるエフェクトは、スタイルでも使用できます。 次の XAML コード例は、Label コントロールに適用できる、影エフェクトの "明示的な" スタイルを示します。

<Style x:Key="ShadowEffectStyle" TargetType="Label">
  <Style.Setters>
    <Setter Property="local:ShadowEffect.HasShadow" Value="True" />
    <Setter Property="local:ShadowEffect.Radius" Value="5" />
    <Setter Property="local:ShadowEffect.DistanceX" Value="5" />
    <Setter Property="local:ShadowEffect.DistanceY" Value="5" />
  </Style.Setters>
</Style>

Style は、次のコード例に示すように、StaticResource マークアップ拡張を使用して自身の Style プロパティを Style インスタンスに設定することで、Label に適用できます。

<Label Text="Label Shadow Effect" ... Style="{StaticResource ShadowEffectStyle}" />

スタイルの詳細については、 のスタイルに関するページを参照してください。

各プラットフォームでのエフェクトの作成

次のセクションで、LabelShadowEffect クラスのプラットフォーム固有の実装について説明します。

iOS プロジェクト

iOS プロジェクト用の LabelShadowEffect の実装を次のコード例に示します。

[assembly:ResolutionGroupName ("MyCompany")]
[assembly:ExportEffect (typeof(LabelShadowEffect), "LabelShadowEffect")]
namespace EffectsDemo.iOS
{
    public class LabelShadowEffect : PlatformEffect
    {
        protected override void OnAttached ()
        {
            try {
                UpdateRadius ();
                UpdateColor ();
                UpdateOffset ();
                Control.Layer.ShadowOpacity = 1.0f;
            } catch (Exception ex) {
                Console.WriteLine ("Cannot set property on attached control. Error: ", ex.Message);
            }
        }

        protected override void OnDetached ()
        {
        }
        ...

        void UpdateRadius ()
        {
            Control.Layer.ShadowRadius = (nfloat)ShadowEffect.GetRadius (Element);
        }

        void UpdateColor ()
        {
            Control.Layer.ShadowColor = ShadowEffect.GetColor (Element).ToCGColor ();
        }

        void UpdateOffset ()
        {
            Control.Layer.ShadowOffset = new CGSize (
                (double)ShadowEffect.GetDistanceX (Element),
                (double)ShadowEffect.GetDistanceY (Element));
        }
    }

OnAttached メソッドは、ShadowEffect getter を使用して添付プロパティの値を取得し、Control.Layer のプロパティをそのプロパティの値に設定して影を作成するメソッドを呼び出します。 エフェクトがアタッチされているコントロールに Control.Layer プロパティがない場合に備えて、この機能が try/catch ブロック内にラップされます。 クリーンアップする必要がないので、OnDetached メソッドによる実装は提供されません。

プロパティの変更への応答

ShadowEffect 添付プロパティのいずれかの値が実行時に変更されると、そのエフェクトはその変更を表示することで応答する必要があります。 プラットフォーム固有のエフェクトクラス内のオーバーライドされたバージョンの OnElementPropertyChanged メソッドは、次のコード例に示すように、バインド可能なプロパティの変更に応答する場所です。

public class LabelShadowEffect : PlatformEffect
{
  ...
  protected override void OnElementPropertyChanged (PropertyChangedEventArgs args)
  {
    if (args.PropertyName == ShadowEffect.RadiusProperty.PropertyName) {
      UpdateRadius ();
    } else if (args.PropertyName == ShadowEffect.ColorProperty.PropertyName) {
      UpdateColor ();
    } else if (args.PropertyName == ShadowEffect.DistanceXProperty.PropertyName ||
               args.PropertyName == ShadowEffect.DistanceYProperty.PropertyName) {
      UpdateOffset ();
    }
  }
  ...
}

ShadowEffect 添付プロパティの該当する値が変更されると、OnElementPropertyChanged メソッドが影の半径、色、またはオフセットを更新します。 このオーバーライドは何度も呼び出されることがあるため、変更になったプロパティのチェックは常に行われる必要があります。

Android プロジェクト

Android プロジェクト用の LabelShadowEffect の実装を次のコード例に示します。

[assembly:ResolutionGroupName ("MyCompany")]
[assembly:ExportEffect (typeof(LabelShadowEffect), "LabelShadowEffect")]
namespace EffectsDemo.Droid
{
    public class LabelShadowEffect : PlatformEffect
    {
        Android.Widget.TextView control;
        Android.Graphics.Color color;
        float radius, distanceX, distanceY;

        protected override void OnAttached ()
        {
            try {
                control = Control as Android.Widget.TextView;
                UpdateRadius ();
                UpdateColor ();
                UpdateOffset ();
                UpdateControl ();
            } catch (Exception ex) {
                Console.WriteLine ("Cannot set property on attached control. Error: ", ex.Message);
            }
        }

        protected override void OnDetached ()
        {
        }
        ...

        void UpdateControl ()
        {
            if (control != null) {
                control.SetShadowLayer (radius, distanceX, distanceY, color);
            }
        }

        void UpdateRadius ()
        {
            radius = (float)ShadowEffect.GetRadius (Element);
        }

        void UpdateColor ()
        {
            color = ShadowEffect.GetColor (Element).ToAndroid ();
        }

        void UpdateOffset ()
        {
            distanceX = (float)ShadowEffect.GetDistanceX (Element);
            distanceY = (float)ShadowEffect.GetDistanceY (Element);
        }
    }

OnAttached メソッドは、ShadowEffect getter を使用して添付プロパティの値を取得し、TextView.SetShadowLayer メソッドを呼び出してそのプロパティの値を使用して影を作成するメソッドを呼び出します。 エフェクトがアタッチされているコントロールに Control.Layer プロパティがない場合に備えて、この機能が try/catch ブロック内にラップされます。 クリーンアップする必要がないので、OnDetached メソッドによる実装は提供されません。

プロパティの変更への応答

ShadowEffect 添付プロパティのいずれかの値が実行時に変更されると、そのエフェクトはその変更を表示することで応答する必要があります。 プラットフォーム固有のエフェクトクラス内のオーバーライドされたバージョンの OnElementPropertyChanged メソッドは、次のコード例に示すように、バインド可能なプロパティの変更に応答する場所です。

public class LabelShadowEffect : PlatformEffect
{
  ...
  protected override void OnElementPropertyChanged (PropertyChangedEventArgs args)
  {
    if (args.PropertyName == ShadowEffect.RadiusProperty.PropertyName) {
      UpdateRadius ();
      UpdateControl ();
    } else if (args.PropertyName == ShadowEffect.ColorProperty.PropertyName) {
      UpdateColor ();
      UpdateControl ();
    } else if (args.PropertyName == ShadowEffect.DistanceXProperty.PropertyName ||
               args.PropertyName == ShadowEffect.DistanceYProperty.PropertyName) {
      UpdateOffset ();
      UpdateControl ();
    }
  }
  ...
}

ShadowEffect 添付プロパティの該当する値が変更されると、OnElementPropertyChanged メソッドが影の半径、色、またはオフセットを更新します。 このオーバーライドは何度も呼び出されることがあるため、変更になったプロパティのチェックは常に行われる必要があります。

ユニバーサル Windows プラットフォーム プロジェクト

ユニバーサル Windows プラットフォーム (UWP) プロジェクト用の LabelShadowEffect の実装を次のコード例に示します。

[assembly: ResolutionGroupName ("MyCompany")]
[assembly: ExportEffect (typeof(LabelShadowEffect), "LabelShadowEffect")]
namespace EffectsDemo.UWP
{
    public class LabelShadowEffect : PlatformEffect
    {
        Label shadowLabel;
        bool shadowAdded = false;

        protected override void OnAttached ()
        {
            try {
                if (!shadowAdded) {
                    var textBlock = Control as Windows.UI.Xaml.Controls.TextBlock;

                    shadowLabel = new Label ();
                    shadowLabel.Text = textBlock.Text;
                    shadowLabel.FontAttributes = FontAttributes.Bold;
                    shadowLabel.HorizontalOptions = LayoutOptions.Center;
                    shadowLabel.VerticalOptions = LayoutOptions.CenterAndExpand;

                    UpdateColor ();
                    UpdateOffset ();

                    ((Grid)Element.Parent).Children.Insert (0, shadowLabel);
                    shadowAdded = true;
                }
            } catch (Exception ex) {
                Debug.WriteLine ("Cannot set property on attached control. Error: ", ex.Message);
            }
        }

        protected override void OnDetached ()
        {
        }
        ...

        void UpdateColor ()
        {
            shadowLabel.TextColor = ShadowEffect.GetColor (Element);
        }

        void UpdateOffset ()
        {
            shadowLabel.TranslationX = ShadowEffect.GetDistanceX (Element);
            shadowLabel.TranslationY = ShadowEffect.GetDistanceY (Element);
        }
    }
}

ユニバーサル Windows プラットフォームは影エフェクトを提供しないため、両方のプラットフォーム上の LabelShadowEffect 実装は 2 番目のオフセット Label をプライマリ Label の背後に追加することで、その 1 つをシミュレートします。 OnAttached メソッドは新しい Label を作成し、Label にいくつかのレイアウト プロパティを設定します。 その後、ShadowEffect の getter を使用して添付プロパティの値を取得するメソッドを呼び出し、TextColorTranslationX、および TranslationY の各プロパティを設定して Label の色と場所をコントロールすることで影を作成します。 これにより、shadowLabel のプライマリ Label の背後にオフセットが挿入されます。 エフェクトがアタッチされているコントロールに Control.Layer プロパティがない場合に備えて、この機能が try/catch ブロック内にラップされます。 クリーンアップする必要がないので、OnDetached メソッドによる実装は提供されません。

プロパティの変更への応答

ShadowEffect 添付プロパティのいずれかの値が実行時に変更されると、そのエフェクトはその変更を表示することで応答する必要があります。 プラットフォーム固有のエフェクトクラス内のオーバーライドされたバージョンの OnElementPropertyChanged メソッドは、次のコード例に示すように、バインド可能なプロパティの変更に応答する場所です。

public class LabelShadowEffect : PlatformEffect
{
  ...
  protected override void OnElementPropertyChanged (PropertyChangedEventArgs args)
  {
    if (args.PropertyName == ShadowEffect.ColorProperty.PropertyName) {
      UpdateColor ();
    } else if (args.PropertyName == ShadowEffect.DistanceXProperty.PropertyName ||
                      args.PropertyName == ShadowEffect.DistanceYProperty.PropertyName) {
      UpdateOffset ();
    }
  }
  ...
}

ShadowEffect 添付プロパティの該当する値が変更されると、OnElementPropertyChanged メソッドが影の色またはオフセットを更新します。 このオーバーライドは何度も呼び出されることがあるため、変更になったプロパティのチェックは常に行われる必要があります。

まとめ

この記事では、添付プロパティを使ってエフェクトにパラメーターを渡す方法と、実行時にパラメーターを変更する方法を示しました。 添付プロパティは、実行時のプロパティの変更に応答するエフェクトのパラメーターの定義に使用できます。