WPF places the window under the desktop (can be used for dynamic desktop)

WPF places the window under the desktop (can be used for dynamic desktop)

Let's look at the effect first:

The interface elements are very simple. Just a Button button, and then write a timer to regularly update the contents of the Button to the current time. The following describes the principle and interface composition.

Window introduction

Windows is everywhere in the operating system. Maybe this is also the origin of the system name, including the folder, desktop and right-click menu you see. These are composed of interfaces. So many windows need a reasonable display, so we need to use our layer relationship, such as who displays the first and who displays the second forms.

VS provides us with a tool called Spy + + to find and view window information. In the tool:

After opening, all the window information of the current system is shown here. You can also click the search tool in the red box to view the window information you want to know:

To demonstrate how to find a window, click the find window button in the red box above, and choose one of the two to pop up the following window:

Then you can click the control in the red area and drag it to the information window you want to get, and you can see the details of the current window, including the handle, title and class of the window.

For example, if I directly drag the icon to the desktop, I can see that this is the information displayed on the desktop:

Here, we close this window, return to the main interface of Spy + +, and drag it to the bottom:

As you can see, Progman Manager is the parent window of the desktop window. The front small window icon is gray, indicating that the window is hidden (the child window has the same display level as the parent window).

Principle operation

Now, we just need to put our interface, that is, under the} Program Manager, and then adjust its display order appropriately, but it is difficult for us to operate this part. Another way is to send a special message to the window to give us room for operation.

Just send a message 0x52C to the Program Manager window to split the Program Manager into multiple windows, namely, the Program Manager window and two WorkerW windows.

Here is what this message looks like after it has been sent:

You can see that there is nothing under the Program Manager, and all the contents are transferred to the first WokerW window. At this time, we only need to hang our window at the bottom of the Program Manager window to have the same display level as it (the window is displayed from bottom to top, so the Program Manager is displayed at the bottom). However, it should be noted that, There is another WorkerW window between the Program Manager and the first WorkerW window. In my system, it is hidden by default. In order to ensure consistent effects, we need to hide it manually.

Specific operation

The form part is very simple. Create a form with a button, set some basic window information, and cut a code:

Paste the code part (copy the code and pay attention to your namespace):

<Window
    x:Class="BehindTheDesktop.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:local="clr-namespace:BehindTheDesktop"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Title="MainWindow"
    Width="800"
    Height="450"
    AllowsTransparency="True"
    Background="Transparent"
    ResizeMode="NoResize"
    WindowStyle="None"
    mc:Ignorable="d">

    <Button
        x:Name="btnTime"
        Click="Button_Click"
        Content="start"
        FontSize="300"
        Foreground="WhiteSmoke" />

</Window>

Then, it is the part of the background code. To obtain window information, find the window and send the message 0x52C to the desktop window, you need to use some Win32 API s, which are listed directly below.

    //Win32 method
    public static class Win32Func
    {
        [DllImport("user32.dll")]
        public static extern IntPtr FindWindow(string className, string winName);

        [DllImport("user32.dll")]
        public static extern IntPtr SendMessageTimeout(IntPtr hwnd, uint msg, IntPtr wParam, IntPtr lParam, uint fuFlage, uint timeout, IntPtr result);

        //Delegate lookup logic for lookup window
        public delegate bool EnumWindowsProc(IntPtr hwnd, IntPtr lParam);
        [DllImport("user32.dll")]
        public static extern bool EnumWindows(EnumWindowsProc proc, IntPtr lParam);

        [DllImport("user32.dll")]
        public static extern IntPtr FindWindowEx(IntPtr hwndParent, IntPtr hwndChildAfter, string className, string winName);

        [DllImport("user32.dll")]
        public static extern bool ShowWindow(IntPtr hwnd, int nCmdShow);

        [DllImport("user32.dll")]
        public static extern IntPtr SetParent(IntPtr hwnd, IntPtr parentHwnd);
    }

The following code is the method to send a message to the window and hide the intermediate WorkerW window:

        /// <summary>
        ///Send message to desktop
        /// </summary>
        public void SendMsgToProgman()
        {
            // The desktop window handle, defined externally, is used to put our own window as a child window later
            programHandle = Win32Func.FindWindow("Progman", null);

            IntPtr result = IntPtr.Zero;
            // Send a message of message 0x52c to the Program Manager window with the timeout set to 2 seconds
            Win32Func.SendMessageTimeout(programHandle, 0x52c, IntPtr.Zero, IntPtr.Zero, 0, 2, result);

            // Traverse top-level window
            Win32Func.EnumWindows((hwnd, lParam) =>
            {
                // Find the first WorkerW window with a child window SHELLDLL_DefView, so find the sub window first
                if (Win32Func.FindWindowEx(hwnd, IntPtr.Zero, "SHELLDLL_DefView", null) != IntPtr.Zero)
                {
                    // Find the of the current first WorkerW window, the next window, and the second WorkerW window.
                    IntPtr tempHwnd = Win32Func.FindWindowEx(IntPtr.Zero, hwnd, "WorkerW", null);

                    // Hide the second WorkerW window
                    Win32Func.ShowWindow(tempHwnd, 0);
                }
                return true;
            }, IntPtr.Zero);
        }

Then it is called in the constructor of MainWindow:

        public MainWindow()
        {
            InitializeComponent();

            //Send message to desktop
            SendMsgToProgman();
        }

Finally, paste the Click method of the page Button:

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            // Set the current window as a child window of Program Manager
            Win32Func.SetParent(new WindowInteropHelper(this).Handle, programHandle);

            //Set the button background color to transparent
            btnTime.Background = new SolidColorBrush(Colors.Transparent);
            //Set the width and height of the current interface. Because I have two screens, I can't set the full screen. I set the interface in this way
            this.Width = 1920;
            this.Height = 1080;
            this.Left = 0;
            this.Top = 0;

            //Start the timer to update the contents of the Button button
        }

Keywords: C# Windows WPF microsoft

Added by newmember on Tue, 25 Jan 2022 05:12:10 +0200