Подтвердить что ты не робот

Могу ли я наложить окно WPF поверх другого?

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

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

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

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

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

Любые идеи?


Обновить [23 мая 2011 в 10:13]

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

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

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

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

4b9b3361

Ответ 1

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

Обновление

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

Что AirspacePopup делает

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

Вот пример, где 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
}

Ответ 2

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

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

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

    Минусы:
    • Релятивно большая библиотека.

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

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

    Минусы:
    • Небольшой удар производительности. Насколько я могу судить по Microsoft.DwayneNeed)

  • AirspacePopup от Fredrik Hedblad (fooobar.com/questions/179509/...)

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

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

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

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

  • Перейдите к https://microsoftdwayneneed.codeplex.com/SourceControl/latest
  • Нажмите "Загрузить".
  • Откройте файл Microsoft.DwayneNeed.sln
  • Скомпилируйте его.
  • Ссылка Microsoft.DwayneNeed.dll в созданной папке Debug или Release.
  • Добавить
    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>

Ответ 3

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

Ответ 4

В типичном приложении MVP с использованием агрегатора CAL или Event вам может потребоваться создать другое окно и называть его всплывающим или дочерним (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();
    }
}

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

Ответ 5

Я бы предложил использовать библиотеку 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