【Win10】【Win2D】实现控件阴影效果
学过 WPF 的都知道,在 WPF 中,为控件添加一个阴影效果是相当容易的。
<Border Width="100"
Height="100"
Background="Red">
<Border.Effect>
<DropShadowEffect />
</Border.Effect>
</Border>
那么这样就会显示一个 100 宽、100 高,背景红色,带有阴影的矩形了。如下图所示。
但是,在 WinRT 中,基于 Metro 教义和性能考虑,巨硬扼杀了阴影。但是,需求多多少少还是会有的,以致于部分开发者不得不用渐变来实现蹩脚的“阴影”效果,而且仔细看上去会发现很假,连 duang 一下的特效都没,一眼看上去这阴影效果就是假的。
那么,真正的阴影效果真的没法实现了吗?以前是。但是现在,我们有了 Win2D,什么增强光照啊、高斯模糊啊,都不是问题。阴影当然也是。
先来看看怎么绘制一个阴影先吧。
前台 XAML:
<Page x:Class="App92.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:win2d="using:Microsoft.Graphics.Canvas.UI.Xaml"
Unloaded="Page_Unloaded">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<win2d:CanvasControl x:Name="canvas"
Width="300"
Height="300"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Draw="canvas_Draw" />
</Grid>
</Page>
后台代码:
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Effects;
using Microsoft.Graphics.Canvas.UI.Xaml;
using Windows.Foundation;
using Windows.UI;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls; namespace App92
{
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
} private void canvas_Draw(CanvasControl sender, CanvasDrawEventArgs args)
{
CanvasCommandList cl = new CanvasCommandList(sender);
using (CanvasDrawingSession clds = cl.CreateDrawingSession())
{
clds.FillRectangle(new Rect(, , , ), Colors.White);
} ShadowEffect effect = new ShadowEffect()
{
Source = cl
};
args.DrawingSession.DrawImage(effect);
} private void Page_Unloaded(object sender, RoutedEventArgs e)
{
if (this.canvas != null)
{
this.canvas.RemoveFromVisualTree();
this.canvas = null;
}
}
}
}
Page_Unloaded 里面是释放 Win2D 使用的资源。这点在我上次翻译的《【Win2D】【译】Win2D 快速入门》里面有说过。
Draw 方法的代码则类似于《快速入门》里面对图片施加高斯模糊。
编译并运行后你应该会看见这样的效果:
一坨黑乎乎的东西,而且是毛边的。
在上面的代码中,关键就是
ShadowEffect effect = new ShadowEffect()
{
Source = cl
};
这一句声明了一个阴影效果,并且源是上面那个命令列表,也就是表明对哪个对象施加阴影效果。在上面那个命令列表中绘制了一个在距离 canvas 左上角横坐标 100、纵坐标 100,宽高 100 的矩形。
需要注意的是,尽管我们绘制的矩形是白色的,但是阴影效果是不关心的(详细点说是不关心 RGB,A 通道还是有影响的),而且 ShadowEffect 有自己的颜色属性。
在理清了如何编写代码显示阴影之后,我们再来探究下如何实现控件阴影。
原理很简单,无非就是在控件 z 轴下面显示阴影。
于是乎我们新建一个模板控件,我就叫它 Shadow,并写出以下代码。
cs 代码:
using Microsoft.Graphics.Canvas.UI.Xaml;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Markup; namespace App92
{
[ContentProperty(Name = nameof(Content))]
public class Shadow : Control
{
public static readonly DependencyProperty ContentProperty = DependencyProperty.Register(nameof(Content), typeof(FrameworkElement), typeof(Shadow), new PropertyMetadata(null)); private CanvasControl _canvas; public Shadow()
{
this.DefaultStyleKey = typeof(Shadow);
this.Unloaded += this.OnUnloaded;
} public FrameworkElement Content
{
get
{
return (FrameworkElement)this.GetValue(ContentProperty);
}
set
{
this.SetValue(ContentProperty, value);
}
} protected override void OnApplyTemplate()
{
base.OnApplyTemplate(); this._canvas = (CanvasControl)this.GetTemplateChild("PART_Canvas");
this._canvas.Draw += this.Canvas_Draw;
} private void Canvas_Draw(CanvasControl sender, CanvasDrawEventArgs args)
{
// TODO。
} private void OnUnloaded(object sender, RoutedEventArgs e)
{
if (this._canvas != null)
{
this._canvas.RemoveFromVisualTree();
this._canvas = null;
}
}
}
}
Generic.xaml 代码:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:win2d="using:Microsoft.Graphics.Canvas.UI.Xaml"
xmlns:local="using:App92">
<Style TargetType="local:Shadow">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:Shadow">
<Grid>
<win2d:CanvasControl x:Name="PART_Canvas" />
<ContentControl Content="{TemplateBinding Content}" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
这里我不选择继承自 ContentControl 是因为 ContentControl 的 Content 属性是 object,而后文中我们需要使用到 FrameworkElement。
接下来,开始考虑编写 Draw 代码。
第一个问题,ShadowEffect 的 Source 是哪里来的?对于大部分控件,就是一个矩形,但是,部分如 Border 之类的控件,可能是圆角的(因为有 CornerRadius 属性)。那么该如何得到一个控件的形状呢?这里我们使用 RenderTargetBitmap 这个类,它能够捕获一个在可视树上的控件的外观。对于控件透明的部分,RenderTargetBitmap 就是透明的。那么 RenderTargetBitmap 得到的就相当于控件的形状。但是,RenderTargetBitmap 是异步的,因此我们要将该部分写在其它方法当中。因为 Draw 方法是不能够编写异步代码的。
第二个问题,应该何时重绘阴影?也就是应该何时重新调用 RenderTargetBitmap?这个问题很容易解决,我们使用 FrameworkElement 的 LayoutUpdated 事件好了。所以我们上面的 Content 的属性需要为 FrameworkElement。
第三个问题,从上面 Generic.xaml 来看,CanvasControl 是跟 ContentControl 一样大小的,假设我们的 Content 刚好占满了 ContentControl,那么在下面的 CanvasControl 岂不是无法显示?!也就是说,这时候我们的阴影是完全没办法显示的。所以,就必须要确保 CanvasControl 必须永远大于 ContentControl,以确保有足够的空间显示阴影。使用 ScaleTransform 可以,但是效果不是十分好。要注意一点,ShadowEffect 是会发散的!也就是说,经过 ShadowEffect 处理过的输出是会比输入要大,所以我们并不需要进行缩放,增大容纳空间即可。对 CanvasControl 使用一个负数的 Margin 是一个相对较好的解决方案。至于负多少,我个人认为 10 个像素就足够了,毕竟 ShadowEffect 的发散有限。
另外为了满足实际需要,我们仿照下 WPF 的 DropShadowEffect 类,添加阴影颜色、阴影方向、阴影距离这些属性。修改 cs 代码如下:
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Effects;
using Microsoft.Graphics.Canvas.UI.Xaml;
using System;
using System.Numerics;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.ApplicationModel;
using Windows.Foundation;
using Windows.Graphics.DirectX;
using Windows.Graphics.Display;
using Windows.UI;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Markup;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Imaging; namespace App92
{
[ContentProperty(Name = nameof(Content))]
public class Shadow : Control
{
public static readonly DependencyProperty ColorProperty = DependencyProperty.Register(nameof(Color), typeof(Color), typeof(Shadow), new PropertyMetadata(Colors.Black)); public static readonly DependencyProperty ContentProperty = DependencyProperty.Register(nameof(Content), typeof(FrameworkElement), typeof(Shadow), new PropertyMetadata(null, ContentChanged)); public static readonly DependencyProperty DepthProperty = DependencyProperty.Register(nameof(Depth), typeof(double), typeof(Shadow), new PropertyMetadata(2.0d, DepthChanged)); public static readonly DependencyProperty DirectionProperty = DependencyProperty.Register(nameof(Direction), typeof(double), typeof(Shadow), new PropertyMetadata(270.0d)); private CanvasControl _canvas; private int _pixelHeight; private byte[] _pixels; private int _pixelWidth; public Shadow()
{
this.DefaultStyleKey = typeof(Shadow);
this.Unloaded += this.OnUnloaded;
} public Color Color
{
get
{
return (Color)this.GetValue(ColorProperty);
}
set
{
this.SetValue(ColorProperty, value);
}
} public FrameworkElement Content
{
get
{
return (FrameworkElement)this.GetValue(ContentProperty);
}
set
{
this.SetValue(ContentProperty, value);
}
} public double Depth
{
get
{
return (double)this.GetValue(DepthProperty);
}
set
{
this.SetValue(DepthProperty, value);
}
} public double Direction
{
get
{
return (double)this.GetValue(DirectionProperty);
}
set
{
this.SetValue(DirectionProperty, value);
}
} protected override void OnApplyTemplate()
{
base.OnApplyTemplate(); this._canvas = (CanvasControl)this.GetTemplateChild("PART_Canvas");
this._canvas.Draw += this.Canvas_Draw;
this.ExpendCanvas();
} private static void ContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Shadow obj = (Shadow)d; FrameworkElement oldValue = (FrameworkElement)e.OldValue;
if (oldValue != null)
{
oldValue.LayoutUpdated -= obj.Content_LayoutUpdated;
} FrameworkElement newValue = (FrameworkElement)e.NewValue;
if (newValue != null)
{
newValue.LayoutUpdated += obj.Content_LayoutUpdated;
}
} private static void DepthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Shadow obj = (Shadow)d;
obj.ExpendCanvas();
} private void Canvas_Draw(CanvasControl sender, CanvasDrawEventArgs args)
{
if (this.Content == null || this._pixels == null || this._pixelWidth <= || this._pixelHeight <= )
{
// 不满足绘制条件,清除 Canvas。
args.DrawingSession.Clear(sender.ClearColor);
}
else
{
// 计算内容控件相对于 Canvas 的位置。
GeneralTransform transform = this.Content.TransformToVisual(sender);
Vector2 location = transform.TransformPoint(new Point()).ToVector2(); using (CanvasCommandList cl = new CanvasCommandList(sender))
{
using (CanvasDrawingSession clds = cl.CreateDrawingSession())
{
using (CanvasBitmap bitmap = CanvasBitmap.CreateFromBytes(sender, this._pixels, this._pixelWidth, this._pixelHeight, DirectXPixelFormat.B8G8R8A8UIntNormalized, DisplayInformation.GetForCurrentView().LogicalDpi))
{
// 在 Canvas 对应的位置中绘制内容控件的外观。
clds.DrawImage(bitmap, location);
}
} float translateX = (float)(Math.Cos(Math.PI / 180.0d * this.Direction) * this.Depth);
float translateY = - (float)(Math.Sin(Math.PI / 180.0d * this.Direction) * this.Depth); Transform2DEffect finalEffect = new Transform2DEffect()
{
Source = new ShadowEffect()
{
Source = cl,
BlurAmount = ,// 阴影模糊参数,越大越发散,感觉 2 足够了。
ShadowColor = this.GetShadowColor()
},
TransformMatrix = Matrix3x2.CreateTranslation(translateX, translateY)
}; args.DrawingSession.DrawImage(finalEffect);
}
}
} private async void Content_LayoutUpdated(object sender, object e)
{
if (DesignMode.DesignModeEnabled || this.Visibility == Visibility.Collapsed || this.Content.Visibility == Visibility.Collapsed)
{
// DesignMode 不能调用 RenderAsync 方法。
// 控件自身隐藏或者内容隐藏时也不能调用 RenderAsync 方法。
this._pixels = null;
this._pixelWidth = ;
this._pixelHeight = ;
}
else
{
RenderTargetBitmap bitmap = new RenderTargetBitmap();
await bitmap.RenderAsync(this.Content); int pixelWidth = bitmap.PixelWidth;
int pixelHeight = bitmap.PixelHeight;
if (bitmap.PixelWidth > && bitmap.PixelHeight > )
{
this._pixels = (await bitmap.GetPixelsAsync()).ToArray();
this._pixelWidth = pixelWidth;
this._pixelHeight = pixelHeight;
}
else
{
// 内容宽或高为 0 时不能调用 GetPixelAsync 方法。
this._pixels = null;
this._pixelWidth = pixelWidth;
this._pixelHeight = pixelHeight;
}
} if (this._canvas != null)
{
// 请求重绘。
this._canvas.Invalidate();
}
} private void ExpendCanvas()
{
if (this._canvas != null)
{
// 扩展 Canvas 以确保阴影能够显示。
this._canvas.Margin = new Thickness( - (this.Depth + ));
}
} private Color GetShadowColor()
{
if (this.Content.Visibility == Visibility.Collapsed)
{
return Colors.Transparent;
}
// 阴影透明度应该受内容的 Opacity 属性影响。
double alphaProportion = Math.Max(, Math.Min(, this.Content.Opacity));
return Color.FromArgb((byte)(Color.A * alphaProportion), Color.R, Color.G, Color.B);
} private void OnUnloaded(object sender, RoutedEventArgs e)
{
if (this._canvas != null)
{
this._canvas.RemoveFromVisualTree();
this._canvas = null;
}
}
}
}
然后在页面上测试下吧。
<Page x:Class="App92.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App92">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<local:Shadow HorizontalAlignment="Center"
VerticalAlignment="Center">
<Border Background="Red"
Width="100"
Height="100"></Border>
</local:Shadow>
</Grid>
</Page>
运行效果:
用在 Image 上也是不错的说:
不过用在默认的 Button 上就比较难看了,因为 Button 本身默认的 Background 就是半透明的,然后背后一团黑乎乎的阴影。。。所以还是比较建议这个效果用在那些非透明的控件上。
最后放上项目源代码:http://files.cnblogs.com/files/h82258652/ControlShadow.zip
Win2D 是个好东西,如果你觉得有些效果难以实现的话,可以尝试一下 Win2D 的说。
【Win10】【Win2D】实现控件阴影效果的更多相关文章
- 【Win10】SplitView控件
SplitView是Win10中的新控件. 用于呈现两部分视图. 一个视图是主要内容,另一个视图是用于导航.(也就是通常说的汉堡菜单.) 主要结构: <SplitView> <Spl ...
- silverlight控件阴影效果示例
<ScrollViewer MaxHeight="400" VerticalScrollBarVisibility="Auto" HorizontalSc ...
- win10 uwp 拖动控件
我们会使用控件拖动,可以让我们做出好看的动画,那么我们如何移动控件,我将会告诉大家多个方法.其中第一个是最差的,最后的才是我希望大神你去用. Margin 移动 我们可以使用Margin移动,但这是w ...
- 【Win10】实现控件倒影效果
先引入个小广告: 最近买了台小米盒子折腾下,发觉 UI 还是挺漂亮的,特别是主页那个倒影效果. (图随便找的,就是上面图片底部的那个倒影效果.) 好了,广告结束,回归正题,这个倒影效果我个人觉得是挺不 ...
- 【WIN10】基本控件
先發個下載地址: http://yunpan.cn/cHuCqYzvsWFAL 访问密码 3470 說明一下.這個示例只是最簡單的演示,並不能提供太大的實用價值. 後面會介紹 Bing & ...
- win10 uwp InkCanvas控件数据绑定
本文主要说如何绑定InkCanvas,让笔画变化的时候我们可以知道. 我们本来的InkCanvas没有提供笔画绑定,所以我们自己写 using Windows.UI.Input.Inking; usi ...
- 模仿win10样式,基于jquery的时间控件
工作需要,写了一个基于jquery的时间控件,仿win10系统时间控件格式. 目前基本功能都有了,但时间格式只实现少数,但由于结构设计已经充分优化,填充起来非常容易. 这个控件相对网上其他的时间控件, ...
- Win2D 官方文章系列翻译 - 调整控件分辨率
本文为个人博客备份文章,原文地址: http://validvoid.net/win2d-choosing-control-resolution/ 本文旨在讲解如何配置 Win2D XAML 控件使用 ...
- WPF常用控件应用demo
WPF常用控件应用demo 一.Demo 1.Demo截图如下: 2.demo实现过程 总体布局:因放大缩小窗体,控件很根据空间是否足够改变布局,故用WrapPanel布局. <ScrollVi ...
随机推荐
- Skyline6.5系列覆盖三维地理信息产业上下游
SkylineGlobe将于近日推出6.5 系列产品.该系列产品提供从产业链上游影像处理.中游二三维展示分析.下游具体业务应用等覆盖整个三维空间地理信息产业链的一体化.一站式产品与服务. Skylin ...
- 一个批量移除BOM头的bash脚本
有时候我们的文件可能不需要BOM头,例如:我们公司的SVN服务器提供的代码都UTF8编码保存(不能有BOM头)否则代码提交不上去. 文件很多的时候就需要批量操作. 脚本使用方法:remove-bom. ...
- LCLFramework框架之开发约束
Entity编写 1:所有的实体类都必须继承DomainEntity 2:所有的表都必须有 ID 3:所有表的关系字段必须是ID [Serializable] public class User: D ...
- 利用 a 标签自动解析 url
很多时候,我们有从 url 中提取域名,查询关键字,变量参数值等的需求,然而我们可以让浏览器方便地帮助我们完成这一任务而不用写正则去抓取.方法就是先创建一个 a 标签然后将需要解析的 url 赋值给 ...
- DB2中OLAP函数使用示例
下面的需求是将不仅获取查询的结果集,还要将结果集的SIZE返回.结果集的SIZE是无法通过CURSOR获取的. 通常聚合函数在二种情况下,使用默认分组即没有分组.可以直接使用,比如 selec ...
- ExtJs 可查询的下拉框
最近项目中有个需求,就是有四个模块需要加载一个主表的内容,比如说这个表叫项目表(比如项目表里有两个字段一个是项目ID--projCd,还有一个是项目名称--projNm).主表的内容的要放在一个下拉框 ...
- 【android原生态RPG游戏框架源码】
转载请注明原创地址:http://www.cnblogs.com/zisou/p/android-RPG.html 这份源码是在今年6月份写的,当时公司有一个技术部们的学习讨论的讲座,然后我自己就写了 ...
- 基情四射的两个css样式
自定义blog样式时,代码段的line-height继承样式post的line-height,间隔太大了,决定再减小点,css都玩了几年了,感觉中这是很容易的事情.然后,就悲剧了好久,原先自定义样式表 ...
- LintCode-- Remove Linked List Elements
Remove all elements from a linked list of integers that have valueval. 样例 Given 1->2->3->3- ...
- POJ 2853 Sequence Sum Possibilities
Sequence Sum Possibilities Time Limit: 1000MS Memory Limit: 65536K Total Submissions: 5537 Accep ...