Могу ли я наложить окно WPF поверх другого? - PullRequest
29 голосов
/ 22 мая 2011

У меня есть окно WPF, которое содержит элемент WindowsFormsHost. Мне нужно рисовать вещи поверх этого элемента, но характер WindowsFormsHost означает, что он всегда находится на вершине стопки чертежей. Поскольку я не могу рисовать в том же окне WPF поверх компонента WindowsFormsHost, могу ли я наложить поверх него еще одно окно?

Я пробовал это в зачаточном состоянии, но у меня есть несколько проблем:

1) Я не могу остановить окна других приложений, находящихся между главным окном и окном наложения.

2) Когда я нажимаю Alt-Tab, в списке окон появляется оверлейное окно, что довольно уродливо.

По сути, мне нужна концепция «дочернего окна» и окна, которое во всех смыслах и целях появляется как часть другого окна. UserControls не будут работать для меня, так как WindowsFormsHost будет всегда рисовать поверх него.

Есть идеи?


Обновление [23 мая '11 в 10:13]

Спасибо вам обоим за ответы.

Я попробовал подход ChildWindow, и элемент WindowsFormsHost все еще рисует сверху. Насколько я понимаю, только настоящее окно может рисовать поверх WindowsFormsHost, все, что в этом же окне, будет идти под WindowsFormsHost.

Элемент с WindowsFormsHost будет по-прежнему рисоваться под компонентом WinForms, они всегда рисуются сверху, и это кажется не подлежащим обсуждению ...

Я думаю, что я ищу способ закрепить внешнее окно, чтобы оно действовало как часть главного окна. На Mac есть понятие настоящего «дочернего окна», я ищу что-то подобное.

Ответы [ 5 ]

36 голосов
/ 23 июня 2011

Я обошел эту проблему, используя Popup вместо прозрачного Window

Обновление

Я получил подкласс Popup который я называю AirspacePopup.

Что AirspacePopup делает

  • Следуйте его PlacementTarget.
  • Не всегда сверху, но помещеноотносительно Window, в котором он находится.Это решение взято из блога Криса Кавана .
  • Разрешено перемещать "за пределы экрана".Это достигается путем отсечения Popup и установки отрицательного значения Margin на его дочерний элемент после его перемещения за пределы экрана.Это решение взято из этого сообщения StackOverflow от Рика Сладкей

Вот пример, где AirspacePopup используется для рисования Ellipse поверхWebBrowser Элемент управления (который на самом деле является элементом управления WinForms), но он будет работать так же хорошо с любым 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
}
3 голосов
/ 29 апреля 2017

После многих испытаний различных решений:

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

    Плюсы:
    • Работает хорошо, насколько ямогу сказать (не тестировал так много, но это выглядело так)
    • Удар по производительности кажется довольно низким.

    Минусы:
    • Относительно большойбиблиотека.
  2. AirspaceFixer (https://github.com/chris84948/AirspaceFixer)

    Плюсы:
    • Работает хорошо (не тестировал так много, новыглядело так)

    Минусы:
    • Небольшой удар по производительности. (Насколько я могу судить больше, чем с Microsoft.DwayneNeed)
  3. AirspacePopup от Фредрика Хедблада (https://stackoverflow.com/a/6452940/4870255)

    Плюсы:
    • Простой код. В основном: D

    Минусы:
    • В полноэкранном режиме отсутствует часть нижней части.
    • Проблемы с несколькими мониторами: при переходе из одного окна в другое оверлей не полностью над окном winformshost.

Для меня лучшим решением было использование Microsoft.DwayneNeed.(Я использовал его, чтобы получить CefSharp-Winforms в WPF-приложении)

Поскольку заставить его работать не так просто, это небольшой учебник:

  1. Перейти к https://microsoftdwayneneed.codeplex.com/SourceControl/latest
  2. Нажмите кнопку загрузки.
  3. Откройте Microsoft.DwayneNeed.sln
  4. Скомпилируйте его.
  5. Ссылка на Microsoft.DwayneNeed.dll в созданной папке Debug или Releas.
  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>
    в свою сетку.

Пример:

<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>
0 голосов
/ 26 июля 2016

Я бы предложил использовать библиотеку MahApps .

Я некоторое время боролся с этой проблемой и обнаружил, что библиотека MahApps исправила проблему воздушного пространства без какой-либо конфигурации с моей стороны. Для получения дополнительной информации см. Их FAQ по этому вопросу:

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

0 голосов
/ 22 мая 2011

В типичном приложении MVP, использующем CAL или агрегатор событий, вам, возможно, придется создать другое окно и назвать его всплывающим или дочерним (ChildWindow.XAML) и иметь метод get в (ChildWindow.XAML.CS), например

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

Имейте свойство в maniwindow, которое может при необходимости возвращать тип Childwindow. как

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

Надеюсь, это поможет,

0 голосов
/ 22 мая 2011

Вы можете выполнить наложенную часть в WindowsFormsHost. Таким образом, у вас может быть дочерний элемент, который должен быть поверх другого содержимого размещенного элемента.

...