wpf 覆盖元素在webview2之上 - 优化

备注

原有提供的覆盖元素在webview2的方案性能较差(https://www.cnblogs.com/ives/p/17719809.html)
寻找后发现一个新的方案性能较高。此方案无需重写webview2相关事件而是通过样式绑定到新的组件中,可以保留原有代码只需要极少的改动即可适配。

帮助类SupportControl

using System.Windows;
using System.Windows.Controls;

namespace CraesUmfp.Component.CtrlFolder
{
    public class SupportControl : UserControl
    {
        private readonly SupportWnd _alpha;

        public SupportControl()
        { 
            _alpha = new SupportWnd(this);
        }

        public static readonly DependencyProperty BackProperty = DependencyProperty.Register("Back", typeof(UIElement),
            typeof(SupportControl), new PropertyMetadata(default(UIElement), BackChangedCallback));

        private static void BackChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (!(d is SupportControl control))
                return;

            var argsNewValue = e.NewValue;
            if (argsNewValue is UIElement element)
            {
                control.BackChange(element);
            }
        }

        public UIElement Back
        {
            get => (UIElement)GetValue(BackProperty);
            set => SetValue(BackProperty, value);
        }

        private void BackChange(UIElement value)
        {
            this.Content = value;
        }

        public static readonly DependencyProperty FrontProperty = DependencyProperty.Register("Front", typeof(UIElement),
            typeof(SupportControl), new PropertyMetadata(default(UIElement), FrontChangedCallback));

        private static void FrontChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (!(d is SupportControl control))
                return;

            var argsNewValue = e.NewValue;
            if (argsNewValue is UIElement element)
            {
                control.FrontChange(element);
            }
        }

        public UIElement Front
        {
            get => (UIElement)GetValue(FrontProperty);
            set => SetValue(FrontProperty, value);
        }

        private void FrontChange(UIElement value)
        {
            _alpha.FrontContent = value;
        }

    }
}

帮助类

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;

namespace CraesUmfp.Component.CtrlFolder
{
    internal class SupportWnd : Window
    {
        private Window _wndHost;
        private readonly UserControl _backControl;
        private UIElement _frontContent;
        private readonly Point _basePoint;

        internal SupportWnd(UserControl backControl) : base()
        {
            _basePoint = new Point();
            Background = new SolidColorBrush(Color.FromArgb(0, 255, 255, 255));
            AllowsTransparency = true;
            ResizeMode = ResizeMode.NoResize;
            ShowInTaskbar = false;
            WindowStyle = WindowStyle.None;
            _backControl = backControl;
            _backControl.Loaded += BackgroundLoaded;
            _backControl.LayoutUpdated += BackgroundLayoutUpdated;
            _backControl.Unloaded += BackgroundUnloaded;

        }

        public UIElement FrontContent
        {
            get => _frontContent;
            set
            {
                _frontContent = value;
                Content = value;
            }
        }

        private void BackgroundUnloaded(object sender, RoutedEventArgs e)
        {
            _wndHost.Closing -= WndHost_Closing;
            _wndHost.SizeChanged -= WndHost_SizeChanged;
            _wndHost.LocationChanged -= WndHost_LocationChanged;
            this.Hide();
        }

        private static double GetSourceScaleX(PresentationSource source)
        {
            var compositionTarget = source?.CompositionTarget;
            if (compositionTarget is null)
                return 1;

            var transformToDevice = compositionTarget.TransformToDevice;
            return transformToDevice.M11;
        }

        private static double GetSourceScaleY(PresentationSource source)
        {
            var compositionTarget = source?.CompositionTarget;
            if (compositionTarget is null)
                return 1;

            var transformToDevice = compositionTarget.TransformToDevice;
            return transformToDevice.M22;
        }

        private void UpdateOwnPosition()
        {
            if (_wndHost == null || !_backControl.IsVisible)
            {
                return;
            }

            var locationFromScreen = _backControl.PointToScreen(_basePoint);
            var source = PresentationSource.FromVisual(_wndHost);
            var compositionTarget = source?.CompositionTarget;
            if (compositionTarget is null)
                return;

            var fromDevice = compositionTarget.TransformFromDevice;
            var targetPoints = fromDevice.Transform(locationFromScreen);
            this.Left = targetPoints.X;
            this.Top = targetPoints.Y;
        }



        private void UpdateOwnSize()
        {
            if (_wndHost == null || !_backControl.IsVisible)
            {
                return;
            }
            var source = PresentationSource.FromVisual(_wndHost);
            var backActualWidth = _backControl.ActualWidth;
            var backActualHeight = _backControl.ActualHeight;
            var point = new Point(backActualWidth, backActualHeight);
            var last = _backControl.PointToScreen(point);
            var first = _backControl.PointToScreen(_basePoint);
            var size = last - first;
            var sourceScaleY = GetSourceScaleY(source);
            this.Height = size.Y / sourceScaleY;
            var sourceScaleX = GetSourceScaleX(source);
            this.Width = size.X / sourceScaleX;
        }

        private void BackgroundLoaded(object sender, RoutedEventArgs e)
        {
            _wndHost = GetWindow(_backControl);
            this.Owner = _wndHost;
            if (_wndHost is null)
                return;

            _wndHost.Closing += WndHost_Closing;
            _wndHost.SizeChanged += WndHost_SizeChanged;
            _wndHost.LocationChanged += WndHost_LocationChanged;
            try
            {
                this.Show();
                _wndHost.Focus();
            }
            catch
            {
                this.Hide();
            }
        }
        private void BackgroundLayoutUpdated(object sender, EventArgs e)
        {
            UpdateOwnPosition();
            UpdateOwnSize();
        }

        private void WndHost_LocationChanged(object sender, EventArgs e)
        {
            UpdateOwnPosition();
        }

        private void WndHost_SizeChanged(object sender, SizeChangedEventArgs e)
        {
            UpdateOwnPosition();
            UpdateOwnSize();
        }

        private void WndHost_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            this.Close();
        }
    }
}

使用

<UserControl.Resources>
    <wpf:WebView2 x:Key="WebViewKey"
              Name="webView"
                   Initialized="webView_Initialized" CoreWebView2InitializationCompleted="webView_CoreWebView2InitializationCompleted"/>
    <offlinemap:OfflineMapSaveDialog x:Key="StackPaneCtrlKey" />
</UserControl.Resources>

<Grid>
    <ctrlFolder:SupportControl x:Name="SupportControlName"
                           Back="{StaticResource WebViewKey}"
                           Front="{StaticResource StackPaneCtrlKey}" />
</Grid>

[参考]
最新C# WPF WebView2覆盖上层控件解决办法

posted @ 2025-06-18 23:44  Hey,Coder!  阅读(53)  评论(0)    收藏  举报