Окно "на рабочем столе"

Я использовал Rainlendar в течение некоторого времени, и я заметил, что у него есть возможность поместить окно "на рабочий стол". Это как окно bottomMost (в отличие от самого верхнего).

Как я могу сделать это в WPF-приложении?

Спасибо

Ответы

Ответ 1

Мой ответ в терминах Win32 API, не специфичный для WPF (и, вероятно, требующий P/Invoke из С#):

Rainlendar имеет два варианта:

  • "На рабочем столе" он становится дочерним элементом окна рабочего стола Explorer ( "Менеджер программ" ). Вы можете достичь этого с помощью SetParent API.
  • "Внизу" - это то, что вы описываете - его окна остаются внизу Z-порядка, прямо перед рабочим столом. Достаточно легко положить их туда, чтобы начать (см. SetWindowPos) - трюк заключается в том, чтобы остановить их при выходе на передний план при нажатии. Я бы предложил обработать сообщение WM_WINDOWPOSCHANGING.

Ответ 2

Это то, что я использовал, поэтому окно всегда "снизу":

   using System;
    using System.Runtime.InteropServices;
    using System.Windows;
    using System.Windows.Interop;

...

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

const UInt32 SWP_NOSIZE = 0x0001;
const UInt32 SWP_NOMOVE = 0x0002;
const UInt32 SWP_NOACTIVATE = 0x0010;

static readonly IntPtr HWND_BOTTOM = new IntPtr(1);

public static void SetBottom(Window window)
{
    IntPtr hWnd = new WindowInteropHelper(window).Handle;
    SetWindowPos(hWnd, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE);
}

Ответ 3

Предупреждение Принятый ответ предполагает, что вы вызываете SetParent для создания дочернего элемента рабочего стола. Если вы это сделаете, вы заставите Win32 Window Manager синхронизировать входную очередь рабочего стола с вашим дочерним окном, это плохо - Раймонд Чен объясняет, почему. В принципе, если ваше окно зависает или блокируется (скажем, с помощью MessageBox), вы заблокируете свой рабочий стол.

Ответ 4

Я пытался сделать то же самое... я использовал много идей arround, но я смог это и предотвратить мерцание.

Мне удалось переопределить WndProc, использовать один setwindowpos раньше, чтобы поместить его в фоновом режиме, а другой, чтобы предотвратить его получение...

    const UInt32 SWP_NOSIZE = 0x0001;
    const UInt32 SWP_NOMOVE = 0x0002;
    const UInt32 SWP_NOACTIVATE = 0x0010;
    const UInt32 SWP_NOZORDER = 0x0004;
    const int WM_ACTIVATEAPP = 0x001C;
    const int WM_ACTIVATE = 0x0006;
    const int WM_SETFOCUS = 0x0007;
    static readonly IntPtr HWND_BOTTOM = new IntPtr(1);
    const int WM_WINDOWPOSCHANGING = 0x0046;

    [DllImport("user32.dll")]
    static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X,
       int Y, int cx, int cy, uint uFlags);
    [DllImport("user32.dll")]
    static extern IntPtr DeferWindowPos(IntPtr hWinPosInfo, IntPtr hWnd,
       IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags);
    [DllImport("user32.dll")]
    static extern IntPtr BeginDeferWindowPos(int nNumWindows);
    [DllImport("user32.dll")]
    static extern bool EndDeferWindowPos(IntPtr hWinPosInfo);

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        IntPtr hWnd = new WindowInteropHelper(this).Handle;
        SetWindowPos(hWnd, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE);

        IntPtr windowHandle = (new WindowInteropHelper(this)).Handle;
        HwndSource src = HwndSource.FromHwnd(windowHandle);
        src.AddHook(new HwndSourceHook(WndProc));
    }

    private IntPtr WndProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
    {
        if (msg == WM_SETFOCUS)
        {
            IntPtr hWnd = new WindowInteropHelper(this).Handle;
            SetWindowPos(hWnd, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE);
            handled = true;
        }
        return IntPtr.Zero;
    }

    private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
    {
        IntPtr windowHandle = (new WindowInteropHelper(this)).Handle;
        HwndSource src = HwndSource.FromHwnd(windowHandle);
        src.RemoveHook(new HwndSourceHook(this.WndProc));
    }

Ответ 5

Версия OnDesktop, в которой я использую:

[DllImport("user32.dll")]
static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
[DllImport("user32.dll", SetLastError = true)]
static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

public static void SetOnDesktop(Window window)
{
    IntPtr hWnd = new WindowInteropHelper(window).Handle;         
    IntPtr hWndProgMan = FindWindow("Progman", "Program Manager");
    SetParent(hWnd, hWndProgMan);
}

У меня возникли проблемы с поиском окна Program Manager, но Киммо, создатель Rainlendar дал мне ссылку на код:

http://www.ipi.fi/~rainy/legacy.html

Если кому-то нужно больше деталей, просто посмотрите в библиотеке /rainwindow.cpp для функции SetWindowZPos.

Ответ 6

Вложенная версия версии ответа @HrejWaltz:

Обновление (12/28/2016)

public class WindowSinker
{
    #region Properties

    const UInt32 SWP_NOSIZE = 0x0001;
    const UInt32 SWP_NOMOVE = 0x0002;
    const UInt32 SWP_NOACTIVATE = 0x0010;
    const UInt32 SWP_NOZORDER = 0x0004;
    const int WM_ACTIVATEAPP = 0x001C;
    const int WM_ACTIVATE = 0x0006;
    const int WM_SETFOCUS = 0x0007;
    const int WM_WINDOWPOSCHANGING = 0x0046;

    static readonly IntPtr HWND_BOTTOM = new IntPtr(1);

    Window Window = null;

    #endregion

    #region WindowSinker

    public WindowSinker(Window Window)
    {
        this.Window = Window;
    }

    #endregion

    #region Methods

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

    [DllImport("user32.dll")]
    static extern IntPtr DeferWindowPos(IntPtr hWinPosInfo, IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags);

    [DllImport("user32.dll")]
    static extern IntPtr BeginDeferWindowPos(int nNumWindows);

    [DllImport("user32.dll")]
    static extern bool EndDeferWindowPos(IntPtr hWinPosInfo);

    void OnClosing(object sender, System.ComponentModel.CancelEventArgs e)
    {
        var Handle = (new WindowInteropHelper(Window)).Handle;

        var Source = HwndSource.FromHwnd(Handle);
        Source.RemoveHook(new HwndSourceHook(WndProc));
    }

    void OnLoaded(object sender, RoutedEventArgs e)
    {
        var Hwnd = new WindowInteropHelper(Window).Handle;
        SetWindowPos(Hwnd, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE);

        var Handle = (new WindowInteropHelper(Window)).Handle;

        var Source = HwndSource.FromHwnd(Handle);
        Source.AddHook(new HwndSourceHook(WndProc));
    }

    IntPtr WndProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
    {
        if (msg == WM_SETFOCUS)
        {
            hWnd = new WindowInteropHelper(Window).Handle;
            SetWindowPos(hWnd, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE);
            handled = true;
        }
        return IntPtr.Zero;
    }

    public void Sink()
    {
        Window.Loaded += OnLoaded;
        Window.Closing += OnClosing;
    }

    public void Unsink()
    {
        Window.Loaded -= OnLoaded;
        Window.Closing -= OnClosing;
    }

    #endregion
}

public static class WindowExtensions
{
    #region Always On Bottom

    public static readonly DependencyProperty SinkerProperty = DependencyProperty.RegisterAttached("Sinker", typeof(WindowSinker), typeof(WindowExtensions), new UIPropertyMetadata(null));
    public static WindowSinker GetSinker(DependencyObject obj)
    {
        return (WindowSinker)obj.GetValue(SinkerProperty);
    }
    public static void SetSinker(DependencyObject obj, WindowSinker value)
    {
        obj.SetValue(SinkerProperty, value);
    }

    public static readonly DependencyProperty AlwaysOnBottomProperty = DependencyProperty.RegisterAttached("AlwaysOnBottom", typeof(bool), typeof(WindowExtensions), new UIPropertyMetadata(false, OnAlwaysOnBottomChanged));
    public static bool GetAlwaysOnBottom(DependencyObject obj)
    {
        return (bool)obj.GetValue(AlwaysOnBottomProperty);
    }
    public static void SetAlwaysOnBottom(DependencyObject obj, bool value)
    {
        obj.SetValue(AlwaysOnBottomProperty, value);
    }
    static void OnAlwaysOnBottomChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        var Window = sender as Window;
        if (Window != null)
        {
            if ((bool)e.NewValue)
            {
                var Sinker = new WindowSinker(Window);
                Sinker.Sink();
                SetSinker(Window, Sinker);
            }
            else
            {
                var Sinker = GetSinker(Window);
                Sinker.Unsink();
                SetSinker(Window, null);
            }
        }
    }

    #endregion
}

Ответ 7

Основываясь на ответах HrejWaltz и James M, я хочу предоставить модифицированное решение, которое перехватывает и модифицирует входящие сообщения WM_WINDOWPOSCHANGING, устанавливая флаг SWP_NOZORDER вместо вызова SetWindowPos каждый раз, когда WM_SETFOCUS сообщение WM_SETFOCUS.

Класс предлагает вложенные свойства для непосредственного добавления в окно WPF с помощью WindowSinker.AlwaysOnBottom="True".

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;

public class WindowSinker
{
    #region Windows API

    // ReSharper disable InconsistentNaming

    private const int WM_WINDOWPOSCHANGING = 0x0046;

    private const uint SWP_NOSIZE = 0x0001;
    private const uint SWP_NOMOVE = 0x0002;
    private const uint SWP_NOZORDER = 0x0004;
    private const uint SWP_NOACTIVATE = 0x0010;

    [StructLayout(LayoutKind.Sequential)]
    public struct WINDOWPOS
    {
        public IntPtr hwnd;
        public IntPtr hwndInsertAfter;
        public int x;
        public int y;
        public int cx;
        public int cy;
        public uint flags;
    }

    private static readonly IntPtr HWND_BOTTOM = new IntPtr(1);

    // ReSharper restore InconsistentNaming

    #endregion

    #region WindowSinker

    private readonly Window window;
    private bool disposed;

    public WindowSinker(Window window)
    {
        this.window = window;

        if (window.IsLoaded)
        {
            OnWindowLoaded(window, null);
        }
        else
        {
            window.Loaded += OnWindowLoaded;
        }

        window.Closing += OnWindowClosing;
    }

    protected virtual void Dispose(bool disposing)
    {
        if (disposed) return;

        window.Loaded -= OnWindowLoaded;
        window.Closing -= OnWindowClosing;

        disposed = true;
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    ~WindowSinker()
    {
        Dispose(false);
    }

    #endregion

    #region Event Handlers

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

    private void OnWindowLoaded(object sender, RoutedEventArgs e)
    {
        SetWindowPos(new WindowInteropHelper(window).Handle, HWND_BOTTOM, 0, 0, 0, 0,
            SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE);

        var source = HwndSource.FromHwnd(new WindowInteropHelper(window).Handle);
        source?.AddHook(WndProc);
    }

    private void OnWindowClosing(object sender, CancelEventArgs e)
    {
        var source = HwndSource.FromHwnd(new WindowInteropHelper(window).Handle);
        source?.RemoveHook(WndProc);
    }

    private IntPtr WndProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
    {
        if (msg == WM_WINDOWPOSCHANGING)
        {
            var windowPos = Marshal.PtrToStructure<WINDOWPOS>(lParam);
            windowPos.flags |= SWP_NOZORDER;
            Marshal.StructureToPtr(windowPos, lParam, false);
        }

        return IntPtr.Zero;
    }

    #endregion

    #region Attached Properties

    private static readonly DependencyProperty SinkerProperty = DependencyProperty.RegisterAttached(
        "Sinker",
        typeof(WindowSinker),
        typeof(WindowSinker),
        null);

    public static readonly DependencyProperty AlwaysOnBottomProperty = DependencyProperty.RegisterAttached(
        "AlwaysOnBottom",
        typeof(bool),
        typeof(WindowSinker),
        new UIPropertyMetadata(false, OnAlwaysOnBottomChanged));

    public static WindowSinker GetSinker(DependencyObject d)
    {
        return (WindowSinker) d.GetValue(SinkerProperty);
    }

    private static void SetSinker(DependencyObject d, WindowSinker value)
    {
        d.SetValue(SinkerProperty, value);
    }

    public static bool GetAlwaysOnBottom(DependencyObject d)
    {
        return (bool) d.GetValue(AlwaysOnBottomProperty);
    }

    public static void SetAlwaysOnBottom(DependencyObject d, bool value)
    {
        d.SetValue(AlwaysOnBottomProperty, value);
    }

    private static void OnAlwaysOnBottomChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        if (sender is Window window)
        {
            if ((bool) e.NewValue)
            {
                SetSinker(window, new WindowSinker(window));
            }
            else
            {
                GetSinker(window)?.Dispose();
                SetSinker(window, null);
            }
        }
    }

    #endregion
}