FC2ブログ

ポップアップは動かない(弐)

前回の続き。
親(ウィンドウ)についてこず、動かない子(ポップアップ)。
対策を調べる先は、当然StackOverflowである。
How can I move a WPF Popup when its anchor element moves?
ほほお、なるほど。という感じで、添付プロパティとして実装した。
用意する添付プロパティは、合計で三つ。

AnchorToParentWindow:Popupに添付するためのプロパティ。
FollowerPopups:フォロー中のポップアップリスト。内部用。
NowFollowing:フォロー中のWindowかどうか。内部用。

添付プロパティは、別にpublicである必要はなく、内部用としても活用できるので便利。
/// <summary>
/// 親ウィンドウに追随するかどうかを取得します。
/// </summary>
/// <param name="obj">対象オブジェクト</param>
/// <returns>親ウィンドウに追随するかどうか</returns>
public static bool GetAnchorToParentWindow(DependencyObject obj) {
return (bool)obj.GetValue(AnchorToParentWindowProperty);
}

/// <summary>
/// 親ウィンドウに追随するかどうかを設定します。
/// </summary>
/// <param name="obj">対象オブジェクト</param>
/// <param name="value">親ウィンドウに追随するかどうか</param>
public static void SetAnchorToParentWindow(DependencyObject obj, bool value) {
obj.SetValue(AnchorToParentWindowProperty, value);
}

/// <summary>親ウィンドウに追随するかどうか</summary>
/// <AttachedPropertyComments>
/// <summary>
/// Popupを親Windowに追随させるかどうか
/// </summary>
/// <value>The default value is false</value>
/// </AttachedPropertyComments>
public static readonly DependencyProperty AnchorToParentWindowProperty =
DependencyProperty.RegisterAttached("AnchorToParentWindow",
typeof(bool),
typeof(PopupHelper),
new FrameworkPropertyMetadata(false,
FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender,
OnAnchorToParentWindowPropertyChanged));

/// <summary>
/// AnchorToParentWindowプロパティ値変更イベントハンドラ
/// </summary>
/// <param name="dpObj">変更元オブジェクト</param>
/// <param name="e">イベント引数</param>
private static void OnAnchorToParentWindowPropertyChanged(
DependencyObject dpObj, DependencyPropertyChangedEventArgs e) {
}
以下は内部用
/// <summary>
/// フォロー中かどうかを取得します。
/// </summary>
/// <param name="obj">対象オブジェクト(Window or ScrollViewer)</param>
/// <returns>true:フォロー中</returns>
private static bool GetNowFollowing(DependencyObject obj) {
return (bool)obj.GetValue(NowFollowingProperty);
}

/// <summary>
/// フォロー中かどうかを設定します。
/// </summary>
/// <param name="obj">対象オブジェクト(Window or ScrollViewer)</param>
/// <param name="value">true:フォロー中</param>
private static void SetNowFollowing(DependencyObject obj, bool value) {
obj.SetValue(NowFollowingProperty, value);
}

/// <summary>フォロー中かどうか</summary>
private static readonly DependencyProperty NowFollowingProperty =
DependencyProperty.RegisterAttached("NowFollowing", typeof(bool),
typeof(PopupHelper),
new FrameworkPropertyMetadata(false,
FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender));

/// <summary>
/// フォローしているPopupリストを取得します。
/// </summary>
/// <param name="obj">対象オブジェクト(Window or ScrollViewer)</param>
/// <returns>フォローしているPopupリスト</returns>

private static IList<Popup> GetFollowerPopups(DependencyObject obj) {
return (IList<Popup>)obj.GetValue(FollowerPopupsProperty);
}

/// <summary>
/// フォローしているPopupリストを設定します。
/// </summary>
/// <param name="obj">対象オブジェクト(Window or ScrollViewer)</param>
/// <param name="value">フォローしているPopupリスト</param>
private static void SetFollowerPopups(DependencyObject obj, IList<Popup> value) {
obj.SetValue(FollowerPopupsProperty, value);
}

/// <summary>フォローしているPopupリスト</summary>
private static readonly DependencyProperty FollowerPopupsProperty =
DependencyProperty.RegisterAttached("FollowerPopups", typeof(IList<Popup>),
typeof(PopupHelper),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.SubPropertiesDoNotAffectRender));
OnAnchorToParentWindowPropertyChangedメソッド
/// <summary>
/// AnchorToParentWindowプロパティ値変更イベントハンドラ
/// </summary>
/// <param name="dpObj">変更元オブジェクト</param>
/// <param name="e">イベント引数</param>
private static void OnAnchorToParentWindowPropertyChanged(
DependencyObject dpObj, DependencyPropertyChangedEventArgs e) {

var popup = dpObj as Popup;

if (popup == null) {
return;//Popup以外への添付は無視
}

popup.Opened -= PopupHelper.Popup_Opened;
popup.Closed -= PopupHelper.Popup_Closed;

if (System.Convert.ToBoolean(e.NewValue)) {
popup.Opened += PopupHelper.Popup_Opened;
popup.Closed += PopupHelper.Popup_Closed;
}
}
イベントハンドラを必ず削除するのは、一度のイベントでイベントハンドラが何回も呼ばれないための処置。
Popup_Opened(イベントハンドラ)
/// <summary>
/// Popup Opened event handler
/// </summary>
/// <param name="sender">Event source(Popup)</param>
/// <param name="e">Event data</param>
private static void Popup_Opened(object sender, EventArgs e) {
try {
var popup = sender as Popup;
var window = Window.GetWindow(popup);

if (window == null) {
return;
}

PopupHelper.UpdatePopupOffset(new[]{popup});

//Windowへのイベント登録は、最初のみ。それ以降は冗長になる。
if (window != null && !PopupHelper.GetNowFollowing(window)) {
window.SizeChanged += PopupHelper.Window_SizeChanged;
window.LocationChanged += PopupHelper.Window_LocationChanged;
window.Closed += Window_Closed;
PopupHelper.SetNowFollowing(window, true);
}

var popupList = PopupHelper.GetFollowerPopups(window);

if (popupList == null) {
popupList = new List<Popup>(new []{popup});
PopupHelper.SetFollowerPopups(window, popupList);
} else {
popupList.Add(popup);
}

} catch (Exception) {
//本来は、エラー内容をログファイルへ出力します。
}
}

Window_LocationChanged(イベントハンドラ)
//// <summary>
/// Window LocationChanged Event handler
/// </summary>
/// <param name="sender">Event source(Window)</param>
/// <param name="e">Event data</param>
private static void Window_LocationChanged(object sender, EventArgs e) {

var window = sender as Window;
var popupList = PopupHelper.GetFollowerPopups(window);

if (window.WindowState == WindowState.Minimized) {
return;
}

window.Dispatcher.BeginInvoke(
new Action<IList<Popup>>(PopupHelper.UpdatePopupOffset),
System.Windows.Threading.DispatcherPriority.Background, popupList);
return;
}

Window_SizeChanged(イベントハンドラ)
/// <summary>
/// Window SizeChanged Event handler
/// </summary>
/// <param name="sender">Event source(Window)</param>
/// <param name="e">Event data</param>
private static void Window_SizeChanged(object sender, SizeChangedEventArgs e) {

var window = sender as Window;
var popupList = PopupHelper.GetFollowerPopups(window);

window.Dispatcher.BeginInvoke(
new Action<IList<Popup>>(PopupHelper.UpdatePopupOffset),
System.Windows.Threading.DispatcherPriority.Background, popupList);
}

UpdatePopupOffsetメソッド
/// <summary>
/// Update Popup offset value
/// </summary>
/// <param name="list">Popup list</param>
private static void UpdatePopupOffset(IList<Popup> list) {

if ((list?.Count ?? 0) == 0) {
return;
}

foreach (var popup in list.Where(p => p.IsOpen && p.StaysOpen)) {
var hOffset = popup.HorizontalOffset;

// HorizontalOffsetなどのプロパティを一度変更しないと、
    // ポップアップの位置が更新されないため、
// 同一プロパティに2回 値をセットしている。
popup.HorizontalOffset = hOffset + 1;
popup.HorizontalOffset = hOffset;
}

return;
}
このメソッドが、本命といえば本命。呼び出すまでが長いけど。
おっと。後始末系のメソッドも忘れずに。
Popup_Closed(イベントハンドラ)
/// <summary>
/// Popup Closed Event handler
/// </summary>
/// <param name="sender">Event source(Popup)</param>
/// <param name="e">Event data</param>
private static void Popup_Closed(object sender, EventArgs e) {
var popup = sender as Popup;
var window = Window.GetWindow(popup);

if (window == null) {
return;
}

//リストからクローズしたPopupを削除します。
var popupList = PopupHelper.GetFollowerPopups(window);

if (popupList != null) {
popupList.Remove(popup);
}
}

Window_Closed(イベントハンドラ)
/// <summary>
/// Window Closed event hamdler
/// </summary>
/// <param name="sender">Event source(Window)</param>
/// <param name="e">Event data</param>
private static void Window_Closed(object sender, EventArgs e) {

var window = sender as Window;

//リストからクローズしたPopupを削除します。
var popupList = PopupHelper.GetFollowerPopups(window);

if (popupList != null) {
popupList.Clear();

PopupHelper.SetFollowerPopups(window, null);
PopupHelper.SetNowFollowing(window, false);

window.LocationChanged -= Window_LocationChanged;
window.SizeChanged -= Window_SizeChanged;
window.Closed -= Window_Closed;
}

return;
}
ポイント

LocationChangedイベントだけでなく、SizeChangedイベントでも同様の処理を行っている。
LocationChangedやSizeChangedイベントは、Window一つのイベントにつき、一回の処理になるようにしている。


ここまでやって、ようやく期待する動きになってくれた。やれやれ。

と思っていたら、そうでもなかった。続く。
スポンサーサイト
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 0 | トラックバック: 0


この記事へのコメント

コメントの投稿

非公開コメント


サイドバー背後固定表示サンプル

当ブログに書かれたソースコードは流用自由です。

バグ、スペルミス等はありうる事です。

ご利用の際は自己責任でお願いしますm(_ _)m