65.9K
CodeProject 正在变化。 阅读更多。
Home

C# WPF 动画装饰器弹出控件

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.75/5 (3投票s)

2016 年 10 月 13 日

CPOL

3分钟阅读

viewsIcon

29542

downloadIcon

1123

一个类似弹出窗口的控件, 嵌入在装饰器层中, 可以进行动画处理

引言

应用程序中的可视空间非常宝贵。即使在拥有惊人分辨率的 4K 显示器时代,设计师和开发人员仍然会面临在更小的区域内呈现更多信息的压力。多年来,出现了新的技术来满足这些需求,例如,对话框、弹出框等。另一种向用户显示附加信息的方法是使用 Adorner 类。Adorner 类与其他控件的不同之处在于它显示在 Adorner Layer 中,该图层位于应用程序中所有其他 UIElement 之上。Adorner 的难点在于没有现成的方法来显示供用户交互的控件。以下是如何解决这个小缺陷。可能还有更优雅的解决方案,但这个解决方案可以完成任务。

背景

有时,可以使用简单的描述来呈现数据结构,但实际上,它包含更广泛的数据,这些数据会占用宝贵的屏幕空间。过去,这是通过多文档界面 (MDI) 处理的,允许用户访问数据。借助 WPF 中提供的视觉支持,我们只需单击一下,即可在应用程序的可视呈现区域内显示或隐藏所有这些附加信息。

PopupAdorner

PopupAdorner 类充当需要显示的任何附加信息的可视容器。为了使本文简短,我没有包含任何检查 adorner 在窗口中的位置的代码。

using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;

namespace UI
{
    public class PopupAdorner : Adorner
    {
        private VisualCollection _visuals;
        private ContentPresenter _presenter;
        
        /// <summary>
        /// Creates a new popup adorner with the specified content.
        /// </summary>
        /// <param name="adornedElement">The UIElement that will be adorned</param>
        /// <param name="content">The content that will be display inside the popup</param>
        /// <param name="offset">The popup position in regards to the adorned element</param>
        public PopupAdorner(UIElement adornedElement, UIElement content, Vector offset)
            : base(adornedElement)
        {
            _visuals = new VisualCollection(this);
            _presenter = new ContentPresenter();
            _visuals.Add(_presenter);
            _presenter.Content = content;
            Margin = new Thickness(offset.X, offset.Y, 0, 0);
        }
        
        protected override Size MeasureOverride(Size constraint)
        {
            _presenter.Measure(constraint);
            return _presenter.DesiredSize;
        }
        protected override Size ArrangeOverride(Size finalSize)
        {
            _presenter.Arrange(new Rect(0, 0, finalSize.Width, finalSize.Height));
            return _presenter.RenderSize;
        }
        protected override Visual GetVisualChild(int index)
        {
            return _visuals[index];
        }
        protected override int VisualChildrenCount
        {
            get
            {
                return _visuals.Count;
            }
        }
        
        /// <summary>
        /// Brings the popup into view.
        /// </summary>
        public void Show()
        {
            AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(AdornedElement);
            adornerLayer.Add(this);
        }
        /// <summary>
        /// Removes the popup into view.
        /// </summary>
        public void Hide()
        {
            AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(AdornedElement);
            adornerLayer.Remove(this);
        }
    }
}

通过 VisualCollection 类,我们可以克服 Adorner 类的开箱即用设计。通过覆盖默认渲染行为,我们可以在 Adorner 中包含任何类型的控件及其子控件。限制是我们只能有一个父对象。

定义 Adorner 内容

有两种方法可以构建内容,自定义 UserControl 或通过代码隐藏。由于使用 VS 构建自定义用户控件非常简单,因此我将演示如何以编程方式构建一个简单的控件。

using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace UI
{
    public static class DynamicAdornerContent
    {
        public static Border GetSimpleInfoPopup(string[] textInfo)
        {
            Border popupBorder = new Border() 
                { BorderBrush = Brushes.Black, BorderThickness = new Thickness(1D) };
            Grid container = new Grid() 
                { Background = Brushes.White };
            
            Style txtStyle = new Style();
            txtStyle.TargetType = typeof(TextBlock);
            txtStyle.Setters.Add(new Setter() 
                { 
                    Property = FrameworkElement.HorizontalAlignmentProperty, 
                    Value = HorizontalAlignment.Left 
                });
            txtStyle.Setters.Add(new Setter() 
                { 
                    Property = FrameworkElement.VerticalAlignmentProperty, 
                    Value = VerticalAlignment.Center 
                });
            
            for (int i = 0; i < textInfo.Length; ++i)
            {
                container.RowDefintions.Add(new RowDefinition() 
                    { Height = new GridLength(26D) };

                var tbox = new TextBlock();
                tbox.Style = txtStyle;
                tbox.Text = textInfo[i];
                
                Grid.SetRow(tbox, i);
                container.Children.Add(tbox);
            }
            
            popupBorder.Child = container;
            return popupBorder;
        }
    }
}

使用 PopupAdorner

Adorner 可用于装饰任何类型的 UIElement。为此,我将构建一个自定义用户控件,其中包含一个 TextBlock 和一个简单的箭头图像,用户可以单击该图像来展开或折叠 PopupAdorner

XAML

<UserControl>
    <UserControl.Resources>
        <DrawingImage x:Key="img_Arrow">
            <DrawingImage.Drawing>
                <DrawingGroup>
                    <GeometryDrawing Brush="Gray">
                        <GeometryDrawing.Pen>
                            <Pen Brush="DarkGray" 
                                EndLineCap="Round" 
                                LineJoin="Round" 
                                StartLineCap="Round" 
                                Thickness="1"/>
                        </GeometryDrawing.Pen>
                        <GeometryDrawing.Geometry>
                            <PathGeometry>
                                <PathFigure IsFilled="True" StartPoint="0,5">
                                    <PathFigure.Segments>
                                        <LineSegment Point="5,0"/>
                                        <LineSegment Point="5,10"/>
                                        <LineSegment Point="0,5"/>
                                    </Pathfigure.Segments>
                                </PathFigure>
                            </PathGeometry>
                        </GeometryDrawing.Geometry>
                    </GeometryDrawing>
                </DrawingGroup>
            </DrawingImage.Drawing>
        </DrawingImage>
    </UserControl.Resources>
    <Grid Background="White" Height="28" Width="300" x:Name="grid_Container">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="1*"/>
            <ColumnDefinition Width="20"/>
        </Grid.ColumnDefinitions>
        <TextBlock Grid.Column="0" 
            HorizontalAlignment="Stretch" 
            TextTrimming="CharacterEllipsis" 
            TextWrapping="NoWrap" 
            VerticalAlignment="Center" 
            x:Name="txt_DisplayText"/>
        <Image Grid.Column="1" 
            Height="18" 
            MouseDown="TogglePopup" 
            Source="{StaticResource ResourceKey=img_Arrow}" 
            ToolTip="Expand" 
            Width="16" 
            x:Name="ctl_Expander"/>
    </Grid>
</UserControl>

代码后置

using System;
using System.Windows;
using System.Windows.Controls;
using System.Widnows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;

namespace UI
{
    public class ControlWithPopup : UserControl
    {
        private Border _infoContainer;
        private PopupAdorner _infoPopup;
        private bool _isExpanded;
        
        public ControlWithPopup(string displayText, string[] additionalInfo)
        {
            InitializeComponent();
            _displayText.Text = displayText;
            _infoContainer = DynamicAdornerContent.GetSimpleInfoPopup(additionalInfo);
        }
        
        private TogglePopup(sender object, MouseButtonEventArgs e)
        {
            if (_isExpanded)
            {
                _infoPopup.Hide();
                _isExpanded = false;
            }
            else
            {
                if (_infoPopup == null)
                {
                    _infoPopup = new PopupAdorner(
                        grid_Container, 
                        _infoContainer, 
                        new Vector(0, 29));
                }
                
                _infoPopup.Show();
                _isExpanded = true;
            }
        }
    }
}
这样,当用户单击箭头图像时,提供的附加信息将弹出到自定义用户控件的正下方,并在再次单击箭头时从视图中消失。

添加动画

此时,除了显示和隐藏附加信息之外,该控件几乎没有任何视觉效果。我们需要一些动画来使过渡更具视觉吸引力。

// add these animations, transforms, and method to the ControlWithPopup class
private DoubleAnimation _extendAnimation;
private DoubleAnimation _collapseAnimation;
private DoubleAnimation _extendArrowAnimation;
private DoubleAnimation _collapseArrowAnimation;
private RotateTransform _extendArrowTransform;
private RotateTransform _collapseArrowTransform;

// call this in the constructor to build the animations 
// for rotating the arrow in response to user interactions
private void InitializeAnimations()
{
    _extendArrowAnimation = 
        new DoubleAnimation(0.0, -90.0, new Duration(TimeSpan.FromSeconds(0.25)));
    _collapseArrowAnimation = 
        new DoubleAnimation(-90.0, 0.0, new Duration(TimeSpan.FromSeconds(0.25)));
    _extendArrowTransform = 
        new RotateTransform() { Angle = -90, CenterX = 0.5, CenterY = 0.5 };
    _collapseArrowTransform = 
        new RotateTransform() { Angle = 0, CenterX = 0.5, CenterY = 0.5 };
}

// alter the TogglePopup function to include animation calls
private TogglePopup(sender object, MouseButtonEventArgs e)
{
    ctl_Expander.RenderTransformOrigin = new Point(0.5, 0.5);
    
    if (_isExpanded)
    {
        ctl_Expander.RenderTransform = _collapseArrowTransform;
        ctl_Expander.BeginAnimation(RotateTransform.AngleProperty, _collapseArrowAnimation);
        
        _collapseAnimation = new DoubleAnimation(
            _infoContainer.ActualHeight, 
            0.0, 
            new Duration(TimeSpan.FromSeconds(0.25)), 
            FillBehavior.Stop);
        // this next line will prevent the popup from being removed from the
        // AdornerLayer until the animation completes
        _collapseAnimation.Completed += (s, ev) => { _infoPopup.Hide(); }; 
        
        _infoPopup.BeginAnimation(HeightProperty, _collapseAnimation);
        _isExpanded = false;
    }
    else
    {
         if (_infoPopup == null)
        {
             _infoPopup = 
                 new PopupAdorner(grid_Container, _infoContainer, new Vector(0, 29));
             _infoContainer.Measure
                 (new Size(Double.PositiveInfinity, Double.PositiveInifity));
             
             _extendAnimation = new DoubleAnimation(
                0.0, 
                _infoContainer.DesiredSize.Height, 
                new Duration(TimeSpan.FromSeconds(0.25)), 
                FillBehavior.Stop);
             
            // this line will cause the extend animation to begin after the container
            // information is loaded
             _infoContainer.Loaded += (s, ev) => 
                { 
                    _infoContainer.BeginAnimation(HeightProperty, _extendAnimation; 
                }
        }
        
        ctl_Expander.RenderTransform = extendArrowTransform;
        ctl_Expander.BeginAnimation(RotateTransform.AngleProperty, _extendArrowAnimation);
        
        _infoPopup.Show();
        _isExpanded = true;
    }
}
此处添加了四个 DoubleAnimation 对象和两个 RotateTransform 对象。这些动画和变换负责改变视觉效果以响应用户。当用户单击展开箭头时,箭头会旋转指向下方,并且 adorner 弹出窗口会向下滑入视图。再次单击时,箭头会旋转回其原始位置,adorner 弹出窗口会向上滑动并退出视图,并且当关闭动画完成时,adorner 会从 AdornerLayer 中删除自身。我没有包含任何检查点击用户的代码,如果在过渡期间单击展开箭头,则会出错。

特别鸣谢

我只是想包括所有帮助我完成此项目的数据源的链接

历史

  • 2016年10月13日 首次发布
  • 2016年10月14日 修复错误;添加示例项目
© . All rights reserved.