【问题标题】:Can I overlay a WPF window on top of another?我可以将 WPF 窗口覆盖在另一个窗口之上吗?
【发布时间】:2017-04-29 19:55:58
【问题描述】:

我有一个 WPF 窗口,其中包含一个 WindowsFormsHost 元素。我需要在这个元素上绘制东西,但是WindowsFormsHost 的本质意味着它总是在绘图堆的顶部。由于我无法在 WindowsFormsHost 组件顶部的同一 WPF 窗口中绘图,我可以在其顶部覆盖另一个窗口吗?

我已经初步尝试过,但有一些问题:

1) 我无法阻止其他应用程序的窗口进入主窗口和覆盖窗口之间。

2) 当我 Alt-Tab 时,覆盖窗口出现在窗口列表中,非常难看。

基本上我需要“子窗口”的概念,并且所有意图和目的的窗口都作为另一个窗口的一部分出现。 UserControls 对我不起作用,因为WindowsFormsHost总是在它上面绘制。

有什么想法吗?


更新 [2011 年 5 月 23 日 10:13]

感谢两位的回答。

我尝试了ChildWindow 方法,但WindowsFormsHost 元素仍然在顶部。据我了解,只有真正的窗口才能在WindowsFormsHost 之上绘制,同一窗口中的任何内容都会在WindowsFormsHost 之下。

带有WindowsFormsHost 的元素仍将在 WinForms 组件下绘制,它们始终绘制在顶部,这似乎不可协商...

我想我正在寻找一种停靠外部窗口以充当主窗口一部分的方法。在 Mac 上,有一个真正的“子窗口”的概念,我正在寻找类似的东西。

【问题讨论】:

标签: .net wpf interop windowsformshost


【解决方案1】:

我通过使用Popup 而不是透明的Window 解决了这个问题

更新

我最终得到了一个子类Popup,我称之为AirspacePopup

AirspacePopup 做了什么

  • 关注其PlacementTarget
  • 并不总是在顶部,而是相对于放置它的Window 放置。该解决方案来自Chris Cavanagh's Blog
  • 允许移动到“屏幕外”。这是通过剪裁Popup 并在其子元素移出屏幕后为其设置负值Margin 来实现的。该解决方案来自 Rick Sladkeythis StackOverflow post

这是一个示例,其中AirspacePopup 用于在WebBrowser 控件(实际上是一个 WinForms 控件)之上绘制一个Ellipse,但它与任何WindowsFormsHost 一样适用。

<Grid>
    <local:AirspacePopup PlacementTarget="{Binding ElementName=webBrowser}"
                         FollowPlacementTarget="True"
                         AllowOutsideScreenPlacement="True"
                         ParentWindow="{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
                         IsOpen="True"
                         AllowsTransparency="True"
                         Placement="Center"
                         Width="{Binding ElementName=googleBrowser, Path=ActualWidth}"
                         Height="{Binding ElementName=googleBrowser, Path=ActualHeight}">
        <Grid>
            <Ellipse Width="100" Height="100" Fill="Green" Margin="100"/>
        </Grid>
    </local:AirspacePopup>
    <WebBrowser Name="webBrowser" Loaded="WebBrowser_Loaded"/>
</Grid>

导航的简单代码..

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void WebBrowser_Loaded(object sender, RoutedEventArgs e)
    {
        WebBrowser webbrowser = sender as WebBrowser;
        webbrowser.Navigate("http://www.stackoverflow.com");
    }
}

AirspacePopup

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Interop;

public class AirspacePopup : Popup
{
    public static readonly DependencyProperty IsTopmostProperty =
        DependencyProperty.Register("IsTopmost",
                                    typeof(bool),
                                    typeof(AirspacePopup),
                                    new FrameworkPropertyMetadata(false, OnIsTopmostChanged));

    public static readonly DependencyProperty FollowPlacementTargetProperty =
        DependencyProperty.RegisterAttached("FollowPlacementTarget",
                                            typeof(bool),
                                            typeof(AirspacePopup),
                                            new UIPropertyMetadata(false));

    public static readonly DependencyProperty AllowOutsideScreenPlacementProperty =
        DependencyProperty.RegisterAttached("AllowOutsideScreenPlacement",
                                            typeof(bool),
                                            typeof(AirspacePopup),
                                            new UIPropertyMetadata(false));

    public static readonly DependencyProperty ParentWindowProperty =
        DependencyProperty.RegisterAttached("ParentWindow",
                                            typeof(Window),
                                            typeof(AirspacePopup),
                                            new UIPropertyMetadata(null, ParentWindowPropertyChanged));

    private static void OnIsTopmostChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
    {
        AirspacePopup airspacePopup = source as AirspacePopup;
        airspacePopup.SetTopmostState(airspacePopup.IsTopmost);
    }

    private static void ParentWindowPropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e)
    {
        AirspacePopup airspacePopup = source as AirspacePopup;
        airspacePopup.ParentWindowChanged();
    }

    private bool? m_appliedTopMost;
    private bool m_alreadyLoaded;
    private Window m_parentWindow;

    public AirspacePopup()
    {
        Loaded += OnPopupLoaded;
        Unloaded += OnPopupUnloaded;

        DependencyPropertyDescriptor descriptor = DependencyPropertyDescriptor.FromProperty(PlacementTargetProperty, typeof(AirspacePopup));
        descriptor.AddValueChanged(this, PlacementTargetChanged);
    }

    public bool IsTopmost
    {
        get { return (bool)GetValue(IsTopmostProperty); }
        set { SetValue(IsTopmostProperty, value); }
    }
    public bool FollowPlacementTarget
    {
        get { return (bool)GetValue(FollowPlacementTargetProperty); }
        set { SetValue(FollowPlacementTargetProperty, value); }
    }
    public bool AllowOutsideScreenPlacement
    {
        get { return (bool)GetValue(AllowOutsideScreenPlacementProperty); }
        set { SetValue(AllowOutsideScreenPlacementProperty, value); }
    }
    public Window ParentWindow
    {
        get { return (Window)GetValue(ParentWindowProperty); }
        set { SetValue(ParentWindowProperty, value); }
    }

    private void ParentWindowChanged()
    {
        if (ParentWindow != null)
        {
            ParentWindow.LocationChanged += (sender, e2) =>
            {
                UpdatePopupPosition();
            };
            ParentWindow.SizeChanged += (sender, e2) =>
            {
                UpdatePopupPosition();
            };
        }
    }
    private void PlacementTargetChanged(object sender, EventArgs e)
    {
        FrameworkElement placementTarget = this.PlacementTarget as FrameworkElement;
        if (placementTarget != null)
        {
            placementTarget.SizeChanged += (sender2, e2) =>
            {
                UpdatePopupPosition();
            };
        }
    }

    private void UpdatePopupPosition()
    {
        FrameworkElement placementTarget = this.PlacementTarget as FrameworkElement;
        FrameworkElement child = this.Child as FrameworkElement;

        if (PresentationSource.FromVisual(placementTarget) != null &&
            AllowOutsideScreenPlacement == true)
        {
            double leftOffset = CutLeft(placementTarget);
            double topOffset = CutTop(placementTarget);
            double rightOffset = CutRight(placementTarget);
            double bottomOffset = CutBottom(placementTarget);
            Debug.WriteLine(bottomOffset);
            this.Width = Math.Max(0, Math.Min(leftOffset, rightOffset) + placementTarget.ActualWidth);
            this.Height = Math.Max(0, Math.Min(topOffset, bottomOffset) + placementTarget.ActualHeight);

            if (child != null)
            {
                child.Margin = new Thickness(leftOffset, topOffset, rightOffset, bottomOffset);
            }
        }
        if (FollowPlacementTarget == true)
        {
            this.HorizontalOffset += 0.01;
            this.HorizontalOffset -= 0.01;
        }
    }
    private double CutLeft(FrameworkElement placementTarget)
    {
        Point point = placementTarget.PointToScreen(new Point(0, placementTarget.ActualWidth));
        return Math.Min(0, point.X);
    }
    private double CutTop(FrameworkElement placementTarget)
    {
        Point point = placementTarget.PointToScreen(new Point(placementTarget.ActualHeight, 0));
        return Math.Min(0, point.Y);
    }
    private double CutRight(FrameworkElement placementTarget)
    {
        Point point = placementTarget.PointToScreen(new Point(0, placementTarget.ActualWidth));
        point.X += placementTarget.ActualWidth;
        return Math.Min(0, SystemParameters.VirtualScreenWidth - (Math.Max(SystemParameters.VirtualScreenWidth, point.X)));
    }
    private double CutBottom(FrameworkElement placementTarget)
    {
        Point point = placementTarget.PointToScreen(new Point(placementTarget.ActualHeight, 0));
        point.Y += placementTarget.ActualHeight;
        return Math.Min(0, SystemParameters.VirtualScreenHeight - (Math.Max(SystemParameters.VirtualScreenHeight, point.Y)));
    }

    private void OnPopupLoaded(object sender, RoutedEventArgs e)
    {
        if (m_alreadyLoaded) 
            return;

        m_alreadyLoaded = true;

        if (Child != null)
        {
            Child.AddHandler(PreviewMouseLeftButtonDownEvent, new MouseButtonEventHandler(OnChildPreviewMouseLeftButtonDown), true);
        }

        m_parentWindow = Window.GetWindow(this);

        if (m_parentWindow == null) 
            return;

        m_parentWindow.Activated += OnParentWindowActivated;
        m_parentWindow.Deactivated += OnParentWindowDeactivated;
    }

    private void OnPopupUnloaded(object sender, RoutedEventArgs e)
    {
        if (m_parentWindow == null)
            return;
        m_parentWindow.Activated -= OnParentWindowActivated;
        m_parentWindow.Deactivated -= OnParentWindowDeactivated;
    }

    private void OnParentWindowActivated(object sender, EventArgs e)
    {
        SetTopmostState(true);
    }

    private void OnParentWindowDeactivated(object sender, EventArgs e)
    {
        if (IsTopmost == false)
        {
            SetTopmostState(IsTopmost);
        }
    }

    private void OnChildPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        SetTopmostState(true);
        if (!m_parentWindow.IsActive && IsTopmost == false)
        {
            m_parentWindow.Activate();
        }
    }

    protected override void OnOpened(EventArgs e)
    {
        SetTopmostState(IsTopmost);
        base.OnOpened(e);
    }

    private void SetTopmostState(bool isTop)
    {
        // Don’t apply state if it’s the same as incoming state
        if (m_appliedTopMost.HasValue && m_appliedTopMost == isTop)
        {
            return;
        }

        if (Child == null) 
            return;

        var hwndSource = (PresentationSource.FromVisual(Child)) as HwndSource;

        if (hwndSource == null) 
            return;
        var hwnd = hwndSource.Handle;

        RECT rect;

        if (!GetWindowRect(hwnd, out rect)) 
            return;

        Debug.WriteLine("setting z-order " + isTop);

        if (isTop)
        {
            SetWindowPos(hwnd, HWND_TOPMOST, rect.Left, rect.Top, (int)Width, (int)Height, TOPMOST_FLAGS);
        }
        else
        {
            // Z-Order would only get refreshed/reflected if clicking the
            // the titlebar (as opposed to other parts of the external
            // window) unless I first set the popup to HWND_BOTTOM
            // then HWND_TOP before HWND_NOTOPMOST
            SetWindowPos(hwnd, HWND_BOTTOM, rect.Left, rect.Top, (int)Width, (int)Height, TOPMOST_FLAGS);
            SetWindowPos(hwnd, HWND_TOP, rect.Left, rect.Top, (int)Width, (int)Height, TOPMOST_FLAGS);
            SetWindowPos(hwnd, HWND_NOTOPMOST, rect.Left, rect.Top, (int)Width, (int)Height, TOPMOST_FLAGS);
        }

        m_appliedTopMost = isTop;
    }

    #region P/Invoke imports & definitions
    #pragma warning disable 1591 //Xml-doc
    #pragma warning disable 169 //Never used-warning
    // ReSharper disable InconsistentNaming
    // Imports etc. with their naming rules

    [StructLayout(LayoutKind.Sequential)]
    public struct RECT

    {
        public int Left;
        public int Top;
        public int Right;
        public int Bottom;
    }

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);

    [DllImport("user32.dll")]
    private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X,
    int Y, int cx, int cy, uint uFlags);

    static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
    static readonly IntPtr HWND_NOTOPMOST = new IntPtr(-2);
    static readonly IntPtr HWND_TOP = new IntPtr(0);
    static readonly IntPtr HWND_BOTTOM = new IntPtr(1);

    private const UInt32 SWP_NOSIZE = 0x0001;
    const UInt32 SWP_NOMOVE = 0x0002;
    const UInt32 SWP_NOZORDER = 0x0004;
    const UInt32 SWP_NOREDRAW = 0x0008;
    const UInt32 SWP_NOACTIVATE = 0x0010;

    const UInt32 SWP_FRAMECHANGED = 0x0020; /* The frame changed: send WM_NCCALCSIZE */
    const UInt32 SWP_SHOWWINDOW = 0x0040;
    const UInt32 SWP_HIDEWINDOW = 0x0080;
    const UInt32 SWP_NOCOPYBITS = 0x0100;
    const UInt32 SWP_NOOWNERZORDER = 0x0200; /* Don’t do owner Z ordering */
    const UInt32 SWP_NOSENDCHANGING = 0x0400; /* Don’t send WM_WINDOWPOSCHANGING */

    const UInt32 TOPMOST_FLAGS = 
        SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_NOMOVE | SWP_NOREDRAW | SWP_NOSENDCHANGING;

    // ReSharper restore InconsistentNaming
    #pragma warning restore 1591
    #pragma warning restore 169
    #endregion
}

【讨论】:

  • 我怎样才能使这个 HitTestVisible?我试图将网格背景设置为“透明”,但它不起作用。当我将背景设置为“白色”并将不透明度设置为 0.01 时,它是 HitTestVisible!也许有人有想法?
  • 窗口最大化时底部有一个小间隙,弹出窗口没有正确跟随。我该如何解决这个问题?
  • 从最小化状态恢复窗口时,这不适用于 dwm 动画。当主窗口仍在动画时,椭圆会立即出现。有解决办法吗?
  • 截至 2021 年,似乎仍然是最可靠的解决方案
【解决方案2】:

经过大量测试不同的解决方案:

  1. Microsoft.DwayneNeed (https://microsoftdwayneneed.codeplex.com/)

    优点:
    • 据我所知,效果很好(没有测试太多,但看起来很像)
    • 性能影响似乎非常低。

    缺点:
    • 相对较大的图书馆。

  2. AirspaceFixer (https://github.com/chris84948/AirspaceFixer)

    优点:
    • 效果很好(没怎么测试过,但看起来很像)

    缺点:
    • 对性能有一点影响。 (据我所知,比 Microsoft.DwayneNeed 大)

  3. Fredrik Hedblad 的 AirspacePopup (https://stackoverflow.com/a/6452940/4870255)

    优点:
    • 简单代码。主要是:D

    缺点:
    • 在全屏模式下,底部的一部分缺失。
    • 多显示器问题:当从一个窗口移动到另一个窗口时,覆盖没有完全覆盖 winformshostwindow。

对我来说,最好的解决方案是使用 Microsoft.DwayneNeed。 (我用它来让 CefSharp-Winforms 进入 WPF 应用程序)

因为让它工作并不简单,所以她是一个小教程:

  1. 转到https://microsoftdwayneneed.codeplex.com/SourceControl/latest
  2. 点击下载。
  3. 打开 Microsoft.DwayneNeed.sln
  4. 编译它。
  5. 在创建的 Debug 或 Releas 文件夹中引用 Microsoft.DwayneNeed.dll。
  6. 添加
    xmlns:interop="clr-namespace:Microsoft.DwayneNeed.Interop;assembly=Microsoft.DwayneNeed
    到您的窗口。
  7. 添加 到您的网格。

例子:

<Window x:Class="Toll.MainWindow"
		xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
		xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
		xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
		xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
		xmlns:interop="clr-namespace:Microsoft.DwayneNeed.Interop;assembly=Microsoft.DwayneNeed"
		xmlns:local="clr-namespace:Toll"
		mc:Ignorable="d"
		Title="MainWindow" Width="1500" Height="800" Closing="Window_Closing">
	<Grid Name="root">
		<interop:AirspaceDecorator AirspaceMode="Redirect"
						   Background="White"
						   IsInputRedirectionEnabled="True"
						   IsOutputRedirectionEnabled="True">
			<WindowsFormsHost x:Name="windowsFormsHost1" Visibility="Visible" />
		</interop:AirspaceDecorator>
		
		<Ellipse Width="100" Height="100" Fill="Green" Margin="100"/>
	</Grid>
</Window>

【讨论】:

  • 对我不起作用:System.NullReferenceException 对象引用未设置为对象的实例。在 Microsoft.DwayneNeed.Interop.RedirectedHwndHost.UpdateOutputRedirection() 在 Microsoft.DwayneNeed.Interop.RedirectedHwndHost.<.ctor>b__31_2(Object e, EventArgs a) 在 System.Windows.Threading.DispatcherTimer.FireTick(对象未使用) 在 System。 Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs) ....
  • 使用 Microsoft.DwayneNeed,一切都很好,很简单。但!!!在我的例子中,WinForm 控件中非常重要的特性是拖放内容的能力,不幸的是它在使用 AirspaceDecorator 装饰 WindowsFormsHost 后停止工作。我无法找到并解决这个问题:(
  • 使用 Microsoft.DwayneNeed 有一个很大的问题...... Winform 上的 TextBox 当我开始在我之前单击的其他程序上键入它时,重点是 TextBox ...:/ 有人遇到过这个问题吗?!
  • @folibis 我遇到了同样的问题,但我必须稍微编辑一下库源代码。如果您想这样做,请导航到Microsoft.DwayneNeed/Interop/RedirectedWindow.cs,然后转到第 165 行并将_interopBitmap.Invalidate(); 编辑为_interopBitmap?.Invalidate();。这似乎是一个愚蠢的黑客,但这个东西有效,到目前为止我仍然没有遇到任何问题。
【解决方案3】:

在使用 CAL 或事件聚合器的典型 MVP 应用程序中,您可能必须创建另一个窗口并将其命名为弹出窗口或子窗口 (ChildWindow.XAML),并在 (ChildWindow.XAML.CS) 中使用 get 方法,例如

public static ChildWindow Get()
        {
            ChildWindow dialogBox = new ChildWindow();
            return dialogBox;
        } 

maniwindow 中有一个属性,需要时可以返回 Childwindow 类型。 喜欢

public bool ShowChildWindow
{
    get
    {
        return PopUpDialog.Get().ShowDialog().GetValueOrDefault();
    }
}

希望对你有帮助,

【讨论】:

    【解决方案4】:

    您可以在WindowsFormsHost 中执行“叠加”部分。所以你可以有一个子元素,它应该位于托管元素中的其他内容之上。

    【讨论】:

    • WPF 似乎不允许您在 WindowsFormsHost 之上分层任何东西 - 所以我认为这不会起作用。
    【解决方案5】:

    我建议使用MahApps library

    我已经为这个问题苦苦挣扎了一段时间,发现 MahApps 库在我没有任何配置的情况下解决了空域问题。有关更多信息,请参阅他们的常见问题解答:

    https://github.com/MahApps/MahApps.Metro/wiki/FAQ#1-why-is-so-and-so-winforms-control-invisible-or-not-rendering-why-is-the-webbrowser-or-other-control-covering-my-flyout-or-another-control-airspace

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-09-21
      • 2012-05-04
      • 2010-11-20
      • 1970-01-01
      • 1970-01-01
      • 2020-11-11
      • 2016-07-08
      相关资源
      最近更新 更多