MAR
14

添付ビヘイビア試してみた

Published:2009-03-14 00:55:12 UTC
WPF

ひとつ前のエントリJosh Smithさんの添付プロパティの解説記事を訳したのですが、実際に自分でもコードを書いて試してみようということで、少し変えて、添付されたListBoxのItemsSourceが更新された時にListBoxのスクロール位置を自動的にトップに巻き戻す添付ビヘイビアを作成してみました。ItemsSourceが更新されてもスクロール位置が変わらないというListBoxの挙動は、一つづつ項目の追加削除をする場合は便利なのですが、丸ごと全部項目を入れ替える場合、例えば非常に多くの項目をページに分割して表示しているときにページを行き来する時などには、ページを移った先でスクロール位置が保持されているのはかえって困ることになります。
で、書いてみたのはこんな感じ。

using System.Windows;
using System.Windows.Controls;

namespace SharpLab.IKnow.ItemBankPane {
	public class ListBoxBehavior {

		//添付されたListBoxのItemsSourceが更新された時にListBoxのスクロール位置を自動的に巻き戻すかを設定します。
		#region AutoRewind

		public static bool GetAutoRewind(ListBox listBox) {
			return (bool)listBox.GetValue(AutoRewindProperty);
		}

		public static void SetAutoRewind(
		  ListBox listBox, bool value) {
			listBox.SetValue(AutoRewindProperty, value);
		}

		public static readonly DependencyProperty AutoRewindProperty =
			DependencyProperty.RegisterAttached(
			"AutoRewind",
			typeof(bool),
			typeof(ListBoxBehavior),
			new UIPropertyMetadata(false, OnAutoRewindChanged));

		static void OnAutoRewindChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e) {
			ListBox listBox = depObj as ListBox;
			if (listBox == null)
				return;

			if (e.NewValue is bool == false)
				return;

			bool scrollToTop = (bool)e.NewValue;

			if (scrollToTop) {
				listBox.TargetUpdated += OnListBoxTargetUpdated;
			}
			else {
				listBox.TargetUpdated -= OnListBoxTargetUpdated;
			}
		}

		static void OnListBoxTargetUpdated(object sender, System.Windows.Data.DataTransferEventArgs e) {
			if (e.Property == ListBox.ItemsSourceProperty) {
				ListBox listBox = e.OriginalSource as ListBox;
				if (listBox.Items.Count > 0) {
					listBox.ScrollIntoView(listBox.Items[0]);
				}
			}
		}

		#endregion
	}
}

使う時はこんな感じ。

<ListBox v:ListBoxBehavior.AutoRewind="true" ItemsSource="{Binding NotifyOnTargetUpdated=true, Path=<適当に>}"/>

添付プロパティの値の変更時のイベントハンドラ内で、添付された要素のインスタンスのイベントを購読するのがキモで、後はどのように要素のふるまいを変えるかはお好み次第、といったとこでしょうか。同じ処理をコードビハインド内にイベントハンドラとして書く場合と異なり、即再利用可能ですから便利ですね。

 

ところでこれを書いているときに少しはまりかけたのが、FrameworkElement.SourceUpdated イベント (System.Windows)FrameworkElement.TargetUpdated イベント (System.Windows)の使い分け。バインディングソースが変更された結果、ListBoxが更新されるのを監視したいのだから、SourceUpdatedイベントを購読すればいいのかと思ったらTargetUpdatedイベントだったという罠。Binding.SourceUpdated アタッチされるイベント (System.Windows.Data)Binding.TargetUpdated アタッチされるイベント (System.Windows.Data)のエイリアスだというから、データバインディングの結果、どちらが影響を受けたかで決まっているのでしょうか??よくわかりません。