我喜欢Windows现在内置了虚拟桌面,但我有一些我想要添加/修改的功能。 (强制窗口显示在所有桌面上,使用热键启动任务视图,使用每个监视器桌面等)
我搜索过应用程序和开发人员参考资料,以帮助我自定义桌面,但没有运气。
我知道应该从哪里开始吗?
答案 0 :(得分:24)
对虚拟桌面功能的编程访问非常有限,Microsoft仅公开了IVirtualDesktopManager COM接口。不足以完成任何有用的事情。我根据NickoTin完成的逆向工程工作编写了一些C#代码。我在他的博客文章中看不到任何俄语,但他的C ++代码非常准确。
我需要强调的是,此代码不是您要在产品中提交的内容。微软总是随时随地改变无证的apis。并且存在运行时风险,当用户也在修改虚拟桌面时,此代码不一定能很好地交互。请始终牢记,虚拟桌面可以随时出现和消失,与您的代码完全不同步。
创建一个新的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;
}
}
最后我用来测试代码的一个小测试Winforms项目。只需在表单上放下4个按钮,然后将它们命名为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);
}
}
}
在测试时我注意到的唯一真正的怪癖是,当您首先切换桌面时,将窗口从一个桌面移动到另一个桌面可以将其移动到Z顺序的底部,然后移动窗口。如果你这样做,就没有这样的问题了。