Как я могу переместить всплывающее окно WPF при перемещении его элемента привязки? - PullRequest
41 голосов
/ 21 октября 2009

У меня есть всплывающее окно, определенное так:

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

Я добавил обработчики событий в элемент myPopupAnchor для событий MouseEnter и MouseLeave. Два обработчика событий переключают видимость всплывающего окна.

Моя проблема в том, что позиция myPopupAnchor читается только тогда, когда всплывающее окно сначала отображается, или скрыто, а затем отображается снова. Если якорь перемещается, всплывающее окно не перемещается.

Я ищу способы обойти это, я хочу всплывающее окно. Могу ли я уведомить WPF, что привязка PlacementTarget изменилась и должна быть прочитана снова? Можно ли вручную установить позицию всплывающего окна?

В настоящее время у меня есть очень грубый обходной путь, который включает закрытие, а затем повторное открытие всплывающего окна, что вызывает некоторые проблемы с перекрашиванием.

Ответы [ 8 ]

71 голосов
/ 18 марта 2010

Я посмотрел на пару вариантов и образцов там. Мне кажется, что лучше всего работает «удар» по одному из свойств, которое заставляет Popup перемещаться самостоятельно. Свойство, которое я использовал, является 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;
    };
}

Когда окно перемещается, всплывающее окно будет перемещено. Незаметное изменение в HorizontalOffset не замечено, потому что окно и всплывающее окно уже перемещаются.

Я все еще оцениваю, является ли всплывающий элемент управления лучшим вариантом в тех случаях, когда он остается открытым во время другого взаимодействия. Я думаю, что предложение Рэй Бернса поместить этот материал в слой Adorner кажется хорошим подходом для некоторых сценариев.

22 голосов
/ 28 марта 2012

Просто чтобы добавить к отличному решению NathanAW выше, я подумал, что укажет некоторый контекст , такой как , где разместить код C # в этом случае. Я все еще довольно новичок в WPF, поэтому я сначала попытался выяснить, куда поместить код NathanAW. Когда я пытался поместить этот код в конструктор для 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>

Затем в коде, чтобы избежать Window.GetWindow() return Null, подключите обработчик к событию Loaded для размещения кода NathanAW (см. Комментарий Питера Уолка к аналогичному обсуждению 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;
            };
        }
    }
}
20 голосов
/ 23 декабря 2010
    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);
        }
    }
3 голосов
/ 11 февраля 2016

Чтобы добавить ответ Джейсона Фрэнка, подход Window.GetWindow() не будет работать, если в конечном итоге WPF UserControl будет размещен в WinForms ElementHost. Мне нужно было найти ScrollViewer, в который был помещен мой UserControl, так как это был элемент, показывающий полосы прокрутки.

Этот универсальный рекурсивный метод (измененный из другого ответа) поможет найти родителя определенного типа в логическом дереве (возможно также использовать визуальное дерево) и вернуть его, если найден.

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() и продолжить с ответом Джейсона о подписке на нужные события. В случае ScrollViewer это событие ScrollChanged.

3 голосов
/ 09 августа 2010

Если вы хотите переместить всплывающее окно, есть простой трюк: измените его положение, затем установите:

IsOpen = false;
IsOpen = true;
1 голос
/ 11 июня 2014

Я изменил код от Джейсона, потому что всплывающее окно уже на переднем плане, если окно не активировано. Есть ли какая-либо опция в классе 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;
    }
0 голосов
/ 21 февраля 2010

Загрузить пример позиции всплывающего окна по адресу:

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

В примере кода используется класс CustomPopupPlacement с объектом Rect и привязка к горизонтальным и вертикальным смещениям для перемещения всплывающего окна.

<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}"
0 голосов
/ 05 ноября 2009

Вы не можете сделать это. Когда всплывающее окно отображается на экране, оно не переустанавливает себя, если его родитель переустанавливается. Это поведение Popup контроля. проверьте это: http://msdn.microsoft.com/en-us/library/system.windows.controls.primitives.popup.aspx

Вы можете использовать Окно (с WindowStyle = Нет) вместо Popup, которое может решить вашу проблему.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...