在WPF的早期,控件效果丰富,颜色渐变,其中Bitmap-EFFECTS尤为突出。然而,随着时间的推移,这些效果逐渐被标记为技术上的过时,并被更通用的Effect类所取代。尽管如此,在某些情况下,仍然可能会需要这样的特性。
由于Bevel-Effect既方便又简单,无论是在设计上还是在性能上,决定构建一个简单且易于实现的Bevel效果。在实现过程中,采用了以下WPF中级技术:
由于Bevel-Effect简单、清晰的特性,并且它除了简单的装饰特性之外还具有一定的设计价值,所以时不时地会使用它。这也是选择这种效果最简单的视觉形式的原因。
在本文中,省略了所有与主题不直接相关的代码,因为相信它会掩盖想要表达的核心思想和简单性。
当开始寻找实现这种效果的最佳方案时,目标是找到一个既简单又优雅、健壮且通用的实现方式。最终,找到了一个满足目标的解决方案,那就是这里展示的方案。
在基础层面上,它是一个“协同”的行为和装饰器的混合体:
也考虑过基于XAML的装饰器是使用装饰器的首选方式(与基于代码的替代方案相比)。
以下是实现方式:
《Button》
《i:Interaction.Behaviors》
《local:BevelBehavior BevelThickness="30" /》
《/i:Interaction.Behaviors》
《/Button》
行为的工作方式如下:
一旦“待Beveled/Adorned”的元素被加载,它就有一个“Adorner-Layer”。然后,BevelBehavior实例化一个BevEffAdor装饰器,并用来自XAML*的数据填充其属性。
BevelBehavior充当效果用户(在XAML中使用Bevel相关术语)和处理不同“实现相关”属性集的装饰器之间的中介:
行为中的属性:
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填充的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覆盖中完成的(作为Geometry-Path字符串):
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);
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();
};