【问题标题】:How can I move a WPF Popup when its anchor element moves?当 WPF 弹出窗口的锚元素移动时,如何移动它?
【发布时间】:2010-12-08 16:25:20
【问题描述】:

我有一个这样定义的弹出窗口:

<Popup
    Name="myPopup"
    StaysOpen="True"
    Placement="Bottom"
    PlacementRectangle="0,20,0,20"
    PlacementTarget="{Binding ElementName=myPopupAnchor}">
    <TextBlock ... />
</Popup>

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

我的问题是 myPopupAnchor 的位置仅在弹出窗口第一次显示或隐藏然后再次显示时读取。如果锚点移动,则弹出窗口不会移动。

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

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

【问题讨论】:

    标签: wpf popup binding


    【解决方案1】:

    我查看了几个选项和示例。对我来说似乎最有效的事情是“碰撞”导致弹出窗口自行重新定位的属性之一。我使用的属性是 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;
        };
    }
    

    当窗口移动时,弹出窗口将重新定位。 Horizo​​ntalOffset 的细微变化没有被注意到,因为窗口和弹出窗口已经在移动了。

    如果控件在其他交互期间保持打开状态,我仍在评估弹出控件是否是最佳选择。我认为Ray Burns suggestion to put this stuff in an Adorner layer 在某些情况下似乎是一个不错的方法。

    【讨论】:

    • 您考虑的装饰层方法有什么更新吗?
    • 我已经用装饰层做了很多事情,发现效果很好。装饰器有点挑战性,需要进行一些实验,但总的来说似乎是一个不错的选择。
    • +1 @NathanAW 我希望我能 +gig 你的答案。优秀。我阅读了整个ComboBox 实现。但无法弄清楚它如何移动Popup。虽然没有关于ComboBox 如何移动Popup 的指导,但您知道吗?
    • @Javad_Amiry,我希望我知道,但我不确定。我怀疑大多数人都会逃避这样一个事实,即它会响应大多数不影响组合框的交互而关闭。因此,如果您单击父窗口来移动它,组合就会关闭。你有它与父窗口一起移动的例子吗?
    • @NathanAW:也许最好添加0.001 而不是1,以使更改更小。 HorizontalOffset 无论如何都是 double
    【解决方案2】:

    只是为了补充上面 NathanAW 的出色解决方案,我想我会指出一些上下文,例如 where 在这种情况下放置 C# 代码。我对 WPF 还是很陌生,所以起初我很难弄清楚将 NathanAW 的代码放在哪里。当我尝试将该代码放入托管我的 Popup 的 UserControl 的构造函数中时,Window.GetWindow() 总是返回 Null(因此“碰撞”代码从未执行)。所以我认为其他新手可能会从上下文中受益。

    在上下文中显示 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,将处理程序连接到 Loaded 事件以容纳 NathanAW 的代码(例如,参见 Peter Walke's comment 类似的 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;
                };
            }
        }
    }
    

    【讨论】:

    • 很好的跟进.. 我也遇到了 Window null 问题!
    • 它工作得很好,谢谢:),但是我在将应用程序最小化到任务栏时遇到问题,弹出窗口仍然显示在屏幕上,如何让它消失?
    • 我知道这是旧的,但是当弹出目标移动时怎么样 - 因为它在视图中向下/向上滚动?
    【解决方案3】:
        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);
            }
        }
    

    【讨论】:

    • 什么?可以通过反射运行/调用私有方法吗?这并没有给我一个很好的感觉......在这一点上对我来说很方便......但不知何故我觉得这样做很奇怪......我的意思是private是有原因的......
    • @Marcel Benthin 是的,但是你不应该在私有方法上使用明显的名字,你也可以混淆它们。
    • 这里的私有并不意味着安全,也不是这样。这只是一种建立干净接口并隐藏调用者不应该关心的东西(或者在某些情况下是潜在危险的东西)的方法。如果您的安全依赖于会员隐私,那么您就有麻烦了。
    • 我认为,在这种情况下调用内部方法 Reposition() 更安全。根据this,它会在调用 UpdatePosition() 之前进行一些检查。
    • @tom.maruska 您的链接已断开,新链接为:referencesource.microsoft.com/#PresentationFramework/Framework/…
    【解决方案4】:

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

    IsOpen = false;
    IsOpen = true;
    

    【讨论】:

      【解决方案5】:

      为了补充 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 事件。

      【讨论】:

        【解决方案6】:

        我修改了 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;
            }
        

        【讨论】:

          【解决方案7】:

          你不能这样做。当 Popup 显示在屏幕上时,如果重新定位其父级,它不会重新定位自身。这就是 Popup 控件的行为。 检查这个:http://msdn.microsoft.com/en-us/library/system.windows.controls.primitives.popup.aspx

          您可以使用 Window(WindowStyle=None)代替 Popup,这可能会解决您的问题。

          【讨论】:

            【解决方案8】:

            我将 Jason Frank 提供的逻辑封装在一个类中,并继承自 PopUp 类。

            class MyPopup : Popup
                {
                    private Window _root;
            
                    public MyPopup()
                    {
                        Loaded += OnLoaded;
                        Unloaded += OnUnloaded;
                    }
            
                    private void OnLoaded(object sender, RoutedEventArgs e)
                    {
                        _root = Window.GetWindow(this);
                        _root.LocationChanged += OnRootLocationChanged;
                        
                    }
            
                    private void OnRootLocationChanged(object sender, EventArgs e)
                    {
                        var offset = this.HorizontalOffset;
                        this.HorizontalOffset = offset + 1;
                        this.HorizontalOffset = offset;
                    }
            
                    private void OnUnloaded(object sender, RoutedEventArgs e)
                    {
                        _root.LocationChanged -= OnRootLocationChanged;
                        Loaded -= OnLoaded;
                        Unloaded -= OnUnloaded;
                    }
                }
            

            【讨论】:

              【解决方案9】:

              在以下位置下载 Popup 弹出位置示例:

              http://msdn.microsoft.com/en-us/library/ms771558(v=VS.90).aspx

              代码示例使用带有 Rect 对象的 CustomPopupPlacement 类,并绑定到水平和垂直偏移来移动 Popup。

              <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}"
              

              【讨论】:

              • 属性改变时弹出框是否移动了?还是它们只是在最初显示弹出窗口时读取?
              • 弹出窗口移动。下载示例,试一试。
              • 我试过这个示例,但是当窗口移动时它似乎没有移动弹出窗口。
              • 样本不再可用:(
              • 正如 NathanAW 所说,当窗口移动时,Popup 不会更新其位置。
              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2020-09-10
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多