@@ -31,8 +31,13 @@ public partial class OpacityMaskView : ContentControl
3131 private const string RootGridTemplateName = "PART_RootGrid" ;
3232
3333 private readonly Compositor _compositor = CompositionTarget . GetCompositorForCurrentThread ( ) ;
34+
35+ // Composition resources we create and need to tear down explicitly to avoid retaining composition references longer than necessary
3436 private CompositionBrush ? _mask ;
3537 private CompositionMaskBrush ? _maskBrush ;
38+ private CompositionSurfaceBrush ? _sourceBrush ;
39+ private Grid ? _rootGrid ;
40+ private SpriteVisual ? _redirectVisual ;
3641
3742 /// <summary>
3843 /// Initializes a new instance of the <see cref="OpacityMaskView"/> class.
@@ -41,6 +46,9 @@ public partial class OpacityMaskView : ContentControl
4146 public OpacityMaskView ( )
4247 {
4348 DefaultStyleKey = typeof ( OpacityMaskView ) ;
49+
50+ // Ensure composition resources are cleaned up when control unloads
51+ Unloaded += OpacityMaskView_Unloaded ;
4452 }
4553
4654 /// <summary>
@@ -57,22 +65,35 @@ protected override void OnApplyTemplate()
5765 {
5866 base . OnApplyTemplate ( ) ;
5967
68+ // Clean up any prior composition resources (e.g., when the template is re-applied)
69+ CleanupComposition ( ) ;
70+
6071 Grid rootGrid = ( Grid ) GetTemplateChild ( RootGridTemplateName ) ;
6172 ContentPresenter contentPresenter = ( ContentPresenter ) GetTemplateChild ( ContentPresenterTemplateName ) ;
6273 Border maskContainer = ( Border ) GetTemplateChild ( MaskContainerTemplateName ) ;
6374
75+ _rootGrid = rootGrid ;
76+
77+ // Create mask brush and its sources
6478 _maskBrush = _compositor . CreateMaskBrush ( ) ;
65- _maskBrush . Source = GetVisualBrush ( contentPresenter ) ;
79+
80+ // Source is the content we want to render through the mask
81+ _sourceBrush = GetVisualBrush ( contentPresenter ) ;
82+ _maskBrush . Source = _sourceBrush ;
83+
84+ // Mask is the opacity mask visual brush
6685 _mask = GetVisualBrush ( maskContainer ) ;
6786 _maskBrush . Mask = OpacityMask is null ? null : _mask ;
6887
88+ // Create a sprite visual that draws with the mask brush, and redirect the control visual to it
6989 SpriteVisual redirectVisual = _compositor . CreateSpriteVisual ( ) ;
7090 redirectVisual . RelativeSizeAdjustment = Vector2 . One ;
7191 redirectVisual . Brush = _maskBrush ;
7292 ElementCompositionPreview . SetElementChildVisual ( rootGrid , redirectVisual ) ;
93+ _redirectVisual = redirectVisual ;
7394 }
7495
75- private static CompositionBrush GetVisualBrush ( UIElement element )
96+ private static CompositionSurfaceBrush GetVisualBrush ( UIElement element )
7697 {
7798 Visual visual = ElementCompositionPreview . GetElementVisual ( element ) ;
7899
@@ -100,6 +121,51 @@ private static void OnOpacityMaskChanged(DependencyObject d, DependencyPropertyC
100121 }
101122
102123 UIElement ? opacityMask = ( UIElement ? ) e . NewValue ;
124+
125+ // Switch to the mask brush if an opacity mask is set; otherwise remove the mask
103126 maskBrush . Mask = opacityMask is null ? null : self . _mask ;
104127 }
105- }
128+
129+ // On control unload, ensure we tear down composition resources and clear the child visual
130+ private void OpacityMaskView_Unloaded ( object sender , RoutedEventArgs e ) => CleanupComposition ( ) ;
131+
132+ /// <summary>
133+ /// Clears the ElementCompositionPreview child visual and disposes all composition resources created by this control.
134+ /// This prevents composition resource retention across template reapplications or when the control is unloaded.
135+ /// </summary>
136+ private void CleanupComposition ( )
137+ {
138+ // Detach the child visual from the root grid if present
139+ if ( _rootGrid != null )
140+ {
141+ ElementCompositionPreview . SetElementChildVisual ( _rootGrid , null ) ;
142+ }
143+
144+ // Dispose the redirect visual
145+ if ( _redirectVisual != null )
146+ {
147+ _redirectVisual . Brush = null ;
148+ _redirectVisual . Dispose ( ) ;
149+ _redirectVisual = null ;
150+ }
151+
152+ // Dispose mask brush and its sources
153+ if ( _maskBrush != null )
154+ {
155+ _maskBrush . Source = null ;
156+ _maskBrush . Mask = null ;
157+ _maskBrush . Dispose ( ) ;
158+ _maskBrush = null ;
159+ }
160+
161+ // Dispose the source content brush explicitly
162+ _sourceBrush ? . Dispose ( ) ;
163+ _sourceBrush = null ;
164+
165+ // Dispose the mask brush instance if we created one
166+ _mask ? . Dispose ( ) ;
167+ _mask = null ;
168+
169+ _rootGrid = null ;
170+ }
171+ }
0 commit comments