79657303

Date: 2025-06-07 20:37:09
Score: 1
Natty:
Report link

WPF blur control

Here is a REAL WPF background blur. use the BackgroundPresenter, and use BlurEffect on it.

public class BackgroundPresenter : FrameworkElement
{
    private static readonly FieldInfo _drawingContentOfUIElement = typeof(UIElement)
        .GetField("_drawingContent", BindingFlags.Instance | BindingFlags.NonPublic)!;

    private static readonly FieldInfo _contentOfDrawingVisual = typeof(DrawingVisual)
        .GetField("_content", BindingFlags.Instance | BindingFlags.NonPublic)!;

    private static readonly Func<UIElement, DrawingContext> _renderOpenMethod = typeof(UIElement)
        .GetMethod("RenderOpen", BindingFlags.Instance | BindingFlags.NonPublic)!
        .CreateDelegate<Func<UIElement, DrawingContext>>();

    private static readonly Action<UIElement, DrawingContext> _onRenderMethod = typeof(UIElement)
        .GetMethod("OnRender", BindingFlags.Instance | BindingFlags.NonPublic)!
        .CreateDelegate<Action<UIElement, DrawingContext>>();

    private static readonly GetContentBoundsDelegate _methodGetContentBounds = typeof(VisualBrush)
        .GetMethod("GetContentBounds", BindingFlags.Instance | BindingFlags.NonPublic)!
        .CreateDelegate<GetContentBoundsDelegate>();

    private delegate void GetContentBoundsDelegate(VisualBrush visualBrush, out Rect bounds);
    private readonly Stack<UIElement> _parentStack = new();

    private static void ForceRender(UIElement target)
    {
        using DrawingContext drawingContext = _renderOpenMethod(target);

        _onRenderMethod.Invoke(target, drawingContext);
    }

    private static void DrawVisual(DrawingContext drawingContext, Visual visual, Point relatedXY)
    {
        var visualBrush = new VisualBrush(visual);
        _methodGetContentBounds.Invoke(visualBrush, out var contentBounds);

        drawingContext.DrawRectangle(
            visualBrush, null,
            new Rect(relatedXY.X + contentBounds.X, contentBounds.Y, contentBounds.Width, contentBounds.Height));
    }

    protected override Geometry GetLayoutClip(Size layoutSlotSize)
    {
        return new RectangleGeometry(new Rect(0, 0, ActualWidth, ActualHeight));
    }

    protected override void OnVisualParentChanged(DependencyObject oldParentObject)
    {
        if (oldParentObject is UIElement oldParent)
        {
            oldParent.LayoutUpdated -= ParentLayoutUpdated;
        }

        if (Parent is UIElement newParent)
        {
            newParent.LayoutUpdated += ParentLayoutUpdated;
        }
    }

    private void ParentLayoutUpdated(object? sender, EventArgs e)
    {
        // cannot use 'InvalidateVisual' here, because it will cause infinite loop

        ForceRender(this);

        Debug.WriteLine("Parent layout updated, forcing render of BackgroundPresenter.");
    }

    private static void DrawBackground(
        DrawingContext drawingContext, UIElement self,
        Stack<UIElement> parentStackStorage,
        int maxDepth,
        bool throwExceptionIfParentArranging)
    {
#if DEBUG
        bool selfInDesignMode = DesignerProperties.GetIsInDesignMode(self);
#endif

        var parent = VisualTreeHelper.GetParent(self) as UIElement;
        while (
            parent is { } &&
            parentStackStorage.Count < maxDepth)
        {
            // parent not visible, no need to render
            if (!parent.IsVisible)
            {
                parentStackStorage.Clear();
                return;
            }

#if DEBUG
            if (selfInDesignMode &&
                parent.GetType().ToString().Contains("VisualStudio"))
            {
                // 遍历到 VS 自身的设计器元素, 中断!
                break;
            }
#endif

            // is parent arranging
            // we cannot render it
            if (parent.RenderSize.Width == 0 ||
                parent.RenderSize.Height == 0)
            {
                parentStackStorage.Clear();

                if (throwExceptionIfParentArranging)
                {
                    throw new InvalidOperationException("Arranging");
                }

                // render after parent arranging finished
                self.InvalidateArrange();
                return;
            }

            parentStackStorage.Push(parent);
            parent = VisualTreeHelper.GetParent(parent) as UIElement;
        }

        var selfRect = new Rect(0, 0, self.RenderSize.Width, self.RenderSize.Height);
        while (parentStackStorage.TryPop(out var currentParent))
        {
            if (!parentStackStorage.TryPeek(out var breakElement))
            {
                breakElement = self;
            }

            var parentRelatedXY = currentParent.TranslatePoint(default, self);

            // has render data
            if (_drawingContentOfUIElement.GetValue(currentParent) is { } parentDrawingContent)
            {
                var drawingVisual = new DrawingVisual();
                _contentOfDrawingVisual.SetValue(drawingVisual, parentDrawingContent);

                DrawVisual(drawingContext, drawingVisual, parentRelatedXY);
            }

            if (currentParent is Panel parentPanelToRender)
            {
                foreach (UIElement child in parentPanelToRender.Children)
                {
                    if (child == breakElement)
                    {
                        break;
                    }

                    var childRelatedXY = child.TranslatePoint(default, self);
                    var childRect = new Rect(childRelatedXY, child.RenderSize);

                    if (!selfRect.IntersectsWith(childRect))
                    {
                        continue; // skip if not intersecting
                    }

                    if (child.IsVisible)
                    {
                        DrawVisual(drawingContext, child, childRelatedXY);
                    }
                }
            }
        }
    }

    public static void DrawBackground(DrawingContext drawingContext, UIElement self)
    {
        var parentStack = new Stack<UIElement>();
        DrawBackground(drawingContext, self, parentStack, int.MaxValue, true);
    }

    protected override void OnRender(DrawingContext drawingContext)
    {
        DrawBackground(drawingContext, this, _parentStack, MaxDepth, false);
    }

    public int MaxDepth
    {
        get { return (int)GetValue(MaxDepthProperty); }
        set { SetValue(MaxDepthProperty, value); }
    }

    public static readonly DependencyProperty MaxDepthProperty =
        DependencyProperty.Register("MaxDepth", typeof(int), typeof(BackgroundPresenter), new PropertyMetadata(64));
}

Code repository: SlimeNull/BlurBehindTest

Reasons:
  • Probably link only (1):
  • Contains signature (1):
  • Long answer (-1):
  • Has code block (-0.5):
  • Low reputation (0.5):
Posted by: SlimeNull