如何在其锚元素移动时移动WPF Popup?

Miz*_*zor 45 wpf binding popup

我有一个像这样定义的Popup:

<Popup
    Name="myPopup"
    StaysOpen="True"
    Placement="Bottom"
    PlacementRectangle="0,20,0,20"
    PlacementTarget="{Binding ElementName=myPopupAnchor}">
    <TextBlock ... />
</Popup>
Run Code Online (Sandbox Code Playgroud)

我已经myPopupAnchor为事件MouseEnter和元素添加了事件处理程序MouseLeave.两个事件处理程序切换弹出窗口的可见性.

我的问题是myPopupAnchor的位置只在首次显示弹出窗口时被读取,或者被隐藏然后再次显示.如果锚移动,则弹出窗口不会移动.

我正在寻找解决这个问题的方法,我想要一个动人的Popup.我可以通知WPF PlacementTarget绑定已更改并应再次读取吗?我可以手动设置弹出窗口的位置吗?

目前,我有一个非常粗略的解决方法,涉及关闭然后再次打开弹出窗口,这会导致一些重新绘制问题.

Nat*_*nAW 75

我看了几个选项并在那里打样.对我来说似乎最有用的事情是"碰撞"导致Popup自行重新定位的属性之一.我使用的属性是Horizo​​ntalOffset.

我将其设置为(本身+ 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;
    };
}
Run Code Online (Sandbox Code Playgroud)

移动窗口时,弹出窗口将重新定位.不会注意到Horizo​​ntalOffset中的细微变化,因为窗口和弹出窗口仍然在移动.

我还在评估在其他交互过程中控件保持打开的情况下,弹出控件是否是最佳选项.我认为Ray Burns建议将这些东西放在Adorner层中似乎是一种适用于某些场景的好方法.

  • @NathanAW:也许最好添加`0.001`而不是`1`,以使变化更小.`Horizo​​ntalOffset`无论如何都是`double`. (3认同)

Jas*_*ank 26

只需添加到NathanAW的伟大的解决方案上面,我想我会指出一些方面,如 这里放置C#代码在这种情况下.我还是WPF的新手,所以我一开始就想弄清楚NathanAW的代码放在哪里.当我尝试将该代码放在托管我的Popup的UserControl的构造函数中时,Window.GetWindow()总是返回Null(因此"bump"代码从未执行过).所以我认为其他新手可能会从上下文中看到事情中受益.

在上下文中显示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>
Run Code Online (Sandbox Code Playgroud)

然后在代码隐藏中,为避免Window.GetWindow()返回Null,将处理程序连接到Loaded事件以容纳NathanAW的代码(例如,参见Peter Walke关于类似stackoverflow讨论的评论).以下是我在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;
            };
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 我知道这很旧,但是当弹出目标移动时怎么样 - 因为它向下/向上滚动视图? (2认同)

小智 21

    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);
        }
    }
Run Code Online (Sandbox Code Playgroud)

  • 这里的私人并不意味着安全,并不是那种方式.它只是一种建立干净界面并隐藏调用者不应该关心的东西(或者在某些情况下可能是危险的东西)的方法.如果您的安全依赖于会员隐私,那么您就遇到了麻烦. (4认同)
  • 卧槽?可以通过反射运行/调用私有方法吗?这并没有给我内心带来良好的感觉……此时这对我来说很方便……但不知怎的,我觉得这样做很奇怪……我的意思是“私人”的存在是有原因的…… (2认同)
  • 我认为,在这种情况下调用内部方法**Reposition()**会更安全.根据[this](http://referencesource-beta.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Controls/Primitives/Popup.cs#1754),它会对它进行一些检查**UpdatePosition ()**. (2认同)

Aur*_*bon 5

如果你想移动弹出窗口,有一个简单的技巧:改变它的位置,然后设置:

IsOpen = false;
IsOpen = true;
Run Code Online (Sandbox Code Playgroud)


zem*_*ien 5

要补充 Jason Frank 的答案,Window.GetWindow()如果 WPF UserControl 最终托管在 WinForms ElementHost 中,则该方法将不起作用。我需要找到的是我的 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);
    }
Run Code Online (Sandbox Code Playgroud)

调用此辅助方法而不是并Window.GetWindow()继续 Jason 的订阅正确事件的回答。对于 ScrollViewer,它是 ScrollChanged 事件。