Windows 10的虚拟桌面

本文介绍了Windows 10的虚拟桌面功能,包括快捷键、IVirtualDesktopManager接口在C#中的应用,以及与传统Win32 Desktop API的区别。通过示例代码展示了如何在C#中实现桌面管理,探讨了虚拟桌面的隔离性和不采用Win32 Desktop的原因。
部署运行你感兴趣的模型镜像

简介

Windows 10发布后,Windows系统外壳终于内置了虚拟桌面功能。虽然该功能有些跟随Mac多重桌面的嫌疑,但它的确极大方便了桌面组织。

首先,本文将介绍一些使用虚拟桌面的快捷键。然后,讨论使用IVirtualDesktopManager接口,在C#里实现追随虚拟桌面的代码。最后,谈谈Windows 10虚拟桌面和Windows Desktop API的区别。

使用虚拟桌面的快捷键

新建桌面:Ctrl+Win + D
删除桌面:Ctrl+Win + F4
上个桌面:Ctrl+Win + <-
下个桌面:Ctrl+Win + ->

如果记不清快捷键也没关系,可以用Win+Tab还呼出桌面管理视图。或者,用鼠标点击任务栏上的“任务视图”图标:
任务视图图标
出现桌面管理视图后,用鼠标新建,删除,切换,以及移动窗口到其他桌面。
桌面管理视图

IVirtualDesktopManager接口

Windows SDK Support Team博客中,有一篇Chris Lewis1的文章《Virtual Desktop Switching in Windows 10》。它介绍了如何在C#里利用Windows Shell的VirtualDeskTopManager2,以及它的COM接口IVirtualDeskTopManager3来将当前窗口移动到活跃桌面的方法。这里给出简单的c#实现。

首先,在Visual Studio中新建一个WinForm项目,选用DotNet Framework4.0或以上都可以。
然后,在Form1.cs文件中,Form1类的后面,贴入如下COM调用声明:

    public partial class Form1 : Form
    {
        //...
    }

    [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("a5cd92ff-29be-454c-8d04-d82879fb3f1b")]
    [System.Security.SuppressUnmanagedCodeSecurity]
    public interface IVirtualDesktopManager
    {
        bool IsWindowOnCurrentVirtualDesktop([In] IntPtr TopLevelWindow);
        Guid GetWindowDesktopId([In] IntPtr TopLevelWindow);
        void MoveWindowToDesktop([In] IntPtr TopLevelWindow, ref Guid CurrentDesktop);
    }

    [ComImport, Guid("aa509086-5ca9-4c25-8f95-589d3c07b48a")]
    public class CVirtualDesktopManager
    {
    }

接口和COM class的GUID可以在Shobjidl.h头文件中找到。
使用IVirtualDesktopManager接口,我们可以

  1. 判断是否当前窗口位于活动桌面上,
  2. 获取某个顶级窗口所在桌面的桌面标志,
  3. 将某个顶级窗口移动到目标桌面上。

在Form1的构造函数下,我们启用一个定时器。定时器每一秒将检查是否当前窗口位于活动桌面下。
如果用户切换到另外一个虚拟桌面,我们将尝试获取该活动虚拟桌面的标志,并将当前窗口移动到活动桌面下。

using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            this.timer.Tick += delegate
            {
                if (!virtualDesktopManager.IsWindowOnCurrentVirtualDesktop(this.Handle)) // 如果当前窗口不在活动桌面(Active Desktop)下
                {
                    Guid activeVirtualDesktop;
                    using (var form = new Form() { Size = new Size(50, 50), Location = new Point(-100, -100) })
                    {
                        form.Show(null); // 创建一个‘隐藏窗口’,该窗口将默认显示在活动桌面下。
                        activeVirtualDesktop = virtualDesktopManager.GetWindowDesktopId(form.Handle); // 获取该‘隐藏窗口’的桌面标志
                        this.Text = activeVirtualDesktop.ToString();
                    }
                    if (activeVirtualDesktop != Guid.Empty)
                    {
                        virtualDesktopManager.MoveWindowToDesktop(this.Handle, ref activeVirtualDesktop); // 将当前窗口移动到活动桌面下。
                    }
                }
            };

            try
            {
                virtualDesktopManager = new CVirtualDesktopManager() as IVirtualDesktopManager;
                this.timer.Start(); // 如果virtualDesktopManager顺利创建,开始定时器
            }
            catch
            { 
            }
        }
        readonly IVirtualDesktopManager virtualDesktopManager;
        readonly Timer timer = new Timer() { Interval = 1000 };
    }

    [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("a5cd92ff-29be-454c-8d04-d82879fb3f1b")]
    [System.Security.SuppressUnmanagedCodeSecurity]
    public interface IVirtualDesktopManager
    {
        bool IsWindowOnCurrentVirtualDesktop([In] IntPtr TopLevelWindow);
        Guid GetWindowDesktopId([In] IntPtr TopLevelWindow);
        void MoveWindowToDesktop([In] IntPtr TopLevelWindow, ref Guid CurrentDesktop);
    }

    [ComImport, Guid("aa509086-5ca9-4c25-8f95-589d3c07b48a")]
    public class CVirtualDesktopManager
    {
    }
}

编译成功后,运行。用快捷键或“任务视图图标”创建新的虚拟桌面。约一秒内,测试窗口将自动移到当下的虚拟桌面中。

Windows Desktop API

在回答“曹同学”的帖子4《c#的windows服务程序中调用dll(c++编写),在dll中调用了一个exe外部文件》中,我简单地介绍了Windows系统的Session,WindowStation和Desktop的关系。

但是,我的回答中有错误的地方。实际上Windows 10里的快速虚拟桌面,不等于传统Win32 API里的Desktop。Windows 10里的快速虚拟桌面,不是用User32里的CreateDesktop API5实现的。要实现传统意义上的User32 Desktop创建和切换,有兴趣的朋友,可以下载和运行Mark Russinovich的Desktop.exe工具6

这里我给出了检查当前程序的会话ID,当前WindowStation和当前Win32桌面名字的C#实现:

using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;


static class Program
{
    static void Main()
    {
        var hWINSTA = GetProcessWindowStation();
        var stationName = GetUserObjectName(hWINSTA); // WinSta0

        var hDESK = GetThreadDesktop(GetCurrentThreadId());
        var desktopName = GetUserObjectName(hDESK); // Default

        var currentTime = DateTime.Now.ToString("s");
        var sessionId = Process.GetCurrentProcess().SessionId;

        var detail = $"[{currentTime}] 会话:{sessionId}, 当前用户:{Environment.UserName}, Station:{stationName}, 桌面:{desktopName}";
        Console.WriteLine(detail);

        if (File.Exists(@"c:\temp\log.txt"))
        {
            File.AppendAllLines(@"c:\temp\log.txt", new[] { detail }); // 写文件里。当该程序在Windows服务中调用时,方便检查。
        }
    }

    static string GetUserObjectName(IntPtr hObject)
    {
        const int UOI_NAME = 2;
        var bufferLength = 128;
        var buffer = new byte[bufferLength];
        if (!GetUserObjectInformationW(hObject, UOI_NAME, buffer, bufferLength, ref bufferLength))
        {
            throw new Win32Exception();
        }
        return Encoding.Unicode.GetString(buffer, 0, bufferLength).TrimEnd('\0');
    }

    [DllImport("User32", SetLastError = true)]
    extern static bool GetUserObjectInformationW(IntPtr hObj, int nIndex, byte[] pvInfo, int nLength, ref int lpnLengthNeeded);

    [DllImport("User32", SetLastError = true)]
    extern static IntPtr GetProcessWindowStation();

    [DllImport("User32", SetLastError = true)]
    extern static IntPtr GetThreadDesktop(int dwThreadId);

    [DllImport("Kernel32", SetLastError = true)]
    extern static int GetCurrentThreadId();
}

当该程序在Windows 10的虚拟桌面下运行时,将汇报当前桌面名字为’Default’。
如果用Russinovich的Desktop.exe工具,切换Win32桌面,那么该程序将汇报桌面名字为’SysInternals Desktop 1‘等等。

Win32的Desktop的隔离性比Windows 10的虚拟桌面强。Win32的Desktop界定了Windows消息,菜单,和钩子的范围7。也就是说,SendMessage和PostMessage不能跨越Win32 Desktop。Windows相关的钩子,也只能在当前Win32 Desktop内有效。

我大胆猜测这也是Windows 10的虚拟桌面不采用Win32 Desktop实现的原因。

总结

本文简单介绍了Windows 10的新功能“虚拟桌面”。并解释了它没有采用传统的Win32 Desktop实现。它的实现是基于Windows Shell新增的IVirtualDeskTopManager接口。

注脚


  1. https://blogs.msdn.microsoft.com/winsdk/2015/09/10/virtual-desktop-switching-in-windows-10/ ↩︎

  2. https://docs.microsoft.com/en-us/windows/win32/shell/virtualdesktopmanager ↩︎

  3. https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nn-shobjidl_core-ivirtualdesktopmanager ↩︎

  4. https://bbs.youkuaiyun.com/topics/395462438 ↩︎

  5. https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-createdesktopa ↩︎

  6. https://docs.microsoft.com/en-us/sysinternals/downloads/desktops ↩︎

  7. https://docs.microsoft.com/en-us/windows/win32/winstation/desktops ↩︎

您可能感兴趣的与本文相关的镜像

Stable-Diffusion-3.5

Stable-Diffusion-3.5

图片生成
Stable-Diffusion

Stable Diffusion 3.5 (SD 3.5) 是由 Stability AI 推出的新一代文本到图像生成模型,相比 3.0 版本,它提升了图像质量、运行速度和硬件效率

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值