アンカー要素が移動するときに WPF ポップアップを移動するにはどうすればよいですか?
質問
次のようにポップアップを定義しました。
<Popup
Name="myPopup"
StaysOpen="True"
Placement="Bottom"
PlacementRectangle="0,20,0,20"
PlacementTarget="{Binding ElementName=myPopupAnchor}">
<TextBlock ... />
</Popup>
イベントハンドラーを追加しました myPopupAnchor
イベントの要素 MouseEnter
そして MouseLeave
. 。2 つのイベント ハンドラーは、ポップアップの表示/非表示を切り替えます。
私の問題は、myPopupAnchor の位置が、ポップアップが最初に表示されるとき、または非表示になってから再び表示されるときにのみ読み取られることです。アンカーが移動しても、ポップアップは移動しません。
これを回避する方法を探しています。動くポップアップが必要です。WPF に次のことを通知できますか? PlacementTarget
装丁が変わったのでもう一度読むべきですか?ポップアップの位置を手動で設定できますか?
現在のところ、ポップアップを閉じてから再度開くという非常に大雑把な回避策があり、再描画の問題が発生します。
解決
いくつかのオプションとサンプルを調べました。私にとって最も効果的だと思われるのは、ポップアップの位置を自動的に変更するプロパティの 1 つを「バンプ」することです。私が使用したプロパティは horizontalOffset です。
これを (それ自体 + 1) に設定してから、元の値に戻します。これは、ウィンドウの位置が変更されたときに実行されるイベント ハンドラーで行います。
// Reference to the PlacementTarget.
DependencyObject myPopupPlacementTarget;
// Reference to the popup.
Popup myPopup;
Window w = Window.GetWindow(myPopupPlacementTarget);
if (null != w)
{
w.LocationChanged += delegate(object sender, EventArgs args)
{
var offset = myPopup.HorizontalOffset;
myPopup.HorizontalOffset = offset + 1;
myPopup.HorizontalOffset = offset;
};
}
ウィンドウを移動すると、ポップアップの位置が変更されます。いずれにせよ、ウィンドウとポップアップは既に移動しているため、水平オフセットの微妙な変化には気付かれません。
他の操作中にコントロールが開いたままになる場合に、ポップアップ コントロールが最良の選択肢であるかどうかはまだ評価中です。私はそう思っています これを Adorner レイヤーに入れるという Ray Burns の提案 いくつかのシナリオでは良いアプローチのように思えます。
他のヒント
上記の NathanAWの優れたソリューションに追加するだけで、 など、コンテキストを指摘すると思いましたこの場合にC#コードを配置します。私はまだWPFにかなり慣れていないので、最初はNathanAWのコードを配置する場所を見つけるのに苦労しました。 PopupをホストするUserControlのコンストラクターにそのコードを配置しようとすると、 Window.GetWindow()
は常に Null
を返しました(したがって、&quot; bump&quot;コードは実行されませんでした) 。だから私は他の初心者が物事を文脈で見ることから恩恵を受けるかもしれないと思った。
コンテキストでC#を表示する前に、関連する要素とその名前を表示するXAMLコンテキストの例を次に示します。
<UserControl x:Class="MyNamespace.View1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >
<TextBlock x:Name="popupTarget" />
<Popup x:Name="myPopup"
Placement="Bottom"
PlacementTarget="{Binding ElementName=popupTarget}" >
(popup content here)
</Popup>
</UserControl>
次に、コードビハインドで、 Window.GetWindow()
が Null
を返すのを避けるために、NathanAWのコードを格納するためにLoadedイベントにハンドラーを接続します(<同様のstackoverflowの議論に関するa href = "https://stackoverflow.com/a/304604/718325"> Peter Walkeのコメント。以下は、UserControlのコードビハインドでのすべての表示です。
public partial class View1 : UserControl
{
// Constructor
public View1()
{
InitializeComponent();
// Window.GetWindow() will return Null if you try to call it here!
// Wire up the Loaded handler instead
this.Loaded += new RoutedEventHandler(View1_Loaded);
}
/// Provides a way to "dock" the Popup control to the Window
/// so that the popup "sticks" to the window while the window is dragged around.
void View1_Loaded(object sender, RoutedEventArgs e)
{
Window w = Window.GetWindow(popupTarget);
// w should not be Null now!
if (null != w)
{
w.LocationChanged += delegate(object sender2, EventArgs args)
{
var offset = myPopup.HorizontalOffset;
// "bump" the offset to cause the popup to reposition itself
// on its own
myPopup.HorizontalOffset = offset + 1;
myPopup.HorizontalOffset = offset;
};
// Also handle the window being resized (so the popup's position stays
// relative to its target element if the target element moves upon
// window resize)
w.SizeChanged += delegate(object sender3, SizeChangedEventArgs e2)
{
var offset = myPopup.HorizontalOffset;
myPopup.HorizontalOffset = offset + 1;
myPopup.HorizontalOffset = offset;
};
}
}
}
private void ppValues_Opened(object sender, EventArgs e)
{
Window win = Window.GetWindow(YourControl);
win.LocationChanged += new EventHandler(win_LocationChanged);
}
void win_LocationChanged(object sender, EventArgs e)
{
if (YourPopup.IsOpen)
{
var mi = typeof(Popup).GetMethod("UpdatePosition", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
mi.Invoke(YourPopup, null);
}
}
ポップアップを移動する場合は、簡単なトリックがあります。位置を変更してから、設定します:
IsOpen = false;
IsOpen = true;
Jason Frankの答えに追加すると、WPF UserControlが最終的にWinForms ElementHostでホストされる場合、 Window.GetWindow()
アプローチは機能しません。見つける必要があるのは、スクロールバーを表示する要素であるため、UserControlが配置されたScrollViewerです。
この一般的な再帰的メソッド(別の回答を修正)は、論理ツリー内で特定のタイプの親を見つけやすくし(視覚ツリーも使用可能)、見つかった場合はそれを返します。
public static T FindLogicalParentOf<T>(DependencyObject child) where T: FrameworkElement
{
DependencyObject parent = LogicalTreeHelper.GetParent(child);
//Top of the tree
if (parent == null) return null;
T parentWindow = parent as T;
if (parentWindow != null)
{
return parentWindow;
}
//Climb a step up
return FindLogicalParentOf<T>(parent);
}
Window.GetWindow()
の代わりにこのヘルパーメソッドを呼び出して、適切なイベントにサブスクライブするというJasonの回答を続けます。 ScrollViewerの場合、代わりにScrollChangedイベントです。
Jasonのコードを変更しました。ウィンドウがアクティブ化されていない場合、ポップアップはすでにフォアグラウンドにあるためです。 Popupクラスにオプションがありますか、またはソリューションは大丈夫ですか?
private void FullLoaded(object sender, RoutedEventArgs e) {
Window CurrentWindow = Window.GetWindow(this.Popup);
if (CurrentWindow != null) {
CurrentWindow.LocationChanged += (object innerSender, EventArgs innerArgs) => {
this.RedrawPopup();
};
CurrentWindow.SizeChanged += (object innerSender, SizeChangedEventArgs innerArgs) => {
this.RedrawPopup();
};
CurrentWindow.Activated += (object innerSender, EventArgs innerArgs) => {
if (this.m_handleDeActivatedEvents && this.m_ShowOnActivated) {
this.Popup.IsOpen = true;
this.m_ShowOnActivated = false;
}
};
CurrentWindow.Deactivated += (object innerSender, EventArgs innerArgs) => {
if (this.m_handleDeActivatedEvents && this.Popup.IsOpen) {
this.Popup.IsOpen = false;
this.m_ShowOnActivated = true;
}
};
}
}
private void RedrawPopup() {
double Offset = this.Popup.HorizontalOffset;
this.Popup.HorizontalOffset = Offset + 1;
this.Popup.HorizontalOffset = Offset;
}
これはできません。 Popupが画面に表示されるとき、その親が再配置されても、ポップアップ自体は再配置されません。これがPopupコントロールの動作です。 これを確認してください: http://msdn.microsoft .com / en-us / library / system.windows.controls.primitives.popup.aspx
Popupの代わりにWindow(WindowStyle = Noneを使用)を使用して問題を解決できます。
次の場所でPopup Popup Positionサンプルをダウンロードします。
http://msdn.microsoft。 com / en-us / library / ms771558(v = VS.90).aspx
サンプルコードは、RectオブジェクトでクラスCustomPopupPlacementを使用し、水平および垂直オフセットにバインドしてポップアップを移動します。
<Popup Name="popup1" Placement="Bottom" AllowsTransparency="True"
IsOpen="{Binding ElementName=popupOpen, Path=IsChecked}"
HorizontalOffset="{Binding ElementName=HOffset, Path=Value, Mode=TwoWay}"
VerticalOffset="{Binding ElementName=VOffset, Path=Value, Mode=TwoWay}"