我可以将WPF窗口覆盖在另一个窗口之上吗?

时间:2011-05-22 12:12:23

标签: .net wpf interop windowsformshost

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

我已经初步尝试了这个,但我遇到了一些问题:

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

2)当我按Alt-Tab时,叠加窗口出现在窗口列表中,这非常难看。

基本上我需要一个“子窗口”的概念,并且所有意图和目的的窗口都显示为另一个窗口的一部分。 UserControls对我不起作用,因为WindowsFormsHost始终在其上绘制。

任何想法?


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

谢谢你们的回答。

我尝试了ChildWindow方法,而WindowsFormsHost元素仍然排在首位。据我了解,只有一个真正的窗口可以在WindowsFormsHost之上绘制,同一窗口中的任何内容都将位于WindowsFormsHost之下。

具有WindowsFormsHost的元素仍会在WinForms组件下绘制,它们总是被绘制在顶部,这似乎是不可协商的...

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

5 个答案:

答案 0 :(得分:36)

我使用Popup而非透明Window

解决了这个问题

<强>更新

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

AirspacePopup做什么

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

下面是一个示例,其中AirspacePopup用于在Ellipse控件(实际上是WinForms控件)之上绘制WebBrowser,但它也能正常工作任何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
}

答案 1 :(得分:3)

经过大量测试后,我们找到了不同的解决方案:

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

    优点:
    • 据我所知,效果很好(没有测试过那么多,但看起来很像)
    • 性能打击似乎很低。

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

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

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

    缺点:
    • 表现不佳。 (据我所知,比微软.DwayneNeed更大)

  3. Fredrik Hedblad的空域警告(https://stackoverflow.com/a/6452940/4870255

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

    缺点:
    • 全屏显示底部的一部分缺失。
    • Multimonitor-problems:当从一个窗口移动到另一个窗口时,叠加层不会完全覆盖在winformshost窗口上。
  4. 对我来说,最好的解决方案是使用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. 添加
      <interop:AirspaceDecorator AirspaceMode="Redirect" Background="White" IsInputRedirectionEnabled="True" IsOutputRedirectionEnabled="True">
      <WindowsFormsHost x:Name="windowsFormsHost1" Visibility="Visible" />
      </interop:AirspaceDecorator>
      到您的网格。
    8. 示例:

      <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>

答案 2 :(得分:0)

您可以在WindowsFormsHost中执行“覆盖”部分。因此,您可以拥有一个子元素,该元素应该位于托管元素中的其他内容之上。

答案 3 :(得分:0)

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

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

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

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

希望这有帮助,

答案 4 :(得分:0)

我建议使用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