C# WPF 动画装饰器弹出控件
一个类似弹出窗口的控件,
引言
应用程序中的可视空间非常宝贵。即使在拥有惊人分辨率的 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
中删除自身。我没有包含任何检查点击用户的代码,如果在过渡期间单击展开箭头,则会出错。特别鸣谢
我只是想包括所有帮助我完成此项目的数据源的链接
- https://codeproject.org.cn/Articles/54472/Defining-WPF-Adorners-in-XAML
- https://codeproject.org.cn/Articles/17696/Spelling-Suggestions-in-a-WPF-TextBox
- https://social.msdn.microsoft.com/Forums/vstudio/en-US/81eca7d5-88d7-477a-8cdb-cfb9e8b75379/how-to-add-controls-to-adorner?forum=wpf
- http://stackoverflow.com/questions/9998691/wpf-adorner-with-controls-inside
- http://stackoverflow.com/questions/8576594/drawingcontext-adorner-possible-to-draw-stackpanel
历史
- 2016年10月13日 首次发布
- 2016年10月14日 修复错误;添加示例项目