简单的基于 Adorner 的斜角效果
在 WPF 中应用斜角效果的简单优雅的方法
* 备注 -
示例代码使用 VS2015、C#6、NET 4.5.2 编写
引言
在 WPF 的早期,控件华丽,颜色渐变,那时有 Bitmap-EFFECTS!!! 现在它们几乎不被使用,而且大多是有充分理由的。它们被标记为技术上已过时,并被更通用的 Effect
类所取代。但是,有时人们可能会遇到对这种功能的需求。
由于斜面效果非常方便,并且在设计和性能方面都很简单,因此我决定构建一个在其内部工作原理及其(“外部”)实现中都很简单的斜面效果。
背景
经过(不算)几次尝试和改进,我想出了一个让我满意的解决方案。
它涉及使用以下中级 WPF 技术
- 行为
- 装饰器
- 依赖属性
如果您不熟悉任何这些,我建议在继续之前快速了解一下它们。
此外,正如我之前提到的,我时不时地(大约每隔几年一次)继续使用斜面效果,因为它具有简单而干净的“性质”,并且除了简单的装饰性功能外,它还具有一些额外的设计价值。 这也是我选择这种效果的最简单视觉形式的原因。
与我的大多数文章一样,我省略了附加示例中任何“非直接相关”的代码,因为我认为它掩盖了想法本身和我所追求的简单性。
Using the Code
当我开始试验这种效果的解决方案时,我正在寻找在简单性、优雅性、健壮性和多功能性方面最佳的实现。 最终满足我目标的解决方案就是这里介绍的解决方案。
它的基础是行为和装饰器的“协同”混合
1. 为什么选择装饰器? 从其本质上讲,它是一种覆盖在视觉元素顶部的视觉属性,目的是增强该元素的初始特征。
这正是我想要在现有视觉元素上实现斜面效果时所寻找的; 将其“视觉外观”增强为 3D 且干净的表示形式。
2. 为什么选择行为? 经过多次实验,事实证明,使用行为可以产生将装饰器“注入”到 XAML 中的最优雅的方式*
*我还认为基于 XAML 的装饰器是使用装饰器的首选方式(与基于代码的替代方案相比)
这就是它的实现方式
<Button> ... <i:Interaction.Behaviors> <local:BevelBehavior BevelThickness="30"/> </i:Interaction.Behaviors>
这是 Behavior
的工作方式
一旦“要进行斜面/装饰”的元素加载完毕,它就具有一个 - “装饰器层”。 然后,BevelBehavior
实例化一个 BevEffAdor
装饰器,并使用来自 XAML* 的数据填充其属性
BevelBehavior
充当效果用户(在使用斜面相关术语的 XAML 中)和 Adorner 本身之间的中介,Adorner 本身处理不同的“与实现相关的”属性集
行为中的属性
public double BevelThickness { get; set; } = 30.0;
public Brush Lighted { get; set; } = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#30000000"));
public Brush Shadowed { get; set; } = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#60000000"));
public Brush Darkened { get; set; } = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#90000000"));
public Brush FaceShadowedTransp { get; set; } = new SolidColorBrush((Color)ColorConverter.ConvertFromString("#50000000"));
*BevelBehavior
中的属性是 DependencyProperties
,可以提示在未来/进一步的开发中对已更改的属性值做出反应的选项。
由 BevelBehavior
填充的 BevEffAdor
中的属性
bevador.NotIsPressedN = Lighted.Clone();
bevador.NotIsPressedW = Shadowed.Clone();
bevador.NotIsPressedSE = Darkened.Clone();
bevador.IsPressedNW = Darkened.Clone();
bevador.IsPressedNW.Opacity = 0;
bevador.IsPressedE = Shadowed.Clone();
bevador.IsPressedE.Opacity = 0;
bevador.IsPressedS = Lighted.Clone();
bevador.IsPressedS.Opacity = 0;
bevador.IsPressedFace = FaceShadowedTransp.Clone();
bevador.IsPressedFace.Opacity = 0;
正如我所说,我尝试了很多不同的方法,以在技术上和视觉上(它的外观)正确解决这个问题。 我无法详细说明为什么排除其他方法中的每一种,但我最终选择了“OnRender-override, Adorner-Painting”方法。
其中,大小相关变量的计算是在“MeasureOverride
”中进行的
var pNWi = $"{BevelThickness},{BevelThickness}";
var pNEo = $"{AdornedElement.ActualWidth},0";
var pNEi = $"{AdornedElement.ActualWidth - BevelThickness},{BevelThickness}";
var pSEo = $"{AdornedElement.ActualWidth},{AdornedElement.ActualHeight}";
var pSEi = $"{AdornedElement.ActualWidth - BevelThickness},{AdornedElement.ActualHeight - BevelThickness}";
var pSWo = $"0,{AdornedElement.ActualHeight}";
var pSWi = $"{BevelThickness},{AdornedElement.ActualHeight - BevelThickness}";
geoInnerRect = Geometry.Parse($"M {pNWi} {pNEi} {pSEi} {pSWi}");
geoN = Geometry.Parse($"M {pNWo} {pNWi} {pNEi} {pNEo}");
geoSE = Geometry.Parse($"M {pNEo} {pNEi} {pSEi} {pSWi} {pSWo} {pSEo}");
geoW = Geometry.Parse($"M {pNWo} {pNWi} {pSWi} {pSWo}");
geoNW = Geometry.Parse($"M {pNWo} {pNEo} {pNEi} {pNWi} {pSWi} {pSWo}");
geoS = Geometry.Parse($"M {pSWo} {pSWi} {pSEi} {pSEo}");
geoE = Geometry.Parse($"M {pNEo} {pNEi} {pSEi} {pSEo}");
实际绘制是在 OnRender
重写中进行的(作为几何路径字符串)
drawingContext.DrawGeometry(NotIsPressedN, null, geoN);
drawingContext.DrawGeometry(NotIsPressedSE, null, geoSE);
drawingContext.DrawGeometry(NotIsPressedW, null, geoW);
drawingContext.DrawGeometry(IsPressedNW, null, geoNW);
drawingContext.DrawGeometry(IsPressedS, null, geoS);
drawingContext.DrawGeometry(IsPressedE, null, geoE);
drawingContext.DrawGeometry(IsPressedFace, null, geoInnerRect);
装饰器本身标记为 HitTestVisible=False
,因此它不会“干扰”装饰元素的“正常”行为。 装饰器注册到 MouseLeftButtonDown/Up
事件,因此它会通过动画过渡以不同的方式绘制自身(突出/沉浸)。
var durAnim = new Duration(TimeSpan.FromSeconds(0.2));
AdornedElement.PreviewMouseLeftButtonDown += (s, e) =>
{
var daHide = new DoubleAnimation(0, durAnim);
NotIsPressedN.BeginAnimation(Brush.OpacityProperty, daHide);
NotIsPressedW.BeginAnimation(Brush.OpacityProperty, daHide);
NotIsPressedSE.BeginAnimation(Brush.OpacityProperty, daHide);
var daShow = new DoubleAnimation(1, durAnim);
IsPressedNW.BeginAnimation(Brush.OpacityProperty, daShow);
IsPressedE.BeginAnimation(Brush.OpacityProperty, daShow);
IsPressedS.BeginAnimation(Brush.OpacityProperty, daShow);
IsPressedFace.BeginAnimation(Brush.OpacityProperty, daShow);
InvalidateVisual();
};
AdornedElement.PreviewMouseLeftButtonUp += (s, e) =>
{
var daShow = new DoubleAnimation(1, durAnim);
NotIsPressedN.BeginAnimation(Brush.OpacityProperty, daShow);
NotIsPressedW.BeginAnimation(Brush.OpacityProperty, daShow);
NotIsPressedSE.BeginAnimation(Brush.OpacityProperty, daShow);
var daHide = new DoubleAnimation(0, durAnim);
IsPressedNW.BeginAnimation(Brush.OpacityProperty, daHide);
IsPressedE.BeginAnimation(Brush.OpacityProperty, daHide);
IsPressedS.BeginAnimation(Brush.OpacityProperty, daHide);
IsPressedFace.BeginAnimation(Brush.OpacityProperty, daHide);
InvalidateVisual();
};
关注点
使用 Behavior 作为装饰器的中介/注入器的技术可能在其他效果中很有用。