Jak mogę przenieść wyskakujące okienko WPF, gdy jego element kotwicy się porusza?

Mam wyskakujące okienko zdefiniowane tak:

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

Dodałem obsługę zdarzeń do elementu myPopupAnchor dla zdarzeń MouseEnter i MouseLeave. Dwie procedury obsługi zdarzeń przełączają widoczność wyskakującego okienka.

Mój problem polega na tym, że pozycja myPopupAnchor jest odczytywana tylko wtedy, gdy wyskakujące okienko jest najpierw pokazane, lub ukryte, a następnie pokazane ponownie. Jeśli Kotwica się porusza, wyskakujące okienko nie.

Szukam sposobu, aby obejść to, chcę ruchome Popup. Czy Mogę zawiadomić WPF o zmianie wiązania PlacementTarget i powinno się czytać jeszcze raz? Czy mogę ręcznie ustawić pozycję wyskakującego okienka?

Obecnie mam bardzo prymitywne obejście, które obejmuje zamknięcie, a następnie ponowne otwarcie wyskakującego okienka, co powoduje pewne problemy z przemalowaniem.

Author: Dan J, 2009-10-21

8 answers

Spojrzałem na kilka opcji i próbek tam. Rzeczą, która wydaje się działać najlepiej dla mnie jest "podbić" jedną z właściwości, które powodują, że wyskakujące okienko samo się zmienia. Właściwość, której użyłem to HorizontalOffset.

Ustawiam go na (sam + 1), a następnie ustawiam z powrotem oryginalną wartość. Robię to w programie obsługi zdarzeń, który działa, gdy okno jest zmieniane.

// 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;
    };
}

Gdy okno zostanie przesunięte, wyskakujące okienko zmieni położenie. Subtelna zmiana w HorizontalOffset nie jest zauważany, ponieważ okno i wyskakujące okienko i tak już się poruszają.

Nadal oceniam, czy kontrola popup jest najlepszą opcją w przypadkach, gdy kontrola pozostaje otwarta podczas innych interakcji. Myślę, że sugestia Raya Burnsa, aby umieścić to w warstwie Adorner wydaje się dobrym podejściem do niektórych scenariuszy.

 69
Author: NathanAW,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:26:37

Żeby dodać do tego świetne rozwiązanie powyżej, pomyślałem, że wskażę jakiś kontekst , taki jak gdzie aby w tym przypadku umieścić kod C#. Wciąż jestem całkiem nowy w WPF, więc na początku starałem się wymyślić, gdzie umieścić kod NathanAW. Kiedy próbowałem umieścić ten kod w konstruktorze dla kontrolera UserControl, który hostował moje Popup, Window.GetWindow() Zawsze zwracał Null (więc kod "bump" nigdy nie został wykonany). Pomyślałem więc, że inni nowicjusze mogą skorzystać z postrzeganie rzeczy w kontekście.

Przed pokazaniem C# w kontekście, oto przykładowy kontekst XAML pokazujący niektóre istotne elementy i ich nazwy:

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

Następnie w kodzie-behind, aby uniknąć Window.GetWindow() return Null, Podłącz obsługę do załadowanego zdarzenia, aby umieścić kod Nathanawa(zobacz komentarz Petera Walke na przykład w podobnej dyskusji stackoverflow). Oto dokładnie jak to wszystko wyglądało w moim kodzie UserControl-behind:

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;
            };
        }
    }
}
 21
Author: Jason Frank,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:10:41
    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);
        }
    }
 19
Author: AQL.exe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-12-23 14:42:29

Jeśli chcesz przesunąć wyskakujące okienko, istnieje prosta sztuczka: zmień jego pozycję, a następnie Ustaw :

IsOpen = false;
IsOpen = true;
 4
Author: Aurélien Ribon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-08-09 07:16:22

Nie możesz tego zrobić. Gdy wyskakujące okienko jest wyświetlane na ekranie, nie zmienia pozycji, jeśli jego rodzic jest zmieniany. To zachowanie kontroli Popup. sprawdź to: http://msdn.microsoft.com/en-us/library/system.windows.controls.primitives.popup.aspx

Możesz użyć okna (z WindowStyle = None) zamiast Popup, które może rozwiązać twój problem.

 1
Author: viky,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-11-05 13:28:03

Zmodyfikowałem kod z Jasona, ponieważ wyskakujące okienko jest już na pierwszym planie, jeśli okno nie jest aktywowane. Czy jest jakaś opcja w klasie Popup, czy moje rozwiązanie jest w porządku?

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;
    }
 1
Author: Gigabyte,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-11 11:32:09

Aby dodać do odpowiedzi Jasona Franka, podejście Window.GetWindow() nie zadziała, jeśli WPF UserControl zostanie ostatecznie hostowany w Elementhostie WinForms. To, co musiałem znaleźć, to ScrollViewer, w którym umieszczono mój Kontroler UserControl, ponieważ był to element pokazujący paski przewijania.

Ta ogólna metoda rekurencyjna (zmodyfikowana z innej odpowiedzi) pomoże znaleźć rodzica określonego typu w drzewie logicznym (możliwe jest również użycie drzewa wizualnego) i zwróci go, jeśli zostanie znaleziony.

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);
    }

Call ta metoda pomocnicza zamiast Window.GetWindow() i kontynuuje odpowiedź Jasona na odpowiednie zdarzenia. W przypadku ScrollViewer jest to Zdarzenie ScrollChanged.

 1
Author: zemien,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-10 23:05:40

Pobierz przykładową pozycję wyskakującego okienka pod adresem:

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

Próbka kodu używa klasy CustomPopupPlacement z obiektem Rect i wiąże się z poziomymi i pionowymi przesunięciami, aby przesunąć wyskakujące okienko.

<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
Author: Zamboni,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-05-31 14:38:10