はじめに
オレオレ解釈の覚書 その12
依存関係プロパティに関連して、添付プロパティのまとめです。
本文
添付プロパティも実体は依存関係プロパティと同じ DependencyProperty になります。依存関係プロパティは、コントロールやウィンドウに対して直接定義し、実装していました。一方で添付プロパティはこうした処理に手を加えることなく、別のクラスで定義し、名前の通りコントロールに値を添えることができます。よく見かけるところでは、Grid.Row や DockPanel.Dock などがあります。
実際に定義してみましょう。下記のようなヘルパークラスを作成し、DependencyProperty を宣言、初期化します。これにアクセスするための静的メソッドを用意して、対象となるコントロールを引数として要求します。メソッド内でコントロールと DependencyProperty は紐づけられ、値が管理されます。これが添付プロパティの実装で、コントロール以外の領域で値を保管できるようになりました。
using System.Windows;
namespace TestApp.Views.Controls
{
public class ControlAttachedProperty
{
public static readonly DependencyProperty RemarkProperty
= DependencyProperty.RegisterAttached(
"Remark",
typeof(string),
typeof(ControlAttachedProperty),
new FrameworkPropertyMetadata(string.Empty));
public static string GetRemark(DependencyObject target)
=> (string)target.GetValue(RemarkProperty);
public static void SetRemark(DependencyObject target, string value)
=> target.SetValue(RemarkProperty, value);
}
}
作成した添付プロパティは Xaml 上でほかのプロパティと同じようにバインドさせることができます。保管された値は静的メソッドを介して C# 側からも取得、変更ができます。
<Window x:Class="TestApp.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:controls="clr-namespace:TestApp.Views.Controls">
<Grid>
<TextBlock x:Name="textBlock" controls:ControlAttachedProperty.Remark="ほげ"/>
</Grid>
</Window>
using System.Windows;
namespace TestApp.Views
{
public partial class MainWindow : Window
{
public void Hoge()
{
// コントロールを指定して添付プロパティを取得、設定する
var remark = ControlAttachedProperty.GetRemark(this.textBlock);
ControlAttachedProperty.SetRemark(this.textBlock, "ふが");
}
}
}
ただし、添付プロパティはこのままで使用するより、以前お話ししたような添付ビヘイビアとして「ふるまい」を定義するために使われる方が多いと思われます。プロパティの変更を監視したり、特定のイベントを購読することで、決められたタイミングで処理を実行させます。
using System.Windows;
using System.Windows.Input;
namespace TestApp.Views.Behaviors
{
public class WindowAttachedBehavior
{
public static readonly DependencyProperty DraggableAnywhereProperty
= DependencyProperty.RegisterAttached(
"DraggableAnywhere",
typeof(bool),
typeof(WindowAttachedBehavior),
new PropertyMetadata(OnDraggableAnywhereChanged));
public static bool GetDraggableAnywhere(DependencyObject obj)
=> (bool)obj.GetValue(DraggableAnywhereProperty);
public static void SetDraggableAnywhere(DependencyObject obj, bool value)
=> obj.SetValue(DraggableAnywhereProperty, value);
private static void OnDraggableAnywhereChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
if (!(sender is Window window))
return;
if ((bool)e.OldValue)
window.MouseLeftButtonDown -= MouseLeftButtonDown;
if ((bool)e.NewValue)
window.MouseLeftButtonDown += MouseLeftButtonDown;
}
private static void MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (!(sender is Window window))
return;
if (e.ButtonState != MouseButtonState.Pressed)
return;
window.DragMove();
e.Handled = true;
}
}
}
おわりに
添付プロパティについてでした。
実装箇所が違うだけで依存関係プロパティと大きな差はありませんが、既存の処理を汚さずに追加でき、汎用的なものは使いまわせるため、実装の幅をさらに広げることができます。
ビヘイビアとしては、プロパティを定義する必要がない BlendBehavior を使う場合が多いですが、添付ビヘイビアはプロパティがあることにより Style へ登録できるため、リソースファイルで一括適用できたりします。どちらも一長一短です。
話は変わりますが、相互作用処理の回で Prism の InteractionRequest の破棄がコミットされことをコメントで教えて頂きました。
Prism 以外のフレームワークとして、尾上さん(@ugaya40) が作成された Livet があります。日本語の文献が豊富にあり導入のハードルが低く、WPF に的を絞られていたため、私も Prism と並行して愛用しておりました。
かつての Livet はすべての機能がひとまとまりになったライブラリで、Prism とは競合する機能も多く、同一ソリューション内での共存は控えていました。ところが現在は、機能ごとにライブラリが細かく分離されており、使いたい部分だけを個別に導入できるようになっています。Prism をベースにしつつも、Livet だけに存在する機能を部分的に取り入れることが可能になりました。
Livet には InteractionRequest と類似する機能として、柔軟で汎用性の高い Messenger が提供されており、これは今も健在です。次回はこの Messenger について簡単にまとめてみます。