本文翻译自MSDN博客,如有错误,还请指正。
在win10中选择虚拟桌面
win10中引进了一个新的概念叫做虚拟桌面。现在MSDN对其的指南上是这么描述的:
用户可以将若干个窗口放在一起来创建一个虚拟桌面。每个窗口是虚拟桌面的一部分。当一个虚拟桌面隐藏了,和它相关的所有窗口也会被隐藏。这就意味着允许用户创建多个工作环境并可以在它们之间切换(PS:快捷键是ctrl+win+左右方向键)。同样的,当一个虚拟桌面被激活后,和它相关的窗口会显示在屏幕上。 为了支持这个概念,应用应该避免自动将用户从一个虚拟桌面切换到另一个虚拟桌面。仅允许用户自己改变。为了支持这个,新创建的窗口应该在当前激活的虚拟桌面显示。另外,如果应用重复使用当前激活的窗口,它仅能当这些重复使用的窗口也在当前激活的虚拟桌面上时才可以。否则,应该创建一个新的窗口。
这是个好的建议,因为它可以在大多数情况下提供最好的用户体验,而且作为开发者,大多数的简单应用可以完全不用考虑虚拟桌面。但是,如果你的应用或者场景需要做某些诸如即使用户切换虚拟桌面也要永远保持待在上层的事,你该怎么做?
IVirtualDesktopManager
随着win10增加虚拟桌面,也引入一个名为IVirtualDesktopManager的shell接口。虽然它只有三个功能,但是这可以让你的应用和虚拟桌面完成许多事情。尝试使用这些功能移动一个非自己进程所拥有的窗口去另一个虚拟桌面是不会成功的。因为对于大多数应用来说这不是一个通用或者需要的行为。当你应用程序窗口所在的虚拟桌面不再可见或者你的应用程序窗口被移动到一个新的虚拟桌面时,系统不会提供相关通知以供订阅。但是,如果你的应用程序获取了焦点,此时用户选择虚拟桌面,你会被告知程序窗口失去了焦点。
IsWindowOnCurrentVirtualDesktop会告诉你你的窗口是否在当前虚拟桌面。GetWindowDesktopId 会告诉你指定窗口所在桌面的ID。MoveWindowToDesktop允许你将指定窗口移动到指定桌面。
但是如果在当前桌面上没有你的窗口,该如何获取当前桌面的ID呢?这个其实很简单。如果你创建了一个新的无父窗口的窗口,它将会显示在当前桌面上。
示例:
把上面的集合在一起,这有一个简单的例子,它使用c# winform制作一个永远在上层且会自己在多个虚拟桌面间移动的窗口(csproj附在最后)。
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace VirtualDesktopSwitch
{
/// <summary>
/// Example form
/// </summary>
public partial class VDExampleWindow : Form
{
public VDExampleWindow()
{
InitializeComponent();
}
private VirtualDesktopManager vdm;
private void VDExampleWindow_Load(object sender, EventArgs e)
{
//Create IVirtualDesktopManager on load
vdm = new VirtualDesktopManager();
}
private void label1_Click(object sender, EventArgs e)
{
//Show details on click
MessageBox.Show("Virtual Desktop ID: " + vdm.GetWindowDesktopId(Handle).ToString("X") + Environment.NewLine +
"IsCurrentVirtualDesktop: " + vdm.IsWindowOnCurrentVirtualDesktop(Handle).ToString()
);
}
//Timer tick to check if the window is on the current virtual desktop and change it otherwise
//A timer does not have to be used, but something has to trigger the check
//If the window was active before the vd change, it would trigger
//the deactivated and lost focus events when the vd changes
//The timer always gets triggered which makes the example hopefully less confusing
private void VDCheckTimer_Tick(object sender, EventArgs e)
{
try
{
if (!vdm.IsWindowOnCurrentVirtualDesktop(Handle))
{
using (NewWindow nw = new NewWindow())
{
nw.Show(null);
vdm.MoveWindowToDesktop(Handle, vdm.GetWindowDesktopId(nw.Handle));
}
}
}
catch
{
//This will fail due to race conditions as currently written on occassion
}
}
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.label1 = new System.Windows.Forms.Label();
this.VDCheckTimer = new System.Windows.Forms.Timer(this.components);
this.SuspendLayout();
//
// label1
//
this.label1.Dock = System.Windows.Forms.DockStyle.Fill;
this.label1.Font = new System.Drawing.Font("Microsoft Sans Serif", 13.875F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.label1.Location = new System.Drawing.Point(0, 0);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(1112, 368);
this.label1.TabIndex = 0;
this.label1.Text = "Example Contents";
this.label1.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
this.label1.Click += new System.EventHandler(this.label1_Click);
//
// VDCheckTimer
//
this.VDCheckTimer.Enabled = true;
this.VDCheckTimer.Interval = 1000;
this.VDCheckTimer.Tick += new System.EventHandler(this.VDCheckTimer_Tick);
//
// VDExampleWindow
//
this.AutoScaleDimensions = new System.Drawing.SizeF(12F, 25F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(1112, 368);
this.Controls.Add(this.label1);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.Fixed3D;
this.Name = "VDExampleWindow";
this.Text = "VD Example";
this.TopMost = true;
this.Load += new System.EventHandler(this.VDExampleWindow_Load);
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Timer VDCheckTimer;
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new VDExampleWindow());
}
}
[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
);
}
public class NewWindow : Form
{
}
[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);
}
}
}
}