MyDropShadow.vb 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. Public Class MyDropShadow
  2. Inherits Decorator
  3. ''' <summary>
  4. ''' 阴影颜色。
  5. ''' </summary>
  6. Public Property Color As Color
  7. Get
  8. Return GetValue(ColorProperty)
  9. End Get
  10. Set(value As Color)
  11. SetValue(ColorProperty, value)
  12. End Set
  13. End Property
  14. Public Shared ReadOnly ColorProperty As DependencyProperty = DependencyProperty.Register("Color", GetType(Color), GetType(MyDropShadow),
  15. New FrameworkPropertyMetadata(Color.FromArgb(&H71, &H0, &H0, &H0), FrameworkPropertyMetadataOptions.AffectsRender, New PropertyChangedCallback(AddressOf ClearBrushes)))
  16. ''' <summary>
  17. ''' 阴影模糊半径。
  18. ''' </summary>
  19. Public Property ShadowRadius As Double
  20. Get
  21. Return GetValue(ShadowRadiusProperty)
  22. End Get
  23. Set(value As Double)
  24. SetValue(ShadowRadiusProperty, value)
  25. End Set
  26. End Property
  27. Public Shared ReadOnly ShadowRadiusProperty As DependencyProperty = DependencyProperty.Register("ShadowRadius", GetType(Double), GetType(MyDropShadow),
  28. New FrameworkPropertyMetadata(CDbl(5), FrameworkPropertyMetadataOptions.AffectsRender, New PropertyChangedCallback(AddressOf ClearBrushes)))
  29. ''' <summary>
  30. ''' 圆角大小。
  31. ''' </summary>
  32. Public Property CornerRadius As CornerRadius
  33. Get
  34. Return GetValue(CornerRadiusProperty)
  35. End Get
  36. Set(value As CornerRadius)
  37. SetValue(CornerRadiusProperty, value)
  38. End Set
  39. End Property
  40. Private Shared Function IsCornerRadiusValid(value As Object) As Boolean
  41. Dim cr As CornerRadius = value
  42. Return Not (cr.TopLeft < 0.0 OrElse cr.TopRight < 0.0 OrElse cr.BottomLeft < 0.0 OrElse cr.BottomRight < 0.0 OrElse Double.IsNaN(cr.TopLeft) OrElse Double.IsNaN(cr.TopRight) OrElse Double.IsNaN(cr.BottomLeft) OrElse Double.IsNaN(cr.BottomRight) OrElse Double.IsInfinity(cr.TopLeft) OrElse Double.IsInfinity(cr.TopRight) OrElse Double.IsInfinity(cr.BottomLeft) OrElse Double.IsInfinity(cr.BottomRight))
  43. End Function
  44. Public Shared ReadOnly CornerRadiusProperty As DependencyProperty = DependencyProperty.Register("CornerRadius", GetType(CornerRadius), GetType(MyDropShadow),
  45. New FrameworkPropertyMetadata(New CornerRadius(), FrameworkPropertyMetadataOptions.AffectsRender, New PropertyChangedCallback(AddressOf ClearBrushes)), New ValidateValueCallback(AddressOf IsCornerRadiusValid))
  46. ' =======================================
  47. ' 渲染
  48. ' =======================================
  49. Protected Overrides Sub OnRender(drawingContext As DrawingContext)
  50. Dim cornerRadius As CornerRadius = Me.CornerRadius
  51. Dim shadowBounds As New Rect(0, 0, RenderSize.Width, RenderSize.Height)
  52. Dim color As Color = Me.Color
  53. If shadowBounds.Width > 0 AndAlso shadowBounds.Height > 0 AndAlso color.A > 0 Then
  54. Dim centerWidth As Double = shadowBounds.Right - shadowBounds.Left - 2 * ShadowRadius
  55. Dim centerHeight As Double = shadowBounds.Bottom - shadowBounds.Top - 2 * ShadowRadius
  56. Dim maxRadius As Double = Math.Min(centerWidth * 0.5, centerHeight * 0.5)
  57. cornerRadius.TopLeft = Math.Min(cornerRadius.TopLeft, maxRadius)
  58. cornerRadius.TopRight = Math.Min(cornerRadius.TopRight, maxRadius)
  59. cornerRadius.BottomLeft = Math.Min(cornerRadius.BottomLeft, maxRadius)
  60. cornerRadius.BottomRight = Math.Min(cornerRadius.BottomRight, maxRadius)
  61. Dim brushes As Brush() = GetBrushes(color, cornerRadius)
  62. Dim centerTop As Double = shadowBounds.Top + ShadowRadius
  63. Dim centerLeft As Double = shadowBounds.Left + ShadowRadius
  64. Dim centerRight As Double = shadowBounds.Right - ShadowRadius
  65. Dim centerBottom As Double = shadowBounds.Bottom - ShadowRadius
  66. Dim guidelineSetX As Double() = New Double() {centerLeft, centerLeft + cornerRadius.TopLeft, centerRight - cornerRadius.TopRight, centerLeft + cornerRadius.BottomLeft, centerRight - cornerRadius.BottomRight, centerRight}
  67. Dim guidelineSetY As Double() = New Double() {centerTop, centerTop + cornerRadius.TopLeft, centerTop + cornerRadius.TopRight, centerBottom - cornerRadius.BottomLeft, centerBottom - cornerRadius.BottomRight, centerBottom}
  68. drawingContext.PushGuidelineSet(New GuidelineSet(guidelineSetX, guidelineSetY))
  69. cornerRadius.TopLeft += ShadowRadius
  70. cornerRadius.TopRight += ShadowRadius
  71. cornerRadius.BottomLeft += ShadowRadius
  72. cornerRadius.BottomRight += ShadowRadius
  73. Dim topLeft As New Rect(shadowBounds.Left, shadowBounds.Top, cornerRadius.TopLeft, cornerRadius.TopLeft)
  74. drawingContext.DrawRectangle(brushes(Placement.TopLeft), Nothing, topLeft)
  75. Dim topWidth As Double = guidelineSetX(2) - guidelineSetX(1)
  76. If topWidth > 0 Then
  77. Dim top As New Rect(guidelineSetX(1), shadowBounds.Top, topWidth, ShadowRadius)
  78. drawingContext.DrawRectangle(brushes(Placement.Top), Nothing, top)
  79. End If
  80. Dim topRight As New Rect(guidelineSetX(2), shadowBounds.Top, cornerRadius.TopRight, cornerRadius.TopRight)
  81. drawingContext.DrawRectangle(brushes(Placement.TopRight), Nothing, topRight)
  82. Dim leftHeight As Double = guidelineSetY(3) - guidelineSetY(1)
  83. If leftHeight > 0 Then
  84. Dim left As New Rect(shadowBounds.Left, guidelineSetY(1), ShadowRadius, leftHeight)
  85. drawingContext.DrawRectangle(brushes(Placement.Left), Nothing, left)
  86. End If
  87. Dim rightHeight As Double = guidelineSetY(4) - guidelineSetY(2)
  88. If rightHeight > 0 Then
  89. Dim right As New Rect(guidelineSetX(5), guidelineSetY(2), ShadowRadius, rightHeight)
  90. drawingContext.DrawRectangle(brushes(Placement.Right), Nothing, right)
  91. End If
  92. Dim bottomLeft As New Rect(shadowBounds.Left, guidelineSetY(3), cornerRadius.BottomLeft, cornerRadius.BottomLeft)
  93. drawingContext.DrawRectangle(brushes(Placement.BottomLeft), Nothing, bottomLeft)
  94. Dim bottomWidth As Double = guidelineSetX(4) - guidelineSetX(3)
  95. If bottomWidth > 0 Then
  96. Dim bottom As New Rect(guidelineSetX(3), guidelineSetY(5), bottomWidth, ShadowRadius)
  97. drawingContext.DrawRectangle(brushes(Placement.Bottom), Nothing, bottom)
  98. End If
  99. Dim bottomRight As New Rect(guidelineSetX(4), guidelineSetY(4), cornerRadius.BottomRight, cornerRadius.BottomRight)
  100. drawingContext.DrawRectangle(brushes(Placement.BottomRight), Nothing, bottomRight)
  101. If cornerRadius.TopLeft = ShadowRadius AndAlso cornerRadius.TopLeft = cornerRadius.TopRight AndAlso cornerRadius.TopLeft = cornerRadius.BottomLeft AndAlso cornerRadius.TopLeft = cornerRadius.BottomRight Then
  102. Dim center As New Rect(guidelineSetX(0), guidelineSetY(0), centerWidth, centerHeight)
  103. drawingContext.DrawRectangle(brushes(Placement.Center), Nothing, center)
  104. Else
  105. Dim figure As New PathFigure()
  106. If cornerRadius.TopLeft > ShadowRadius Then
  107. figure.StartPoint = New Point(guidelineSetX(1), guidelineSetY(0))
  108. figure.Segments.Add(New LineSegment(New Point(guidelineSetX(1), guidelineSetY(1)), True))
  109. figure.Segments.Add(New LineSegment(New Point(guidelineSetX(0), guidelineSetY(1)), True))
  110. Else
  111. figure.StartPoint = New Point(guidelineSetX(0), guidelineSetY(0))
  112. End If
  113. If cornerRadius.BottomLeft > ShadowRadius Then
  114. figure.Segments.Add(New LineSegment(New Point(guidelineSetX(0), guidelineSetY(3)), True))
  115. figure.Segments.Add(New LineSegment(New Point(guidelineSetX(3), guidelineSetY(3)), True))
  116. figure.Segments.Add(New LineSegment(New Point(guidelineSetX(3), guidelineSetY(5)), True))
  117. Else
  118. figure.Segments.Add(New LineSegment(New Point(guidelineSetX(0), guidelineSetY(5)), True))
  119. End If
  120. If cornerRadius.BottomRight > ShadowRadius Then
  121. figure.Segments.Add(New LineSegment(New Point(guidelineSetX(4), guidelineSetY(5)), True))
  122. figure.Segments.Add(New LineSegment(New Point(guidelineSetX(4), guidelineSetY(4)), True))
  123. figure.Segments.Add(New LineSegment(New Point(guidelineSetX(5), guidelineSetY(4)), True))
  124. Else
  125. figure.Segments.Add(New LineSegment(New Point(guidelineSetX(5), guidelineSetY(5)), True))
  126. End If
  127. If cornerRadius.TopRight > ShadowRadius Then
  128. figure.Segments.Add(New LineSegment(New Point(guidelineSetX(5), guidelineSetY(2)), True))
  129. figure.Segments.Add(New LineSegment(New Point(guidelineSetX(2), guidelineSetY(2)), True))
  130. figure.Segments.Add(New LineSegment(New Point(guidelineSetX(2), guidelineSetY(0)), True))
  131. Else
  132. figure.Segments.Add(New LineSegment(New Point(guidelineSetX(5), guidelineSetY(0)), True))
  133. End If
  134. figure.IsClosed = True
  135. figure.Freeze()
  136. Dim geometry As New PathGeometry()
  137. geometry.Figures.Add(figure)
  138. geometry.Freeze()
  139. drawingContext.DrawGeometry(brushes(Placement.Center), Nothing, geometry)
  140. End If
  141. drawingContext.Pop()
  142. End If
  143. End Sub
  144. Private Enum Placement
  145. TopLeft = 0
  146. Top = 1
  147. TopRight = 2
  148. Left = 3
  149. Center = 4
  150. Right = 5
  151. BottomLeft = 6
  152. Bottom = 7
  153. BottomRight = 8
  154. End Enum
  155. Private Shared _commonBrushes As Brush()
  156. Private Shared _commonCornerRadius As CornerRadius
  157. Private Shared ReadOnly _resourceAccess As New Object()
  158. Private _brushes As Brush()
  159. Private Shared Sub ClearBrushes(o As DependencyObject, e As DependencyPropertyChangedEventArgs)
  160. CType(o, MyDropShadow)._brushes = Nothing
  161. End Sub
  162. Private Function CreateStops(c As Color, cornerRadius As Double) As GradientStopCollection
  163. Dim gradientScale As Double = 1 / (ShadowRadius + cornerRadius)
  164. Dim gsc As New GradientStopCollection
  165. Dim stopColor As Color = c
  166. gsc.Add(New GradientStop(stopColor, (ShadowRadius * 0.1 + cornerRadius) * gradientScale))
  167. stopColor.A = CByte(0.74336 * c.A)
  168. gsc.Add(New GradientStop(stopColor, (ShadowRadius * 0.3 + cornerRadius) * gradientScale))
  169. stopColor.A = CByte(0.38053 * c.A)
  170. gsc.Add(New GradientStop(stopColor, (ShadowRadius * 0.5 + cornerRadius) * gradientScale))
  171. stopColor.A = CByte(0.12389 * c.A)
  172. gsc.Add(New GradientStop(stopColor, (ShadowRadius * 0.7 + cornerRadius) * gradientScale))
  173. stopColor.A = CByte(0.02654 * c.A)
  174. gsc.Add(New GradientStop(stopColor, (ShadowRadius * 0.9 + cornerRadius) * gradientScale))
  175. stopColor.A = 0
  176. gsc.Add(New GradientStop(stopColor, (ShadowRadius + cornerRadius) * gradientScale))
  177. gsc.Freeze()
  178. Return gsc
  179. End Function
  180. Private Function CreateBrushes(c As Color, cornerRadius As CornerRadius) As Brush()
  181. Dim brushes As Brush() = New Brush(8) {}
  182. brushes(Placement.Center) = New SolidColorBrush(c)
  183. brushes(Placement.Center).Freeze()
  184. Dim sideStops As GradientStopCollection = CreateStops(c, 0)
  185. Dim top As New LinearGradientBrush(sideStops, New Point(0, 1), New Point(0, 0))
  186. top.Freeze()
  187. brushes(Placement.Top) = top
  188. Dim left As New LinearGradientBrush(sideStops, New Point(1, 0), New Point(0, 0))
  189. left.Freeze()
  190. brushes(Placement.Left) = left
  191. Dim right As New LinearGradientBrush(sideStops, New Point(0, 0), New Point(1, 0))
  192. right.Freeze()
  193. brushes(Placement.Right) = right
  194. Dim bottom As New LinearGradientBrush(sideStops, New Point(0, 0), New Point(0, 1))
  195. bottom.Freeze()
  196. brushes(Placement.Bottom) = bottom
  197. Dim topLeftStops As GradientStopCollection
  198. If cornerRadius.TopLeft = 0 Then
  199. topLeftStops = sideStops
  200. Else
  201. topLeftStops = CreateStops(c, cornerRadius.TopLeft)
  202. End If
  203. Dim topLeft As New RadialGradientBrush(topLeftStops) With {
  204. .RadiusX = 1,
  205. .RadiusY = 1,
  206. .Center = New Point(1, 1),
  207. .GradientOrigin = New Point(1, 1)
  208. }
  209. topLeft.Freeze()
  210. brushes(Placement.TopLeft) = topLeft
  211. Dim topRightStops As GradientStopCollection
  212. If cornerRadius.TopRight = 0 Then
  213. topRightStops = sideStops
  214. ElseIf cornerRadius.TopRight = cornerRadius.TopLeft Then
  215. topRightStops = topLeftStops
  216. Else
  217. topRightStops = CreateStops(c, cornerRadius.TopRight)
  218. End If
  219. Dim topRight As New RadialGradientBrush(topRightStops) With {
  220. .RadiusX = 1,
  221. .RadiusY = 1,
  222. .Center = New Point(0, 1),
  223. .GradientOrigin = New Point(0, 1)
  224. }
  225. topRight.Freeze()
  226. brushes(Placement.TopRight) = topRight
  227. Dim bottomLeftStops As GradientStopCollection
  228. If cornerRadius.BottomLeft = 0 Then
  229. bottomLeftStops = sideStops
  230. ElseIf cornerRadius.BottomLeft = cornerRadius.TopLeft Then
  231. bottomLeftStops = topLeftStops
  232. ElseIf cornerRadius.BottomLeft = cornerRadius.TopRight Then
  233. bottomLeftStops = topRightStops
  234. Else
  235. bottomLeftStops = CreateStops(c, cornerRadius.BottomLeft)
  236. End If
  237. Dim bottomLeft As New RadialGradientBrush(bottomLeftStops) With {
  238. .RadiusX = 1,
  239. .RadiusY = 1,
  240. .Center = New Point(1, 0),
  241. .GradientOrigin = New Point(1, 0)
  242. }
  243. bottomLeft.Freeze()
  244. brushes(Placement.BottomLeft) = bottomLeft
  245. Dim bottomRightStops As GradientStopCollection
  246. If cornerRadius.BottomRight = 0 Then
  247. bottomRightStops = sideStops
  248. ElseIf cornerRadius.BottomRight = cornerRadius.TopLeft Then
  249. bottomRightStops = topLeftStops
  250. ElseIf cornerRadius.BottomRight = cornerRadius.TopRight Then
  251. bottomRightStops = topRightStops
  252. ElseIf cornerRadius.BottomRight = cornerRadius.BottomLeft Then
  253. bottomRightStops = bottomLeftStops
  254. Else
  255. bottomRightStops = CreateStops(c, cornerRadius.BottomRight)
  256. End If
  257. Dim bottomRight As New RadialGradientBrush(bottomRightStops) With {
  258. .RadiusX = 1,
  259. .RadiusY = 1,
  260. .Center = New Point(0, 0),
  261. .GradientOrigin = New Point(0, 0)
  262. }
  263. bottomRight.Freeze()
  264. brushes(Placement.BottomRight) = bottomRight
  265. Return brushes
  266. End Function
  267. Private Function GetBrushes(c As Color, cornerRadius As CornerRadius) As Brush()
  268. If _commonBrushes Is Nothing Then
  269. SyncLock _resourceAccess
  270. If _commonBrushes Is Nothing Then
  271. _commonBrushes = CreateBrushes(c, cornerRadius)
  272. _commonCornerRadius = cornerRadius
  273. End If
  274. End SyncLock
  275. End If
  276. If c = CType(_commonBrushes(Placement.Center), SolidColorBrush).Color AndAlso cornerRadius = _commonCornerRadius Then
  277. _brushes = Nothing
  278. Return _commonBrushes
  279. ElseIf _brushes Is Nothing Then
  280. _brushes = CreateBrushes(c, cornerRadius)
  281. End If
  282. Return _brushes
  283. End Function
  284. End Class
  285. '参考自:https://referencesource.microsoft.com/#PresentationFramework.Aero/parent/Shared/Microsoft/Windows/Themes/SystemDropShadowChrome.cs,6d9c27d92a8128c1