4

我基本上是在尝试在按钮悬停时实现弹出窗口。当用户将鼠标悬停在按钮上时,我希望弹出窗口出现。如果不是,我只想显示标签。它有点像一个工具提示,只是我不希望 Popup 在经过一段时间后消失。我有点让它在按钮上使用 ControlTemplate 工作,但有两个警告:

  1. 当我将鼠标悬停在按钮下方的区域上时,屏幕会在弹出窗口和标签之间闪烁。
  2. 我希望弹出窗口底部和中心对齐。

Xaml 代码:

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

编辑:修复了闪烁问题。只需要将弹出窗口的位置设置为底部和中心。

4

5 回答 5

2

我最终不得不编写一个转换器,根据弹出窗口的高度和放置目标将其向下移动。

使用这样的多重绑定将信息传递到我的 VerticalOffset 转换器中:

<MultiBinding Converter="{StaticResource PopupVerticalAligner}">
    <Binding RelativeSource="{RelativeSource Self}" Path="PlacementTarget.ActualHeight" />
    <Binding RelativeSource="{RelativeSource Self}" Path="ActualHeight" />
</MultiBinding>
于 2012-08-14T15:19:11.580 回答
0

您是否尝试过 MouseEnter 事件?然后您可以在 DispatcherTimer 上打开弹出窗口,然后再次将其关闭。

于 2012-08-06T20:21:30.713 回答
0

添加到 sohum 的答案中,这就是我如何让我的 ListView-Popup 底部在 ToggleButton 下居中。它根据列表视图的宽度正确地水平偏移。我还留下了一些让切换按钮直观行为的点点滴滴,比如再次单击切换按钮以隐藏弹出窗口。

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

BoolToInvertedBoolConverter 如果为 false 则返回 true ,如果为 true 则返回 false (以允许弹出窗口在用户尝试取消切换时折叠),并且可以在 sohum 的链接中找到 CenterToolTipConverter 。

于 2014-07-29T21:27:55.893 回答
0

更好的是把你的PlacementTarget控件放在 a 中Grid,并使你的Popup控件成为同一个子控件,Grid同时保持Placement=Bottom. 这将Popup在 PlacementTarget 控件下显示您的底部居中。没有转换器,没有样式,简单的 XAML。

于 2016-11-01T07:58:32.830 回答
0

虽然这已经是一个老问题了,但我也有同样的需求——我需要能够将 aPopup与其放置目标对齐。对转换器解决方案不满意,我提出了自己的解决方案,使用附加的依赖属性,我在这里与您和任何有相同需求的人分享。

注意:此解决方案不包括如何Popup在鼠标悬停时显示 a 。它只涵盖了最棘手的部分 -Popup与其放置目标的对齐。有几种方法可以显示Popup鼠标悬停,例如使用TriggersBindings,这两种方法都在 StackOverflow 上广泛介绍。

附加依赖属性解决方案

此解决方案使用单个静态类公开一些附加的依赖属性。使用这些属性,您可以水平或垂直对齐 aPopup到 itsPlacementTarget或 its 。仅当'属性的值表示一条边(、或)时PlacementRectangle才会发生对齐。PopupPlacementLeftTopRightBottom

执行

弹出属性.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


    }
}

这个怎么运作

上面的代码创建了一个静态类,它公开了Popup控件的 4 个附加依赖属性。即,它们是HorizontalPlacementAlignmentVerticalPlacementAlignment和。HorizontalOffsetVerticalOffset

HorizontalPlacementAlignmentVerticalPlacementAlignment附加的依赖属性允许您将弹出窗口相对于它的PlacementTargetor对齐PlacementRectangle。为此,该机制使用Popup.HorizontalOffsetPopup.VerticalOffset属性来定位Popup.

因为机制使用Popup.HorizontalOffsetPopup.VerticalOffset属性来工作,所以这个类提供了自己的HorizontalOffsetVerticalOffset属性(附加的依赖属性)。Popup除了对齐之外,您还可以使用它们来调整位置。

Popup每次Popup打开时,该机制都会自动更新位置。但是,当弹出窗口大小发生变化或放置目标或放置矩形大小发生变化时,它的位置不会自动更新。尽管如此,只要投入更多的工作,该功能就可以轻松实现。

使用示例

Popup您将在下面的示例中使用附加属性。在这个例子中,我们有一个简单的Button和一个Popup. 与 '的Popup底部对齐并以' 的中心Button水平居中显示。Button

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

通过将ui:PopupProperties.HorizontalPlacementAlignment="Center"和添加ui:PopupProperties.VerticalOffset="2"到 a Popup,它将与其放置目标的水平中心对齐,并具有 2 个 WPF 单位的垂直偏移量。

注意 上的xmlns:ui="clr-namespace:MyProjectName.Ui"使用Popup。此属性仅从项目的命名空间导入类型,并通过使用XAML 属性上MyProjectName.Ui的前缀使它们可用。ui:在示例中,Popup为简单起见,此属性设置在 上,但您通常会将其设置在您的WindowResourceDictionary使用这些自定义附加依赖项属性的位置。

结论

使用附加的依赖属性来实现此功能背后的想法是使其在 XAML 中的使用尽可能简单。对于简单的一次性需求,使用转换器可能更易于实现。但是,在这种情况下,使用附加的依赖属性可能会提供一种更加动态和使用友好的方法。

于 2020-09-20T13:13:47.273 回答