我有一个WPF窗口,其中包含一个WindowsFormsHost
元素。我需要在这个元素的顶部绘制东西,但WindowsFormsHost
的性质意味着它总是在绘图堆的顶部。由于我不能在WindowsFormsHost
组件顶部的同一个WPF窗口中绘制,我可以在它的顶部覆盖另一个窗口吗?
我已经用一种基本的方式尝试过了,但我有一些问题:
1) 我无法阻止其他应用程序的窗口进入主窗口和覆盖窗口之间。
2) 当我按下Alt Tab键时,覆盖窗口会出现在窗口列表中,这非常难看。
基本上,我需要一个"子窗口"的概念,这个窗口在所有意图和目的上都是作为另一个窗口的一部分出现的。UserControls对我不起作用,因为WindowsFormsHost
将始终绘制在它上面。
有什么想法吗?
更新[11年5月23日10:13]
谢谢你们两个的回答。
我已经尝试了ChildWindow
方法,但WindowsFormsHost
元素仍然处于领先地位。据我所知,只有一个真正的窗口才能在WindowsFormsHost
的顶部绘制,同一窗口中的任何东西都会在WindowsFormsHost
的下面。
具有WindowsFormsHost
的元素仍然会绘制在WinForms组件下,它们总是绘制在顶部,这似乎是不可协商的。。。
我想我正在寻找的是一种将外部窗口作为主窗口的一部分进行对接的方法。在Mac上,有一个真正的"儿童窗口"的概念,我正在寻找类似的东西。
我已经通过使用Popup
而不是透明的Window
来解决这个问题
更新
我最终得到了一个子类Popup
,我称之为AirspacePopup
。
什么是AirspacePopup
- 遵循其
PlacementTarget
- 并不总是在顶部,而是相对于要放置它的
Window
放置。这个解决方案来自Chris Cavanagh的博客 - 允许移动到"屏幕外"。这是通过剪裁
Popup
并在其子级移动离开屏幕时设置负Margin
来实现的。这个解决方案来自Rick Sladkey的StackOverflow帖子
这里有一个例子,其中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");
}
}
空域弹出
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
}
经过大量测试不同的解决方案:
- Microsoft.DwayneNeed(https://microsoftdwayneneed.codeplex.com/)
优点:- 据我所知,效果很好(没有测试那么多,但看起来很像)
- 演出的成功率似乎很低
缺点:- 相对较大的库
- AirspaceFixer(https://github.com/chris84948/AirspaceFixer)
优点:- 运行良好(没有测试那么多,但看起来很像)
缺点:- 有一个小的性能打击。(据我所知,比Microsoft.DwayneNeed更大)
- Fredrik Hedblad的AirspacePopup(https://stackoverflow.com/a/6452940/4870255)
优点:- 简单代码。多数情况下:D
缺点:- 在全屏显示中,底部缺少一部分
- 多显示器问题:当从一个窗口移动到另一个窗口时,覆盖并没有完全覆盖winformshost窗口
对我来说,最好的解决方案是使用Microsoft.DwayneNeed
因为要让它发挥作用并不是一件容易的事,她只是一个小教程:
- 转到https://microsoftdwayneneed.codeplex.com/SourceControl/latest
- 单击下载
- 打开Microsoft.DwayneNeed.sln
- 编译它
- 请参考创建的Debug或Releas文件夹中的Microsoft.DwayneNeed.dll
- 添加
xmlns:interop="clr namespace:Microsoft.DwayneNeed.interop;assembly=Microsoft.DwayneNeed
到您的窗口 - 添加
<interop:AirspaceDecorator AirspaceMode="Redirect"Background="White"IsInputRedirectionEnabled="True"IsOutputRedirectionEnabled="True"><WindowsFormsHost x:Name="windowsFormsHost1"Visibility="Visible"/><interop:AirspaceDecorator>
到您的网格
示例:
<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>
在使用CAL或事件聚合器的典型MVP应用程序中,您可能需要创建另一个窗口并将其命名为popup或child(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();
}
}
希望这有帮助,
您可以在WindowsFormsHost
中进行"覆盖"部分。因此,您可以有一个子元素,它应该位于托管元素中的其他内容之上。
我建议使用MahApps库。
我已经为这个问题挣扎了一段时间,发现MahApps库在我没有任何配置的情况下解决了空域问题。有关更多信息,请参阅他们的常见问题解答:
https://github.com/MahApps/MahApps.Metro/wiki/FAQ#1-为什么某某winforms控件不可见或不呈现为什么网络浏览器或其他控件覆盖我的弹出按钮或其他控件空域