Как я могу переместить всплывающее окно WPF при перемещении его элемента привязки?
У меня есть всплывающее окно, определенное следующим образом:
<Popup
Name="myPopup"
StaysOpen="True"
Placement="Bottom"
PlacementRectangle="0,20,0,20"
PlacementTarget="{Binding ElementName=myPopupAnchor}">
<TextBlock ... />
</Popup>
Я добавил обработчики событий в элемент myPopupAnchor
для событий MouseEnter
и MouseLeave
. Два обработчика событий переключают видимость всплывающих окон.
Моя проблема заключается в том, что позиция myPopupAnchor считывается только тогда, когда всплывающее окно сначала отображается или скрывается, а затем отображается снова. Если якорь перемещается, всплывающее окно не работает.
Я ищу способы обойти это, мне нужен движущийся всплывающий экран. Могу ли я уведомить WPF о том, что привязка PlacementTarget
изменилась и должна быть прочитана снова? Могу ли я вручную установить всплывающее окно?
В настоящее время у меня очень грубое решение, которое включает закрытие, а затем вскрытие всплывающего окна, что вызывает некоторые проблемы с перерисованием.
Ответы
Ответ 1
Я посмотрел пару вариантов и образцов. То, что, кажется, работает лучше всего для меня, - это "поднять" одно из свойств, которое заставляет 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 кажется хорошим подходом к некоторым сценариям.
Ответ 2
До добавить к NathanAW отличное решение выше, я думал, что указать какой-то контекст, например , где, чтобы поместить код С# в этом случае. Я все еще довольно новичок в WPF, поэтому сначала попытался выяснить, где поставить код NathanAW. Когда я попытался поместить этот код в конструктор для UserControl, на котором размещался мой всплывающий экран, Window.GetWindow()
всегда возвращал Null
(поэтому код "bump" никогда не выполнялся). Поэтому я думал, что другие новички могут извлечь выгоду из того, что видят вещи в контексте.
Прежде чем показывать С# в контексте, вот пример 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 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;
};
}
}
}
Ответ 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);
}
}
Ответ 4
Если вы хотите переместить всплывающее окно, есть простой трюк: измените его положение, а затем установите:
IsOpen = false;
IsOpen = true;
Ответ 5
Вы не можете этого сделать. Когда всплывающее окно отображается на экране, оно не перестраивается, если его родительский элемент перемещен. Это поведение управления всплывающим окном.
проверьте это: http://msdn.microsoft.com/en-us/library/system.windows.controls.primitives.popup.aspx
вы можете использовать окно (с WindowStyle = None) вместо Popup, которое может решить вашу проблему.
Ответ 6
Я изменил код от Jason, потому что Popup уже находится в Foreground, если окно не активировано. Есть ли какой-либо вариант в классе 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
Чтобы добавить к Jason Frank ответ, подход 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.
Ответ 8
Загрузите образец всплывающего всплывающего окна по адресу:
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}"