Как я могу переместить всплывающее окно 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
изменилась и должна быть прочитана снова? Можно ли вручную установить позицию всплывающего окна?
В настоящее время у меня есть очень грубый обходной путь, который включает закрытие, а затем повторное открытие всплывающего окна, что вызывает некоторые проблемы с перекрашиванием.
Решение
Я посмотрел несколько вариантов и образцов там. Мне кажется, что лучше всего работает «поднять» одно из свойств, которое заставляет 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 кажется хорошим подходом для некоторых сценариев.
Другие советы
Просто чтобы добавить к отличному решению 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 ()
не возвращал Null
, подключите обработчик к событию Loaded для размещения кода NathanAW (см. < a href = "https://stackoverflow.com/a/304604/718325"> комментарий Питера Уолка к аналогичному обсуждению 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;
};
}
}
}
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);
}
}
Если вы хотите переместить всплывающее окно, есть простой прием: измените его положение, затем установите:
IsOpen = false;
IsOpen = true;
Чтобы добавить ответ Джейсона Фрэнка, подход 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.
Я изменил код от Джейсона, потому что всплывающее окно уже на переднем плане, если окно не активировано. Есть ли какая-либо опция в классе 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;
}
Вы не можете сделать это. Когда всплывающее окно отображается на экране, оно не переустанавливает себя, если его родитель переустанавливается. Это поведение Popup контроля. проверьте это: http://msdn.microsoft .com / EN-US / библиотека / system.windows.controls.primitives.popup.aspx
вы можете использовать Window (с WindowStyle = None) вместо Popup, что может решить вашу проблему.
Загрузите пример позиции всплывающего окна в:
http://msdn.microsoft. ком / EN-US / библиотека / 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}"