Windows 10中虚拟桌面的程序化控制10

发布于 2025-01-24 07:49:47 字数 219 浏览 0 评论 0原文

我喜欢Windows 10现在可以支持内置的虚拟桌面,但是我有一些我想添加/修改的功能(例如,迫使窗口出现在所有台式机上,用热键启动任务视图, - Monitor桌面等。)

我已经搜索了应用程序和开发人员参考,以帮助我自定义桌面,但我没有运气。

我应该从哪里开始?我正在寻找Windows API函数(理想情况下,可以从C#应用程序中调用),这将使我可以编程访问操作虚拟桌面及其Windows。

I love that Windows 10 now has support for virtual desktops built in, but I have some features that I'd like to add/modify (e.g., force a window to appear on all desktops, launch the task view with a hotkey, have per-monitor desktops, etc.)

I have searched for applications and developer references to help me customize my desktops, but I have had no luck.

Where should I start? I am looking for Windows API functions (ideally, that are callable from a C# application) that will give me programmatic access to manipulate virtual desktops and the windows therein.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

路弥 2025-01-31 07:49:47

Windows SDK支持团队博客发布 c#c​​#c#demo to switch desktops 通过ivirtirualdesktopmanager:

[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("a5cd92ff-29be-454c-8d04-d82879fb3f1b")]
[System.Security.SuppressUnmanagedCodeSecurity]
public interface IVirtualDesktopManager
{
[PreserveSig]
int IsWindowOnCurrentVirtualDesktop(
    [In] IntPtr TopLevelWindow,
    [Out] out int OnCurrentDesktop
    );
[PreserveSig]
int GetWindowDesktopId(
    [In] IntPtr TopLevelWindow,
    [Out] out Guid CurrentDesktop
    );

[PreserveSig]
int MoveWindowToDesktop(
    [In] IntPtr TopLevelWindow,
    [MarshalAs(UnmanagedType.LPStruct)]
    [In]Guid CurrentDesktop
    );
}

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

}
public class VirtualDesktopManager
{
    public VirtualDesktopManager()
    {
        cmanager = new CVirtualDesktopManager();
        manager = (IVirtualDesktopManager)cmanager;
    }
    ~VirtualDesktopManager()
    {
        manager = null;
        cmanager = null;
    }
    private CVirtualDesktopManager cmanager = null;
    private IVirtualDesktopManager manager;

    public bool IsWindowOnCurrentVirtualDesktop(IntPtr TopLevelWindow)
    {
        int result;
        int hr;
        if ((hr = manager.IsWindowOnCurrentVirtualDesktop(TopLevelWindow, out result)) != 0)
        {
            Marshal.ThrowExceptionForHR(hr);
        }
        return result != 0;
    }

    public Guid GetWindowDesktopId(IntPtr TopLevelWindow)
    {
        Guid result;
        int hr;
        if ((hr = manager.GetWindowDesktopId(TopLevelWindow, out result)) != 0)
        {
            Marshal.ThrowExceptionForHR(hr);
        }
        return result;
    }

    public void MoveWindowToDesktop(IntPtr TopLevelWindow, Guid CurrentDesktop)
    {
        int hr;
        if ((hr = manager.MoveWindowToDesktop(TopLevelWindow, CurrentDesktop)) != 0)
        {
            Marshal.ThrowExceptionForHR(hr);
        }
    }
}

it包括用于检测哪个桌面显示窗口的API,它可以切换并移动窗户A桌面。

The Windows SDK Support Team Blog posted a C# demo to switch Desktops via IVirtualDesktopManager:

[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("a5cd92ff-29be-454c-8d04-d82879fb3f1b")]
[System.Security.SuppressUnmanagedCodeSecurity]
public interface IVirtualDesktopManager
{
[PreserveSig]
int IsWindowOnCurrentVirtualDesktop(
    [In] IntPtr TopLevelWindow,
    [Out] out int OnCurrentDesktop
    );
[PreserveSig]
int GetWindowDesktopId(
    [In] IntPtr TopLevelWindow,
    [Out] out Guid CurrentDesktop
    );

[PreserveSig]
int MoveWindowToDesktop(
    [In] IntPtr TopLevelWindow,
    [MarshalAs(UnmanagedType.LPStruct)]
    [In]Guid CurrentDesktop
    );
}

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

}
public class VirtualDesktopManager
{
    public VirtualDesktopManager()
    {
        cmanager = new CVirtualDesktopManager();
        manager = (IVirtualDesktopManager)cmanager;
    }
    ~VirtualDesktopManager()
    {
        manager = null;
        cmanager = null;
    }
    private CVirtualDesktopManager cmanager = null;
    private IVirtualDesktopManager manager;

    public bool IsWindowOnCurrentVirtualDesktop(IntPtr TopLevelWindow)
    {
        int result;
        int hr;
        if ((hr = manager.IsWindowOnCurrentVirtualDesktop(TopLevelWindow, out result)) != 0)
        {
            Marshal.ThrowExceptionForHR(hr);
        }
        return result != 0;
    }

    public Guid GetWindowDesktopId(IntPtr TopLevelWindow)
    {
        Guid result;
        int hr;
        if ((hr = manager.GetWindowDesktopId(TopLevelWindow, out result)) != 0)
        {
            Marshal.ThrowExceptionForHR(hr);
        }
        return result;
    }

    public void MoveWindowToDesktop(IntPtr TopLevelWindow, Guid CurrentDesktop)
    {
        int hr;
        if ((hr = manager.MoveWindowToDesktop(TopLevelWindow, CurrentDesktop)) != 0)
        {
            Marshal.ThrowExceptionForHR(hr);
        }
    }
}

it includes the API to detect on which desktop the Window is shown and it can switch and move a Windows the a Desktop.

旧人哭 2025-01-31 07:49:47

对虚拟桌面功能的程序化访问非常有限,因为Microsoft仅暴露了 ivirtualdesktopmanager com接口。它确实提供了两个关键功能:

  • ivirtualdesktopmanager :: getwindowdesktopid允许您根据已经分配给该桌面的窗口来检索虚拟桌面的ID。

  • ivirtualdesktopmanager :: moveWindowTodesKtop允许您将窗口移至特定的虚拟桌面。

不幸的是,这还不足以完成任何有用的事情。我已经根据

我确实需要强调,此代码不是您想要在产品中承诺的。微软总是可以随时自由地更改无证件的API。而且还有一个运行时的风险:当用户使用虚拟桌面时,此代码不一定会良好。始终请记住,虚拟桌面可以随时出现和消失,完全与您的代码不同步。

要使用代码,请创建一个新的C#类库项目。我将首先发布cominterop.cs,其中包含符合Nickotin的C ++声明的COM接口声明:

using System;
using System.Runtime.InteropServices;

namespace Windows10Interop {
    internal static class Guids {
        public static readonly Guid CLSID_ImmersiveShell = 
            new Guid(0xC2F03A33, 0x21F5, 0x47FA, 0xB4, 0xBB, 0x15, 0x63, 0x62, 0xA2, 0xF2, 0x39);
        public static readonly Guid CLSID_VirtualDesktopManagerInternal = 
            new Guid(0xC5E0CDCA, 0x7B6E, 0x41B2, 0x9F, 0xC4, 0xD9, 0x39, 0x75, 0xCC, 0x46, 0x7B);
        public static readonly Guid CLSID_VirtualDesktopManager = 
            new Guid("AA509086-5CA9-4C25-8F95-589D3C07B48A");
        public static readonly Guid IID_IVirtualDesktopManagerInternal = 
            new Guid("AF8DA486-95BB-4460-B3B7-6E7A6B2962B5");
        public static readonly Guid IID_IVirtualDesktop = 
            new Guid("FF72FFDD-BE7E-43FC-9C03-AD81681E88E4");
    }

    [ComImport]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    [Guid("FF72FFDD-BE7E-43FC-9C03-AD81681E88E4")]
    internal interface IVirtualDesktop {
        void notimpl1(); // void IsViewVisible(IApplicationView view, out int visible);
        Guid GetId();
    }

    [ComImport]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    [Guid("AF8DA486-95BB-4460-B3B7-6E7A6B2962B5")]
    internal interface IVirtualDesktopManagerInternal {
        int GetCount();
        void notimpl1();  // void MoveViewToDesktop(IApplicationView view, IVirtualDesktop desktop);
        void notimpl2();  // void CanViewMoveDesktops(IApplicationView view, out int itcan);
        IVirtualDesktop GetCurrentDesktop();
        void GetDesktops(out IObjectArray desktops);
        [PreserveSig]
        int GetAdjacentDesktop(IVirtualDesktop from, int direction, out IVirtualDesktop desktop);
        void SwitchDesktop(IVirtualDesktop desktop);
        IVirtualDesktop CreateDesktop();
        void RemoveDesktop(IVirtualDesktop desktop, IVirtualDesktop fallback);
        IVirtualDesktop FindDesktop(ref Guid desktopid);
    }

    [ComImport]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    [Guid("a5cd92ff-29be-454c-8d04-d82879fb3f1b")]
    internal interface IVirtualDesktopManager {
        int IsWindowOnCurrentVirtualDesktop(IntPtr topLevelWindow);
        Guid GetWindowDesktopId(IntPtr topLevelWindow);
        void MoveWindowToDesktop(IntPtr topLevelWindow, ref Guid desktopId);
    }

    [ComImport]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    [Guid("92CA9DCD-5622-4bba-A805-5E9F541BD8C9")]
    internal interface IObjectArray {
        void GetCount(out int count);
        void GetAt(int index, ref Guid iid, [MarshalAs(UnmanagedType.Interface)]out object obj);
    }

    [ComImport]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    [Guid("6D5140C1-7436-11CE-8034-00AA006009FA")]
    internal interface IServiceProvider10 {
        [return: MarshalAs(UnmanagedType.IUnknown)]
        object QueryService(ref Guid service, ref Guid riid);
    }

}

下一个是desktop.cs。它包含您可以在代码中使用的友好C#类:

using System;
using System.Runtime.InteropServices;

namespace Windows10Interop
{
    public class Desktop {
        public static int Count {
            // Returns the number of desktops
            get { return DesktopManager.Manager.GetCount(); }
        }

        public static Desktop Current {
            // Returns current desktop
            get { return new Desktop(DesktopManager.Manager.GetCurrentDesktop()); }
        }

        public static Desktop FromIndex(int index) {
            // Create desktop object from index 0..Count-1
            return new Desktop(DesktopManager.GetDesktop(index));
        }

        public static Desktop FromWindow(IntPtr hWnd) {
            // Creates desktop object on which window <hWnd> is displayed
            Guid id = DesktopManager.WManager.GetWindowDesktopId(hWnd);
            return new Desktop(DesktopManager.Manager.FindDesktop(ref id));
        }

        public static Desktop Create() {
            // Create a new desktop
            return new Desktop(DesktopManager.Manager.CreateDesktop());
        }

        public void Remove(Desktop fallback = null) {
            // Destroy desktop and switch to <fallback>
            var back = fallback == null ? DesktopManager.GetDesktop(0) : fallback.itf;
            DesktopManager.Manager.RemoveDesktop(itf, back);
        }

        public bool IsVisible {
            // Returns <true> if this desktop is the current displayed one
            get { return object.ReferenceEquals(itf, DesktopManager.Manager.GetCurrentDesktop()); }
        }

        public void MakeVisible() {
            // Make this desktop visible
            DesktopManager.Manager.SwitchDesktop(itf);
        }

        public Desktop Left {
            // Returns desktop at the left of this one, null if none
            get {
                IVirtualDesktop desktop;
                int hr = DesktopManager.Manager.GetAdjacentDesktop(itf, 3, out desktop);
                if (hr == 0) return new Desktop(desktop);
                else return null;

            }
        }

        public Desktop Right {
            // Returns desktop at the right of this one, null if none
            get {
                IVirtualDesktop desktop;
                int hr = DesktopManager.Manager.GetAdjacentDesktop(itf, 4, out desktop);
                if (hr == 0) return new Desktop(desktop);
                else return null;
            }
        }

        public void MoveWindow(IntPtr handle) {
            // Move window <handle> to this desktop
            DesktopManager.WManager.MoveWindowToDesktop(handle, itf.GetId());
        }

        public bool HasWindow(IntPtr handle) {
            // Returns true if window <handle> is on this desktop
            return itf.GetId() == DesktopManager.WManager.GetWindowDesktopId(handle);
        }

        public override int GetHashCode() {
            return itf.GetHashCode();
        }
        public override bool Equals(object obj) {
            var desk = obj as Desktop;
            return desk != null && object.ReferenceEquals(this.itf, desk.itf);
        }

        private IVirtualDesktop itf;
        private Desktop(IVirtualDesktop itf) { this.itf = itf; }
    }

    internal static class DesktopManager {
        static DesktopManager() {
            var shell = (IServiceProvider10)Activator.CreateInstance(Type.GetTypeFromCLSID(Guids.CLSID_ImmersiveShell));
            Manager = (IVirtualDesktopManagerInternal)shell.QueryService(Guids.CLSID_VirtualDesktopManagerInternal, Guids.IID_IVirtualDesktopManagerInternal);
            WManager = (IVirtualDesktopManager)Activator.CreateInstance(Type.GetTypeFromCLSID(Guids.CLSID_VirtualDesktopManager));
        }

        internal static IVirtualDesktop GetDesktop(int index) {
            int count = Manager.GetCount();
            if (index < 0 || index >= count) throw new ArgumentOutOfRangeException("index");
            IObjectArray desktops;
            Manager.GetDesktops(out desktops);
            object objdesk;
            desktops.GetAt(index, Guids.IID_IVirtualDesktop, out objdesk);
            Marshal.ReleaseComObject(desktops);
            return (IVirtualDesktop)objdesk;
        }

        internal static IVirtualDesktopManagerInternal Manager;
        internal static IVirtualDesktopManager WManager;
    }
}

最后我用来测试代码的一个Little Test Winforms项目。只需将4个按钮放在一个表单上,然后将其命名为buttonleft/右/创建/破坏:

using Windows10Interop;
using System.Diagnostics;
...
    public partial class Form1 : Form {
        public Form1() {
            InitializeComponent();
        }

        private void buttonRight_Click(object sender, EventArgs e) {
            var curr = Desktop.FromWindow(this.Handle);
            Debug.Assert(curr.Equals(Desktop.Current));
            var right = curr.Right;
            if (right == null) right = Desktop.FromIndex(0);
            if (right != null) {
                right.MoveWindow(this.Handle);
                right.MakeVisible();
                this.BringToFront();
                Debug.Assert(right.IsVisible);
            }
        }

        private void buttonLeft_Click(object sender, EventArgs e) {
            var curr = Desktop.FromWindow(this.Handle);
            Debug.Assert(curr.Equals(Desktop.Current));
            var left = curr.Left;
            if (left == null) left = Desktop.FromIndex(Desktop.Count - 1);
            if (left != null) {
                left.MoveWindow(this.Handle);
                left.MakeVisible();
                this.BringToFront();
                Debug.Assert(left.IsVisible);
            } 
        }

        private void buttonCreate_Click(object sender, EventArgs e) {
            var desk = Desktop.Create();
            desk.MoveWindow(this.Handle);
            desk.MakeVisible();
            Debug.Assert(desk.IsVisible);
            Debug.Assert(desk.Equals(Desktop.Current));
        }

        private void buttonDestroy_Click(object sender, EventArgs e) {
            var curr = Desktop.FromWindow(this.Handle);
            var next = curr.Left;
            if (next == null) next = curr.Right;
            if (next != null && next != curr) {
                next.MoveWindow(this.Handle);
                curr.Remove(next);
                Debug.Assert(next.IsVisible);
            }
        }
    }

我在测试时发现的唯一真正的怪癖是将窗口从一个桌面移动到另一个桌面可以将其移至z订单的底部首先切换桌面,然后移动窗口。如果您以相反的方式这样做,那就没有这样的问题。

Programmatic access to the virtual desktop feature is very limited, as Microsoft has only exposed the IVirtualDesktopManager COM interface. It does provide two key functions:

  • IVirtualDesktopManager::GetWindowDesktopId allows you to retrieve the ID of a virtual desktop, based on a window that is already assigned to that desktop.

  • IVirtualDesktopManager::MoveWindowToDesktop allows you to move a window to a specific virtual desktop.

Unfortunately, this is not nearly enough to accomplish anything useful. I've written some C# code based on the reverse-engineering work done by NickoTin. I can't read much of the Russian in his blog post, but his C++ code was pretty accurate.

I do need to emphasize that this code is not something you want to commit to in a product. Microsoft always feels free to change undocumented APIs whenever they feel like it. And there is a runtime risk as well: this code does not necessarily interact well when the user is tinkering with the virtual desktops. Always keep in mind that a virtual desktop can appear and disappear at any time, completely out of sync with your code.

To use the code, create a new C# class library project. I'll first post ComInterop.cs, it contains the COM interface declarations that match NickoTin's C++ declarations:

using System;
using System.Runtime.InteropServices;

namespace Windows10Interop {
    internal static class Guids {
        public static readonly Guid CLSID_ImmersiveShell = 
            new Guid(0xC2F03A33, 0x21F5, 0x47FA, 0xB4, 0xBB, 0x15, 0x63, 0x62, 0xA2, 0xF2, 0x39);
        public static readonly Guid CLSID_VirtualDesktopManagerInternal = 
            new Guid(0xC5E0CDCA, 0x7B6E, 0x41B2, 0x9F, 0xC4, 0xD9, 0x39, 0x75, 0xCC, 0x46, 0x7B);
        public static readonly Guid CLSID_VirtualDesktopManager = 
            new Guid("AA509086-5CA9-4C25-8F95-589D3C07B48A");
        public static readonly Guid IID_IVirtualDesktopManagerInternal = 
            new Guid("AF8DA486-95BB-4460-B3B7-6E7A6B2962B5");
        public static readonly Guid IID_IVirtualDesktop = 
            new Guid("FF72FFDD-BE7E-43FC-9C03-AD81681E88E4");
    }

    [ComImport]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    [Guid("FF72FFDD-BE7E-43FC-9C03-AD81681E88E4")]
    internal interface IVirtualDesktop {
        void notimpl1(); // void IsViewVisible(IApplicationView view, out int visible);
        Guid GetId();
    }

    [ComImport]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    [Guid("AF8DA486-95BB-4460-B3B7-6E7A6B2962B5")]
    internal interface IVirtualDesktopManagerInternal {
        int GetCount();
        void notimpl1();  // void MoveViewToDesktop(IApplicationView view, IVirtualDesktop desktop);
        void notimpl2();  // void CanViewMoveDesktops(IApplicationView view, out int itcan);
        IVirtualDesktop GetCurrentDesktop();
        void GetDesktops(out IObjectArray desktops);
        [PreserveSig]
        int GetAdjacentDesktop(IVirtualDesktop from, int direction, out IVirtualDesktop desktop);
        void SwitchDesktop(IVirtualDesktop desktop);
        IVirtualDesktop CreateDesktop();
        void RemoveDesktop(IVirtualDesktop desktop, IVirtualDesktop fallback);
        IVirtualDesktop FindDesktop(ref Guid desktopid);
    }

    [ComImport]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    [Guid("a5cd92ff-29be-454c-8d04-d82879fb3f1b")]
    internal interface IVirtualDesktopManager {
        int IsWindowOnCurrentVirtualDesktop(IntPtr topLevelWindow);
        Guid GetWindowDesktopId(IntPtr topLevelWindow);
        void MoveWindowToDesktop(IntPtr topLevelWindow, ref Guid desktopId);
    }

    [ComImport]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    [Guid("92CA9DCD-5622-4bba-A805-5E9F541BD8C9")]
    internal interface IObjectArray {
        void GetCount(out int count);
        void GetAt(int index, ref Guid iid, [MarshalAs(UnmanagedType.Interface)]out object obj);
    }

    [ComImport]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    [Guid("6D5140C1-7436-11CE-8034-00AA006009FA")]
    internal interface IServiceProvider10 {
        [return: MarshalAs(UnmanagedType.IUnknown)]
        object QueryService(ref Guid service, ref Guid riid);
    }

}

Next is Desktop.cs. It contains the friendly C# classes that you can use in your code:

using System;
using System.Runtime.InteropServices;

namespace Windows10Interop
{
    public class Desktop {
        public static int Count {
            // Returns the number of desktops
            get { return DesktopManager.Manager.GetCount(); }
        }

        public static Desktop Current {
            // Returns current desktop
            get { return new Desktop(DesktopManager.Manager.GetCurrentDesktop()); }
        }

        public static Desktop FromIndex(int index) {
            // Create desktop object from index 0..Count-1
            return new Desktop(DesktopManager.GetDesktop(index));
        }

        public static Desktop FromWindow(IntPtr hWnd) {
            // Creates desktop object on which window <hWnd> is displayed
            Guid id = DesktopManager.WManager.GetWindowDesktopId(hWnd);
            return new Desktop(DesktopManager.Manager.FindDesktop(ref id));
        }

        public static Desktop Create() {
            // Create a new desktop
            return new Desktop(DesktopManager.Manager.CreateDesktop());
        }

        public void Remove(Desktop fallback = null) {
            // Destroy desktop and switch to <fallback>
            var back = fallback == null ? DesktopManager.GetDesktop(0) : fallback.itf;
            DesktopManager.Manager.RemoveDesktop(itf, back);
        }

        public bool IsVisible {
            // Returns <true> if this desktop is the current displayed one
            get { return object.ReferenceEquals(itf, DesktopManager.Manager.GetCurrentDesktop()); }
        }

        public void MakeVisible() {
            // Make this desktop visible
            DesktopManager.Manager.SwitchDesktop(itf);
        }

        public Desktop Left {
            // Returns desktop at the left of this one, null if none
            get {
                IVirtualDesktop desktop;
                int hr = DesktopManager.Manager.GetAdjacentDesktop(itf, 3, out desktop);
                if (hr == 0) return new Desktop(desktop);
                else return null;

            }
        }

        public Desktop Right {
            // Returns desktop at the right of this one, null if none
            get {
                IVirtualDesktop desktop;
                int hr = DesktopManager.Manager.GetAdjacentDesktop(itf, 4, out desktop);
                if (hr == 0) return new Desktop(desktop);
                else return null;
            }
        }

        public void MoveWindow(IntPtr handle) {
            // Move window <handle> to this desktop
            DesktopManager.WManager.MoveWindowToDesktop(handle, itf.GetId());
        }

        public bool HasWindow(IntPtr handle) {
            // Returns true if window <handle> is on this desktop
            return itf.GetId() == DesktopManager.WManager.GetWindowDesktopId(handle);
        }

        public override int GetHashCode() {
            return itf.GetHashCode();
        }
        public override bool Equals(object obj) {
            var desk = obj as Desktop;
            return desk != null && object.ReferenceEquals(this.itf, desk.itf);
        }

        private IVirtualDesktop itf;
        private Desktop(IVirtualDesktop itf) { this.itf = itf; }
    }

    internal static class DesktopManager {
        static DesktopManager() {
            var shell = (IServiceProvider10)Activator.CreateInstance(Type.GetTypeFromCLSID(Guids.CLSID_ImmersiveShell));
            Manager = (IVirtualDesktopManagerInternal)shell.QueryService(Guids.CLSID_VirtualDesktopManagerInternal, Guids.IID_IVirtualDesktopManagerInternal);
            WManager = (IVirtualDesktopManager)Activator.CreateInstance(Type.GetTypeFromCLSID(Guids.CLSID_VirtualDesktopManager));
        }

        internal static IVirtualDesktop GetDesktop(int index) {
            int count = Manager.GetCount();
            if (index < 0 || index >= count) throw new ArgumentOutOfRangeException("index");
            IObjectArray desktops;
            Manager.GetDesktops(out desktops);
            object objdesk;
            desktops.GetAt(index, Guids.IID_IVirtualDesktop, out objdesk);
            Marshal.ReleaseComObject(desktops);
            return (IVirtualDesktop)objdesk;
        }

        internal static IVirtualDesktopManagerInternal Manager;
        internal static IVirtualDesktopManager WManager;
    }
}

And finally a little test WinForms project that I used to test the code. Just drop 4 buttons on a form and name them buttonLeft/Right/Create/Destroy:

using Windows10Interop;
using System.Diagnostics;
...
    public partial class Form1 : Form {
        public Form1() {
            InitializeComponent();
        }

        private void buttonRight_Click(object sender, EventArgs e) {
            var curr = Desktop.FromWindow(this.Handle);
            Debug.Assert(curr.Equals(Desktop.Current));
            var right = curr.Right;
            if (right == null) right = Desktop.FromIndex(0);
            if (right != null) {
                right.MoveWindow(this.Handle);
                right.MakeVisible();
                this.BringToFront();
                Debug.Assert(right.IsVisible);
            }
        }

        private void buttonLeft_Click(object sender, EventArgs e) {
            var curr = Desktop.FromWindow(this.Handle);
            Debug.Assert(curr.Equals(Desktop.Current));
            var left = curr.Left;
            if (left == null) left = Desktop.FromIndex(Desktop.Count - 1);
            if (left != null) {
                left.MoveWindow(this.Handle);
                left.MakeVisible();
                this.BringToFront();
                Debug.Assert(left.IsVisible);
            } 
        }

        private void buttonCreate_Click(object sender, EventArgs e) {
            var desk = Desktop.Create();
            desk.MoveWindow(this.Handle);
            desk.MakeVisible();
            Debug.Assert(desk.IsVisible);
            Debug.Assert(desk.Equals(Desktop.Current));
        }

        private void buttonDestroy_Click(object sender, EventArgs e) {
            var curr = Desktop.FromWindow(this.Handle);
            var next = curr.Left;
            if (next == null) next = curr.Right;
            if (next != null && next != curr) {
                next.MoveWindow(this.Handle);
                curr.Remove(next);
                Debug.Assert(next.IsVisible);
            }
        }
    }

The only real quirk I noticed while testing this is that moving a window from one desktop to another can move it to the bottom of the Z-order when you first switch the desktop, then move the window. No such problem if you do it the other way around.

沐歌 2025-01-31 07:49:47

有一个人绘制了一个应用程序来映射键盘修理,以在虚拟桌面之间移动一个窗口。
https://github.com/grabacr.com/grabacr07/sylphyhorn
(我每天都使用它)

他有一个博客,可以解释自己的所作所为
http://grabacr.net/archives/5701 (您可以使用Google翻译是日语

)实际上,在Alberto Tostado响应中使用了相同的API。
http://www.cyberforum.ru/blogs/105416/blog3671.html
并且可以在他的github

API真的很简单,但似乎不可能从另一个过程中移动窗口。

public static bool MoveToDesktop(IntPtr hWnd, VirtualDesktop virtualDesktop)
    {
        ThrowIfNotSupported();

        int processId;
        NativeMethods.GetWindowThreadProcessId(hWnd, out processId);

        if (Process.GetCurrentProcess().Id == processId)  // THAT LINE
        {
            var guid = virtualDesktop.Id;
            VirtualDesktop.ComManager.MoveWindowToDesktop(hWnd, ref guid);
            return true;
        }

        return false;
    }

为了解决这个问题,他们做出了另一个实现,他们与俄罗斯博客中的一个相同使用,

if (VirtualDesktopHelper.MoveToDesktop(hWnd, right) //<- the one in the russian blog
                    || this.helper.MoveWindowToDesktop(hWnd, right.Id)) <- the second implementation

可以在这里找到第二个实现: https ://github.com/tmyt/vdmhelper
这个可以将窗口从另一个进程移动到另一个桌面。但这现在是越野车。对于当我尝试像Google Chrome一样移动一些窗口时,它会崩溃。

因此,这是我研究的结果。我现在试图用这些API制作粘性窗口功能。

There is this guy that made a application to map keyboard shorcut to move a window between virtual desktop.
https://github.com/Grabacr07/SylphyHorn
(I use it every day )

He has a blog where he explain what he did
http://grabacr.net/archives/5701 ( you can use google translate it is in japanese)

He in fact used the same api mantionned in the Alberto Tostado response.
http://www.cyberforum.ru/blogs/105416/blog3671.html
and the api can be found on his github https://github.com/Grabacr07/VirtualDesktop

The api is really simple to use BUT it seems impossible to move a window from another process.

public static bool MoveToDesktop(IntPtr hWnd, VirtualDesktop virtualDesktop)
    {
        ThrowIfNotSupported();

        int processId;
        NativeMethods.GetWindowThreadProcessId(hWnd, out processId);

        if (Process.GetCurrentProcess().Id == processId)  // THAT LINE
        {
            var guid = virtualDesktop.Id;
            VirtualDesktop.ComManager.MoveWindowToDesktop(hWnd, ref guid);
            return true;
        }

        return false;
    }

To workaround this problem they made another implementation that they use alongside the one in the russian blog

if (VirtualDesktopHelper.MoveToDesktop(hWnd, right) //<- the one in the russian blog
                    || this.helper.MoveWindowToDesktop(hWnd, right.Id)) <- the second implementation

The second implementation can be found here: https://github.com/tmyt/VDMHelper
This one can move a window from another process to another desktop. BUT it is buggy right now. For exemple when i try to move some window like google chrome it crash.

So this is the result of my research. I m rigth now trying to make a StickyWindow feature with these api.

软的没边 2025-01-31 07:49:47

我担心Windows 10中关于“虚拟桌面”的所有内容都没有证明,但是在俄罗斯页面中,我看到的记录了界面。我不会说俄语,但似乎他们使用了反向工程。无论如何,代码非常清楚(感谢它们!)。

在这里关注:

http://wwwwwww.cyberforum.ru/blogs/105416/blog3671.html 查看旧的API的CreateSktop,OpenDesktop等...链接到新的Virtual-Desktops,但没有办法...

接口与Windows 10(2015-05-08)的最终生产版本一起使用,但是您在Microsoft记录它们之前,不应在真正的宽分布式应用程序中使用它们。风险太多。

问候。

I fear that all about "Virtual desktops" in Windows 10 is undocumented, but in a Russian page I've seen documented the interfaces. I don't speak Russian but seems that they have used reversed engineering. Anyway, the code is very clear (Thanks to them!).

Keep an eye here:
http://www.cyberforum.ru/blogs/105416/blog3671.html

I've been trying to see if the old API's CreateDesktop, OpenDesktop, etc... is linked to the new Virtual-Desktops, but no way...

The interfaces work with the final production release of Windows 10 (2015-05-08), but you shouldn't use them in a real wide distributed application until Microsoft documents them. Too much risk.

Regards.

用心笑 2025-01-31 07:49:47

您可以从注册表密钥中列举虚拟桌面指南,

HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\VirtualDesktops

以补充MagicAndre1981的出色答案,您可能需要事先知道台式机及其相关的指导,而不知道哪个TopWindow在哪个台式机上。

一个示例如何做到这一点。
首先,我创建一个类来保存每个虚拟桌面的信息:

public class VirtualDesktop
{
    public byte[] VirtualDesktopId { get; set; }
    public Guid VirtualDesktopGuid { get; set; }
    public string Name { get; set; }
    public string Wallpaper { get; set; }
    public bool IsCurrentVirtualDesktop { get; set; }
}

然后,这是填充VirtualDesktop实例列表的示例方法:

public static List<VirtualDesktop> EnumerateVirtualDesktops()
{
    List<VirtualDesktop> virtualDesktops = new List<VirtualDesktop>();
    //get ids from HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\VirtualDesktops,VirtualDesktopIDs
    //assume byte length is # of VDesktops * 16
    byte[] virtualDesktopIDs = (byte[])Registry.GetValue(
            keyName: @"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\VirtualDesktops",
            valueName: "VirtualDesktopIDs",
            defaultValue: new byte[0]
        );
    byte[] currentVirtualDesktop = (byte[])Registry.GetValue(
            keyName: @"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\VirtualDesktops",
            valueName: "CurrentVirtualDesktop",
            defaultValue: new byte[0]
        );
    int numberOfVirtualDesktops = virtualDesktopIDs.Length / 16;
    for (int iVD = 0; iVD < numberOfVirtualDesktops; iVD++)
    {
        byte[] virtualDesktopID = new byte[16];
        Array.Copy(
                sourceArray: virtualDesktopIDs,
                sourceIndex: iVD * 16,
                destinationArray: virtualDesktopID,
                destinationIndex: 0,
                length: 16
            );
        Guid virtualDesktopGuid = new Guid(virtualDesktopID);
        //keys for each virtual desktop
        string virtualDesktopName = (string)Registry.GetValue(
                keyName: String.Format(@"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\VirtualDesktops\Desktops\{0:B}", virtualDesktopGuid),
                valueName: "Name",
                defaultValue: ""
            );
        //You should handle null here, which means the desktop key doesn't exist, which never happens in my use-case
        if (virtualDesktopName == "")
        {
            virtualDesktopName = String.Format("Desktop {0}", iVD + 1);
        }
        string virtualDesktopWallpaper = (string)Registry.GetValue(
                keyName: String.Format(@"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\VirtualDesktops\Desktops\{0:B}", virtualDesktopGuid),
                valueName: "Wallpaper",
                defaultValue: ""
            );
        virtualDesktops.Add(new VirtualDesktop()
        {
            VirtualDesktopId = virtualDesktopID,
            VirtualDesktopGuid = virtualDesktopGuid,
            Name = virtualDesktopName,
            Wallpaper = virtualDesktopWallpaper,
            IsCurrentVirtualDesktop = virtualDesktopID.SequenceEqual(currentVirtualDesktop)
        });
    }
    return virtualDesktops;
}

然后,您可以使用Windows API MoveWindowTodeSktop函数作为MagicAndRe1981的答案中的Windows API MoveWindowTodEsktop函数,将窗口愉快地移动到特定的台式机。
例如

MoveWindowToDesktop(TopLevelWindow, virtualDesktops.First(vd => vd.Name == "Desktop 1").VirtualDesktopGuid)

You can enumerate the virtual desktop GUIDs from the registry key

HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\VirtualDesktops

To supplement magicandre1981's excellent answer above, you may need to know the desktops in advance and their related Guids without knowing which TopWindow is on which desktop.

An example how to do this.
First I create a class to hold the information for each virtual desktop:

public class VirtualDesktop
{
    public byte[] VirtualDesktopId { get; set; }
    public Guid VirtualDesktopGuid { get; set; }
    public string Name { get; set; }
    public string Wallpaper { get; set; }
    public bool IsCurrentVirtualDesktop { get; set; }
}

Then here is an example method to fill a List of VirtualDesktop instances:

public static List<VirtualDesktop> EnumerateVirtualDesktops()
{
    List<VirtualDesktop> virtualDesktops = new List<VirtualDesktop>();
    //get ids from HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\VirtualDesktops,VirtualDesktopIDs
    //assume byte length is # of VDesktops * 16
    byte[] virtualDesktopIDs = (byte[])Registry.GetValue(
            keyName: @"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\VirtualDesktops",
            valueName: "VirtualDesktopIDs",
            defaultValue: new byte[0]
        );
    byte[] currentVirtualDesktop = (byte[])Registry.GetValue(
            keyName: @"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\VirtualDesktops",
            valueName: "CurrentVirtualDesktop",
            defaultValue: new byte[0]
        );
    int numberOfVirtualDesktops = virtualDesktopIDs.Length / 16;
    for (int iVD = 0; iVD < numberOfVirtualDesktops; iVD++)
    {
        byte[] virtualDesktopID = new byte[16];
        Array.Copy(
                sourceArray: virtualDesktopIDs,
                sourceIndex: iVD * 16,
                destinationArray: virtualDesktopID,
                destinationIndex: 0,
                length: 16
            );
        Guid virtualDesktopGuid = new Guid(virtualDesktopID);
        //keys for each virtual desktop
        string virtualDesktopName = (string)Registry.GetValue(
                keyName: String.Format(@"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\VirtualDesktops\Desktops\{0:B}", virtualDesktopGuid),
                valueName: "Name",
                defaultValue: ""
            );
        //You should handle null here, which means the desktop key doesn't exist, which never happens in my use-case
        if (virtualDesktopName == "")
        {
            virtualDesktopName = String.Format("Desktop {0}", iVD + 1);
        }
        string virtualDesktopWallpaper = (string)Registry.GetValue(
                keyName: String.Format(@"HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\VirtualDesktops\Desktops\{0:B}", virtualDesktopGuid),
                valueName: "Wallpaper",
                defaultValue: ""
            );
        virtualDesktops.Add(new VirtualDesktop()
        {
            VirtualDesktopId = virtualDesktopID,
            VirtualDesktopGuid = virtualDesktopGuid,
            Name = virtualDesktopName,
            Wallpaper = virtualDesktopWallpaper,
            IsCurrentVirtualDesktop = virtualDesktopID.SequenceEqual(currentVirtualDesktop)
        });
    }
    return virtualDesktops;
}

Then you can happily move your windows around to specific desktops using the Windows API MoveWindowToDesktop function as in magicandre1981's answer.
E.g.

MoveWindowToDesktop(TopLevelWindow, virtualDesktops.First(vd => vd.Name == "Desktop 1").VirtualDesktopGuid)
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文