Wpf 弹出窗口在PlacementTarget的中心和底部对齐
我基本上是想在悬停按钮上实现一个弹出窗口。当用户悬停在按钮上方时,我希望弹出窗口出现。如果没有,我只希望标签出现。它有点像一个工具提示,只是我不想在一段时间后弹出窗口消失。我在按钮上使用了一个ControlTemplate,但有两个注意事项:Wpf 弹出窗口在PlacementTarget的中心和底部对齐,wpf,xaml,popup,controltemplate,Wpf,Xaml,Popup,Controltemplate,我基本上是想在悬停按钮上实现一个弹出窗口。当用户悬停在按钮上方时,我希望弹出窗口出现。如果没有,我只希望标签出现。它有点像一个工具提示,只是我不想在一段时间后弹出窗口消失。我在按钮上使用了一个ControlTemplate,但有两个注意事项: 当我将鼠标悬停在按钮下方的区域时,屏幕在弹出窗口和标签之间闪烁 我希望弹出窗口底部和中心对齐 Xaml代码: <Window> <Window.Resources> <Style x:Key="Labe
<Window>
<Window.Resources>
<Style x:Key="LabelStyle" TargetType="Label">
<Setter Property="Margin" Value="0, 0, 0, 5" />
<Setter Property="Width" Value="58" />
<Setter Property="Height" Value="28" />
<Setter Property="Padding" Value="1, 0, 1, 0" />
</Style>
<ControlTemplate x:Key="ButtonControlTemplate" TargetType="Button">
<StackPanel>
<Button Width="48" Height="48" Background="White" Name="ItemButton">
<ContentPresenter Content="{TemplateBinding Property=ContentControl.Content}" />
</Button>
<Label Style="{StaticResource LabelStyle}" VerticalContentAlignment="Top" HorizontalContentAlignment="Center" Name="ItemLabel">
<TextBlock TextWrapping="Wrap" TextAlignment="Center" FontSize="11" LineHeight="13" LineStackingStrategy="BlockLineHeight">
Hello World!
</TextBlock>
</Label>
<Popup Name="ItemPopup" Placement="Bottom" PlacementTarget="{Binding ElementName=ItemButton}">
<TextBlock Background="Red">Hello World!</TextBlock>
</Popup>
</StackPanel>
<ControlTemplate.Triggers>
<Trigger SourceName="ItemButton" Property="IsMouseOver" Value="True">
<Setter TargetName="ItemLabel" Property="Visibility" Value="Hidden" />
<Setter TargetName="ItemPopup" Property="IsOpen" Value="True" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Window.Resources>
<StackPanel>
<Button Background="Green" Template="{StaticResource ButtonControlTemplate}">
<Image Source="http://leduc998.files.wordpress.com/2010/10/msft_logo.jpg" />
</Button>
</StackPanel>
</Window>
你好,世界!
你好,世界!
编辑:修复了闪烁问题。只需将弹出窗口的位置设置为底部和中心。您尝试过MouseEnter事件吗?然后,您可以在Dispatchermer上打开弹出窗口,然后再次将其关闭。我最后不得不编写一个转换器,根据弹出窗口的高度和放置目标将其向下移动 使用类似这样的多重绑定将信息传递到我的VerticalOffset转换器:
<MultiBinding Converter="{StaticResource PopupVerticalAligner}">
<Binding RelativeSource="{RelativeSource Self}" Path="PlacementTarget.ActualHeight" />
<Binding RelativeSource="{RelativeSource Self}" Path="ActualHeight" />
</MultiBinding>
添加到sohum中,下面是我如何将ListView弹出窗口底部置于切换按钮的中心位置。它会根据listview的宽度进行正确的水平偏移。我还留下了一些零碎的东西,让切换按钮具有直观的行为,比如再次单击切换按钮隐藏弹出窗口
<ToggleButton x:Name="ParentToggleButton" IsChecked="{Binding ToggleButtonStatus}" IsHitTestVisible="{Binding ElementName=ToggledPopup, Path=IsOpen, Converter={StaticResource BoolToInvertedBoolConverter}}" >
<ToggleButton.Content>...</ToggleButton.Content>
</ToggleButton>
<Popup PlacementTarget="{Binding ElementName=ParentToggleButton}" Placement="Bottom" StaysOpen="False" IsOpen="{Binding ToggleButtonStatus}" x:Name="ToggledPopup">
<Popup.HorizontalOffset>
<MultiBinding Converter="{StaticResource CenterToolTipConverter}">
<Binding RelativeSource="{RelativeSource Self}" Path="PlacementTarget.ActualWidth"/>
<Binding ElementName="INeedYourWidth" Path="ActualWidth"/>
</MultiBinding>
</Popup.HorizontalOffset>
<ListView x:Name="INeedYourWidth" ItemsSource="{Binding ItemsSource}" >
<ListView.ItemTemplate>
<DataTemplate>...</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Popup>
...
...
BooltOnvertDBoolconverter在false时返回true,在true时返回false(当用户尝试取消切换时,允许弹出窗口崩溃)在sohum's中可以找到CenterToolTiConverter。更好的方法是将您的PlacementTarget控件放在
网格中,并使您的弹出窗口控件成为同一网格的子控件,同时保持Placement=Bottom
。这将显示PlacementTarget控件下居中的弹出窗口底部。没有转换器,没有样式,只有简单的XAML。虽然这已经是一个老问题,但我也有同样的需要-我需要能够将弹出窗口与其放置目标对齐。不满意转换器解决方案,我提出了自己的解决方案,使用附加的依赖属性,我在这里与您和任何有相同需求的人共享
注意:此解决方案不包括如何在鼠标上显示弹出窗口
悬停。它只涵盖了最棘手的部分-将弹出窗口
与其放置目标对齐。有几种方法可以在鼠标悬停时显示弹出窗口
,比如使用触发器或绑定,这两种方法在StackOverflow上都有广泛的介绍
附加依赖属性解决方案
此解决方案使用一个静态类,该类公开一些附加的依赖属性。使用这些属性,您可以水平或垂直地将弹出窗口
与其放置目标
或其放置矩形
对齐。只有当弹出窗口
的放置
属性的值表示一条边(左
,上
,右
或下
)时,才会发生对齐
实施
PopupProperties.cs
using System;
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Media;
namespace MyProjectName.Ui
{
/// <summary>
/// Exposes attached dependency properties that provide
/// additional functionality for <see cref="Popup"/> controls.
/// </summary>
/// <seealso cref="Popup"/>
/// <seealso cref="DependencyProperty"/>
public static class PopupProperties
{
#region Properties
#region IsMonitoringState attached dependency property
/// <summary>
/// Attached <see cref="DependencyProperty"/>. This property
/// registers (<b>true</b>) or unregisters (<b>false</b>) a
/// <see cref="Popup"/> from the popup monitoring mechanism
/// used internally by <see cref="PopupProperties"/> to keep
/// the <see cref="Popup"/> in synchrony with the
/// <see cref="PopupProperties"/>' attached properties. A
/// <see cref="Popup"/> will be automatically unregistered from
/// this mechanism after it is unloaded.
/// </summary>
/// <seealso cref="Popup"/>
private static readonly DependencyProperty IsMonitoringStateProperty
= DependencyProperty.RegisterAttached("IsMonitoringState",
typeof(bool), typeof(PopupProperties),
new FrameworkPropertyMetadata(false,
FrameworkPropertyMetadataOptions.None,
new PropertyChangedCallback(IsMonitoringStatePropertyChanged)));
private static void IsMonitoringStatePropertyChanged(
DependencyObject dObject, DependencyPropertyChangedEventArgs e)
{
Popup popup = (Popup)dObject;
bool value = (bool)e.NewValue;
if (value)
{
// Attach popup.
popup.Opened += Popup_Opened;
popup.Unloaded += Popup_Unloaded;
// Update popup.
UpdateLocation(popup);
}
else
{
// Detach popup.
popup.Opened -= Popup_Opened;
popup.Unloaded -= Popup_Unloaded;
}
}
private static bool GetIsMonitoringState(Popup popup)
{
if (popup is null)
throw new ArgumentNullException(nameof(popup));
return (bool)popup.GetValue(IsMonitoringStateProperty);
}
private static void SetIsMonitoringState(Popup popup, bool isMonitoringState)
{
if (popup is null)
throw new ArgumentNullException(nameof(popup));
popup.SetValue(IsMonitoringStateProperty, isMonitoringState);
}
#endregion
#region HorizontalPlacementAlignment attached dependency property
public static readonly DependencyProperty HorizontalPlacementAlignmentProperty
= DependencyProperty.RegisterAttached("HorizontalPlacementAlignment",
typeof(AlignmentX), typeof(PopupProperties),
new FrameworkPropertyMetadata(AlignmentX.Left,
FrameworkPropertyMetadataOptions.None,
new PropertyChangedCallback(HorizontalPlacementAlignmentPropertyChanged)),
new ValidateValueCallback(HorizontalPlacementAlignmentPropertyValidate));
private static void HorizontalPlacementAlignmentPropertyChanged(
DependencyObject dObject, DependencyPropertyChangedEventArgs e)
{
Popup popup = (Popup)dObject;
SetIsMonitoringState(popup, true);
UpdateLocation(popup);
}
private static bool HorizontalPlacementAlignmentPropertyValidate(object obj)
{
return Enum.IsDefined(typeof(AlignmentX), obj);
}
public static AlignmentX GetHorizontalPlacementAlignment(Popup popup)
{
if (popup is null)
throw new ArgumentNullException(nameof(popup));
return (AlignmentX)popup.GetValue(HorizontalPlacementAlignmentProperty);
}
public static void SetHorizontalPlacementAlignment(Popup popup, AlignmentX alignment)
{
if (popup is null)
throw new ArgumentNullException(nameof(popup));
popup.SetValue(HorizontalPlacementAlignmentProperty, alignment);
}
#endregion
#region VerticalPlacementAlignment attached dependency property
public static readonly DependencyProperty VerticalPlacementAlignmentProperty
= DependencyProperty.RegisterAttached("VerticalPlacementAlignment",
typeof(AlignmentY), typeof(PopupProperties),
new FrameworkPropertyMetadata(AlignmentY.Top,
FrameworkPropertyMetadataOptions.None,
new PropertyChangedCallback(VerticalPlacementAlignmentPropertyChanged)),
new ValidateValueCallback(VerticalPlacementAlignmentPropertyValidate));
private static void VerticalPlacementAlignmentPropertyChanged(
DependencyObject dObject, DependencyPropertyChangedEventArgs e)
{
Popup popup = (Popup)dObject;
SetIsMonitoringState(popup, true);
UpdateLocation(popup);
}
private static bool VerticalPlacementAlignmentPropertyValidate(object obj)
{
return Enum.IsDefined(typeof(AlignmentY), obj);
}
public static AlignmentY GetVerticalPlacementAlignment(Popup popup)
{
if (popup is null)
throw new ArgumentNullException(nameof(popup));
return (AlignmentY)popup.GetValue(VerticalPlacementAlignmentProperty);
}
public static void SetVerticalPlacementAlignment(Popup popup, AlignmentY alignment)
{
if (popup is null)
throw new ArgumentNullException(nameof(popup));
popup.SetValue(VerticalPlacementAlignmentProperty, alignment);
}
#endregion
#region HorizontalOffset attached dependency property
public static readonly DependencyProperty HorizontalOffsetProperty
= DependencyProperty.RegisterAttached("HorizontalOffset",
typeof(double), typeof(PopupProperties),
new FrameworkPropertyMetadata(0d,
FrameworkPropertyMetadataOptions.None,
new PropertyChangedCallback(HorizontalOffsetPropertyChanged)),
new ValidateValueCallback(HorizontalOffsetPropertyValidate));
private static void HorizontalOffsetPropertyChanged(
DependencyObject dObject, DependencyPropertyChangedEventArgs e)
{
Popup popup = (Popup)dObject;
SetIsMonitoringState(popup, true);
UpdateLocation(popup);
}
private static bool HorizontalOffsetPropertyValidate(object obj)
{
double value = (double)obj;
return !double.IsNaN(value) && !double.IsInfinity(value);
}
public static double GetHorizontalOffset(Popup popup)
{
if (popup is null)
throw new ArgumentNullException(nameof(popup));
return (double)popup.GetValue(HorizontalOffsetProperty);
}
public static void SetHorizontalOffset(Popup popup, double offset)
{
if (popup is null)
throw new ArgumentNullException(nameof(offset));
popup.SetValue(HorizontalOffsetProperty, offset);
}
#endregion
#region VerticalOffset attached dependency property
public static readonly DependencyProperty VerticalOffsetProperty
= DependencyProperty.RegisterAttached("VerticalOffset",
typeof(double), typeof(PopupProperties),
new FrameworkPropertyMetadata(0d,
FrameworkPropertyMetadataOptions.None,
new PropertyChangedCallback(VerticalOffsetPropertyChanged)),
new ValidateValueCallback(VerticalOffsetPropertyValidate));
private static void VerticalOffsetPropertyChanged(
DependencyObject dObject, DependencyPropertyChangedEventArgs e)
{
Popup popup = (Popup)dObject;
SetIsMonitoringState(popup, true);
UpdateLocation(popup);
}
private static bool VerticalOffsetPropertyValidate(object obj)
{
double value = (double)obj;
return !double.IsNaN(value) && !double.IsInfinity(value);
}
public static double GetVerticalOffset(Popup popup)
{
if (popup is null)
throw new ArgumentNullException(nameof(popup));
return (double)popup.GetValue(VerticalOffsetProperty);
}
public static void SetVerticalOffset(Popup popup, double offset)
{
if (popup is null)
throw new ArgumentNullException(nameof(offset));
popup.SetValue(VerticalOffsetProperty, offset);
}
#endregion
#endregion Properties
#region Methods
private static void OnMonitorState(Popup popup)
{
if (popup is null)
throw new ArgumentNullException(nameof(popup));
UpdateLocation(popup);
}
private static void UpdateLocation(Popup popup)
{
// Validate parameters.
if (popup is null)
throw new ArgumentNullException(nameof(popup));
// If the popup is not open, we don't need to update its position yet.
if (!popup.IsOpen)
return;
// Setup initial variables.
double offsetX = 0d;
double offsetY = 0d;
PlacementMode placement = popup.Placement;
UIElement placementTarget = popup.PlacementTarget;
Rect placementRect = popup.PlacementRectangle;
// If the popup placement mode is an edge of the placement target,
// determine the alignment offset.
if (placement == PlacementMode.Top || placement == PlacementMode.Bottom
|| placement == PlacementMode.Left || placement == PlacementMode.Right)
{
// Try to get the popup size. If its size is empty, use the size
// of its child, if any child exists.
Size popupSize = GetElementSize(popup);
UIElement child = popup.Child;
if ((popupSize.IsEmpty || popupSize.Width <= 0d || popupSize.Height <= 0d)
&& child != null)
{
popupSize = GetElementSize(child);
}
// Get the placement rectangle size. If it's empty, get the
// placement target's size, if a target is set.
Size targetSize;
if (placementRect.Width > 0d && placementRect.Height > 0d)
targetSize = placementRect.Size;
else if (placementTarget != null)
targetSize = GetElementSize(placementTarget);
else
targetSize = Size.Empty;
// If we have a valid popup size and a valid target size, determine
// the offset needed to align the popup to the target rectangle.
if (!popupSize.IsEmpty && popupSize.Width > 0d && popupSize.Height > 0d
&& !targetSize.IsEmpty && targetSize.Width > 0d && targetSize.Height > 0d)
{
switch (placement)
{
// Horizontal alignment offset.
case PlacementMode.Top:
case PlacementMode.Bottom:
switch (GetHorizontalPlacementAlignment(popup))
{
case AlignmentX.Left:
offsetX = 0d;
break;
case AlignmentX.Center:
offsetX = -(popupSize.Width - targetSize.Width) / 2d;
break;
case AlignmentX.Right:
offsetX = -(popupSize.Width - targetSize.Width);
break;
default:
break;
}
break;
// Vertical alignment offset.
case PlacementMode.Left:
case PlacementMode.Right:
switch (GetVerticalPlacementAlignment(popup))
{
case AlignmentY.Top:
offsetY = 0d;
break;
case AlignmentY.Center:
offsetY = -(popupSize.Height - targetSize.Height) / 2d;
break;
case AlignmentY.Bottom:
offsetY = -(popupSize.Height - targetSize.Height);
break;
default:
break;
}
break;
default:
break;
}
}
}
// Add the developer specified offsets to the offsets we've calculated.
offsetX += GetHorizontalOffset(popup);
offsetY += GetVerticalOffset(popup);
// Apply the final computed offsets to the popup.
popup.SetCurrentValue(Popup.HorizontalOffsetProperty, offsetX);
popup.SetCurrentValue(Popup.VerticalOffsetProperty, offsetY);
}
private static Size GetElementSize(UIElement element)
{
if (element is null)
return new Size(0d, 0d);
else if (element is FrameworkElement frameworkElement)
return new Size(frameworkElement.ActualWidth, frameworkElement.ActualHeight);
else
return element.RenderSize;
}
#endregion Methods
#region Event handlers
private static void Popup_Unloaded(object sender, RoutedEventArgs e)
{
if (sender is Popup popup)
{
// Stop monitoring the popup state, because it was unloaded.
SetIsMonitoringState(popup, false);
}
}
private static void Popup_Opened(object sender, EventArgs e)
{
if (sender is Popup popup)
{
OnMonitorState(popup);
}
}
#endregion Event handlers
}
}
通过将ui:popupProperty.HorizontalPlacementAlignment=“Center”
和ui:popupProperty.VerticalOffset=“2”
添加到弹出窗口
,它将与其放置目标的水平中心对齐,并具有2个WPF垂直偏移单位
注意在弹出窗口上使用xmlns:ui=“clr namespace:MyProjectName.ui”
。此属性仅从项目上的MyProjectName.Ui
命名空间导入类型,并通过在XAML属性上使用Ui:
前缀使其可用。在本例中,为简单起见,此属性在弹出窗口
上设置,但您通常会在使用这些自定义附加依赖项属性的窗口
或资源字典
上设置
结论
使用附加的依赖属性来实现此功能背后的想法是使其在XAML中的使用尽可能简单。对于简单的一次性需求,使用转换器可能更容易实现。但是,在这种情况下,使用附加的依赖项属性可能会提供一种更动态、更友好的方法。Good:)。您可以使用MVVM Light和RelayCommand调用viewmodel中的弹出窗口。从这里开始,使用子窗口服务或类似的东西来处理窗口逻辑。我真的不希望为了在正确的位置和方式显示标签而向另一个工具箱添加依赖项。上面的代码几乎可以工作。我觉得我的触发器只是在错误的对象上,我缺少了一个如何使用弹出窗口的放置属性的技巧。。。您可以执行标记扩展并将其绑定到事件。如果我没有弄错的话,你也可以对混合行为做同样的处理。我必须说,尽管MVVM light在调用viewmodel上的东西时不是最好的,但它确实很棒。这一天节省了好几次时间。我能够修复SourceName属性的闪烁问题,而我在触发器中没有注意到这个问题。现在弹出窗口的问题迫在眉睫。这似乎很愚蠢,因为MSFT提供了中间和底部,所以默认情况下不会提供。。。将底部对齐并
<Button x:Name="MyTargetElement">My Button</Button>
<Popup xmlns:ui="clr-namespace:MyProjectName.Ui"
PlacementTarget="{Binding ElementName=MyTargetElement}"
Placement="Bottom"
ui:PopupProperties.HorizontalPlacementAlignment="Center"
ui:PopupProperties.VerticalOffset="2">
</Popup>