在 WPF 中,我们通常用 DropShadow 做阴影效果,但都是做外阴影。内阴影(Inner Shadow)的话其实也不是不可以,就是有些曲折。这篇文章介绍几种做内引用的做法。

文章涉及到以下概念:

UIElement.ClipToBounds 属性 (System.Windows)

UIElement.Clip 属性 (System.Windows)

UIElement.OpacityMask 属性

VisualBrush 类 (System.Windows.Media)

1. ClipToBounds

<Border>
<Border.Clip>
<RectangleGeometry Rect="0,0,100,100" />
</Border.Clip>
<Border.Effect>
<DropShadowEffect BlurRadius="8" ShadowDepth="0" />
</Border.Effect>
<ContentControl HorizontalAlignment="Center"
VerticalAlignment="Center"
Content="Clip " />
</Border>

上面是一个普通的加上 DropShadowEffect 的 Border。要做内部阴影的话就只是将外部阴影裁剪掉,在 Border 上简单地加上 ClipToBounds="True" 就可以实现这个效果:

ClipToBounds 属性用于指示是否剪切此元素的内容(或来自此元素的子元素的内容)使其适合包含元素的大小。

但如果 Border 有圆角(最近微软向圆角势力屈服了,Windows 11 到处都是圆角)的话,那这个方案就有问题了,因为它不能裁剪圆角:

2. Clip

为了可以裁剪圆角内容,还是老老实实用 Clip 来裁剪,不过这就需要自己计算尺寸及圆角半径:

<Border>
<Border.Clip>
<RectangleGeometry RadiusX="8"
RadiusY="8"
Rect="0,0,100,100" />
</Border.Clip>
<Border.Effect>
<DropShadowEffect BlurRadius="8" ShadowDepth="0" />
</Border.Effect>
<ContentControl HorizontalAlignment="Center"
VerticalAlignment="Center"
Content="Clip " />
</Border>

这个方案的坏处很明显,因为要写死尺寸,真的要用这方案的话最好封装一下在 SizeChanged 事件中重新计算裁剪区域。

3. OpacityMask

<Grid Width="100"
Height="100"
Margin="10">
<Rectangle x:Name="Rectangle2"
Fill="White"
RadiusX="8"
RadiusY="8" />
<Border Margin="0">
<Border.Effect>
<DropShadowEffect BlurRadius="8" ShadowDepth="0" />
</Border.Effect>
<ContentControl HorizontalAlignment="Center"
VerticalAlignment="Center"
Content="OpacityMask" />
</Border>
<Grid.OpacityMask>
<VisualBrush Stretch="None" Visual="{Binding ElementName=Rectangle2}" />
</Grid.OpacityMask>
</Grid>

这个方案用另一个元素的 VisualBrush 来做 OpacityMask,胜在够灵活,就是 XAML 要写多一些。

4. 更粗的内阴影

上面这些 Border 都应用了这个样式:

<Style TargetType="Border">
<Setter Property="Width" Value="100" />
<Setter Property="Height" Value="100" />
<Setter Property="Margin" Value="10" />
<Setter Property="BorderBrush" Value="SkyBlue" />
<Setter Property="BorderThickness" Value="1" />
</Style>

理所当然的,它们制造出来的阴影都是以这个 1 像素的边框为基础,如果需要更大更粗的内阴影,可以使用一个负数的 Margin 配合同样粗细的 BorderThickness 实现。以 OpacityMask 的方案为例,用下面的代码可以做个又粗又大的内阴影:

private void Slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
ShadowElement.Margin = new Thickness(-e.NewValue);
ShadowElement.BorderThickness = new Thickness(e.NewValue);
(ShadowElement.Effect as DropShadowEffect).BlurRadius = e.NewValue * 2;
}

5. 源码

https://github.com/DinoChan/wpf_design_and_animation_lab

[WPF] 实现 WPF 的 Inner Shadow的更多相关文章

  1. 【WPF】WPF截屏

    原文:[WPF]WPF截屏 引言 .NET的截图控件在网上流传得不多啊,难得发现一个精品截图控件( 传送门),但是无奈是winform的.后来又找到一个周银辉做的WPF截图(继续传送门),发现截屏是实 ...

  2. 【WPF】wpf用MultiBinding解决Converter需要动态传参的问题,以Button为例

    原文:[WPF]wpf用MultiBinding解决Converter需要动态传参的问题,以Button为例       用Binding并通过Converter转换的时候,可能偶尔会遇到传参的问题, ...

  3. 基于托管的C++来使用WPF - Using WPF with Managed C++

    基于托管的C++来使用WPF - Using WPF with Managed C++ Posted by Zeeshan Amjad This article was originally publ ...

  4. 【WPF】WPF中的List<T>和ObservableCollection<T>

    在WPF中 控件绑定数据源时,数据源建议采用 ObservableCollection<T>集合 ObservableCollection<T> 类:表示一个动态数据集合,在添 ...

  5. 学习WPF——了解WPF中的XAML

    XAML的简单说明 XAML是用于实例化.NET对象的标记语言,主要用于构建WPF的用户界面 XAML中的每一个元素都映射为.NET类的一个实例,例如<Button>映射为WPF的Butt ...

  6. WPF Extended WPF Toolkit

    1.VS 2013 通过NUGet获取Extended WPF Toolkit 我自己的项目已安装 2.在自己页面引用Extended WPF Toolkit xmlns:xctk="htt ...

  7. 【WPF】WPF中调用Winform

    1.添加两个引用:WindowsFormsIntegration.dll(负责整合WPF和Windows).System.Windows.Forms.2.在 XAML文件中添加两个引用(粗体部分): ...

  8. 【转】【WPF】WPF 自定义快捷键命令(Command)

    命令简介 WPF 中的命令是通过实现 ICommand 接口创建的.ICommand 公开两个方法(Execute 及 CanExecute)和一个事件(CanExecuteChanged).Exec ...

  9. 【转】【WPF】WPF样式(Style)—触发器

    样式(Styles)由三部分构成:设置器(Setter).触发器(Triggers).资源(Resources). (1)触发器,让样式的使用更加准确.灵活和高效. (2)触发器(Triggers)主 ...

随机推荐

  1. 如何在 Kubernetes 集群中玩转 Fluid + JuiceFS

    作者简介: 吕冬冬,云知声超算平台架构师, 负责大规模分布式机器学习平台架构设计与功能研发,负责深度学习算法应用的优化与 AI 模型加速.研究领域包括高性能计算.分布式文件存储.分布式缓存等. 朱唯唯 ...

  2. 十行HTML实现增强现实--思途青岛

    你想通过网络实现增强现实吗?现在你只需要 10 行 HTML 代码!真的!让我带你看一看代码,非常简单.我们最近发布了AR.js.你不需要安装任何应用,用你的手机通过网络就能体验到强大的增强现实.但让 ...

  3. js offset系列属性

    offsetParent:返回该元素有定位的父级,如果父级都没有定位则返回body offsetTop:返回元素相对父级(带有定位的父级)上方的偏移 offsetLeft:返回元素相对父级(带有定位的 ...

  4. 【AGC052A】

    题目 \(有T组询问\) \(每次给一个n\) \(3个01串\),\(这三个串每个都有n个0\),\(n个1\),\(求一个2*n + 1的字符串\),\(使他成为S1+S1,S2+S2,S3+S3 ...

  5. Codeforces 1063F - String Journey(后缀数组+线段树+dp)

    Codeforces 题面传送门 & 洛谷题面传送门 神仙题,做了我整整 2.5h,写篇题解纪念下逝去的中午 后排膜拜 1 年前就独立切掉此题的 ymx,我在 2021 年的第 5270 个小 ...

  6. Codeforces 690A2 - Collective Mindsets (medium)

    Codeforces 题面传送门 & 洛谷题面传送门 一道脑筋急转弯的结论题. 首先我们考虑对于某个特定的金币数 \(m\),有哪些 \(n\) 满足条件.考虑最 naive 的情况,\(m= ...

  7. 查看nginx(Web网页服务器)状态是否正常

    Linux每个应用运行都会产生一个进程,那么我们就可以通过查看Nginx进程是否存在来判断它是否启动. 1.有时想知道nigix是否在正常运行,需要用linux命令查看nginx运行情况.执行命令:p ...

  8. 【翻译】.NET 6 中的 dotnet monitor

    原文:Announcing dotnet monitor in .NET 6 我们在 2020 年 6 月首次推出了dotnet monitor 作为实验工具,并在去年(2020年)努力将其转变为生产 ...

  9. hadoop运行jar包报错

    执行命令:[root@hadoop102 mapreduce]# hadoop jar mapreduce2_maven.jar Filter 错误信息:Exception in thread &qu ...

  10. Shell $()、${}、$[]、$(())

    目录 Shell中的 $().${}.$[].$(()) $().${} 替换 ${} 变量内容的替换.删除.取代 数组 $[].$(()) 运算符 Shell中的 $().${}.$[].$(()) ...