65.9K
CodeProject 正在变化。 阅读更多。
Home

桌面切换

2004 年 7 月 8 日

5分钟阅读

viewsIcon

340950

downloadIcon

6989

展示如何实现桌面切换的文章。

引言

由于有一台闲置的电脑,我决定尝试一下 Mandrake Linux,看看它是什么样的(我以前从未使用过 Linux)。我发现它的桌面切换功能很有趣,并开始想知道在我的 Windows 机器上是否也能实现同样的功能。在 MSDN 上快速搜索后,我发现了 Desktop API 函数,这些函数允许我实现这一点。根据文档,这项功能自 Windows 2000 Professional 起就可用了,既然我使用的是 XP,我想我会试试。

Windows 加载并登录后,会自动创建一个名为“default”的桌面。使用提供的函数,您可以创建、打开和切换桌面,但不提供删除功能(稍后详细介绍)。

使用 Desktop 类

创建桌面

创建桌面非常简单,如下面的代码示例所示。可以使用实例成员或静态成员来创建桌面。

// Instance method
Desktop desktop = new Desktop();
desktop.Create("myDesktop");
 
// Static method
Desktop desktop = Desktop.CreateDesktop("myDesktop");

注意:桌面名称不能包含反斜杠字符。

使用以上任一方法都将得到一个 `Desktop` 对象,并带有打开的桌面句柄(前提是桌面创建成功)。新桌面没有任何正在运行的进程——如果您切换到它,您只会看到默认的壁纸。我提供了 `Prepare` 方法,该方法将 explorer 加载到桌面上,使其可用。

切换桌面

切换桌面与创建桌面一样简单,只需调用声明桌面名称的静态方法或打开的 `Desktop` 对象的实例方法即可。

// instance method.
Desktop desktop = new Desktop();
desktop.Open("myDesktop");
desktop.Show();
 
// static method.
Desktop.Show("myDesktop");

在桌面上打开进程

kernel32.dll 中的 `CreateProcess` 函数接受一个 `STARTUPINFO` 类型的参数,其中有一个参数(“lpDesktop”)允许您指定进程要创建的桌面。因此,我选择导入此函数,而不是使用 .NET 框架的 `Process` 类,这意味着在创建进程时功能有所减少,但我希望在下一版本中解决此问题。现在是代码,此示例显示了创建进程的两种方法。

// instance method
Desktop desktop = Desktop.OpenDesktop("myDesktop");
Process p = desktop.CreateProcess("calc.exe");
 
// static method
Process p = Desktop.CreateProcess("calc.exe", "myDesktop");

`CreateProcess` 方法都返回一个 `Process` 对象,该对象可用于终止进程,或根据您的需要使用。

删除桌面

删除桌面有点棘手。删除桌面的唯一方法是终止运行在桌面上的所有进程,此时,桌面会自动删除。到目前为止,我无法获取除输入桌面之外的桌面上运行的进程列表(也不确定这是否可能),但我所做的是提供了 `GetInputProcesses` 方法,该方法将返回当前输入桌面(用户可见的桌面)上所有正在运行的进程的数组。

考虑到这一点,删除用户访问过的桌面的唯一方法是位于该桌面上,枚举进程,然后逐个终止它们。确保您的应用程序在有机会切换桌面之前不会被终止(除非您希望如此)。

Process[] processes = Desktop.GetInputProcesses();
Process thisProc = Process.GetCurrentProcess();
foreach(Process p in processes)
{
   if (p.ProcessName != thisProc.ProcessName)
   {
      p.Kill();
   }
}

设置线程的桌面

您的进程的线程可以移动到不同的桌面,前提是它们*当前桌面上没有任何钩子或窗口*。

Desktop desktop = Desktop.OpenDesktop("myDesktop");
Desktop.SetCurrent(desktop);

上面的代码示例会将调用线程移动到“myDesktop”,但如果该线程有窗口或钩子,则会失败。

幕后

`Desktop` 的桌面切换功能是通过以下 API 函数实现的(从 user32.dll 导入)

  • CreateDesktop
  • OpenDesktop
  • OpenInputDesktop
  • CloseDesktop
  • SwitchDesktop
  • SetThreadDesktop
  • GetThreadDesktop

创建桌面

[DllImport("user32.dll")]
private static extern IntPtr CreateDesktop(string lpszDesktop,
                                           IntPtr lpszDevice,
                                           IntPtr pDevmode,
                                           int dwFlags,
                                           long dwDesiredAccess,
                                           IntPtr lpsa);

`CreateDesktop` 函数从 user32.dll 导入。当您尝试创建一个新桌面时调用它,只需指定桌面名称 (lpszDesktop)、标志 (dwFlags) 和所需的访问权限 (dwDesiredAccess)。

m_desktop = CreateDesktop(name, IntPtr.Zero, IntPtr.Zero, 0, 
    AccessRights, IntPtr.Zero);

返回值是桌面的句柄,如果发生错误则为 `IntPtr.Zero`。

切换桌面

[DllImport("user32.dll")]
private static extern bool SwitchDesktop(IntPtr hDesktop);

`SwitchDesktop` 函数从 user32.dll 导入。切换桌面时,将要成为输入桌面的桌面句柄作为唯一参数传递,返回值指示切换是否成功。

bool result = SwitchDesktop(m_desktop);

在桌面上打开进程

[DllImport("kernel32.dll")]
private static extern bool CreateProcess(
   string lpApplicationName,
   string lpCommandLine,
   IntPtr lpProcessAttributes,
   IntPtr lpThreadAttributes,
   bool bInheritHandles,
   int dwCreationFlags,
   IntPtr lpEnvironment,
   string lpCurrentDirectory,
   ref STARTUPINFO lpStartupInfo,
   ref PROCESS_INFORMATION lpProcessInformation);

`CreateProcess` 函数从 kernel32.dll 导入。在桌面上创建进程时,使用此函数,并仅指定命令行 (lpCommandLine)、继承句柄 (bInheritHandles)、创建标志 (dwCreationFlags)、启动信息 (lpStartupInfo) 和进程信息 (lpProcessInformation),如以下代码所示。

// set startup parameters.
STARTUPINFO si = new STARTUPINFO();
si.cb = Marshal.SizeOf(si);
si.lpDesktop = m_desktopName;
 
PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
 
// start the process.
bool result = CreateProcess(null, path, IntPtr.Zero, IntPtr.Zero, true,
    NORMAL_PRIORITY_CLASS, IntPtr.Zero, null, ref si, ref pi);

指定 `PROCESS_INFORMATION` 结构允许我检索刚创建进程的进程 ID,从而可以为其创建托管的 `Process` 对象。

return Process.GetProcessById(pi.dwProcessId);

设置线程的桌面

[DllImport("user32.dll")]
private static extern bool SetThreadDesktop(IntPtr hDesktop);

`SetThreadDesktop` 函数从*user32.dll*导入。调用 `SetCurrent` 将导致调用此函数。传递给 `SetCurrent` 的 Desktop 对象,如果已打开,将包含一个有效的桌面句柄,可以通过 `DesktopHandle` 属性访问,该属性被传递给 `SetThreadDesktop`。

return SetThreadDesktop(desktop.DesktopHandle);

Example usage

我没有提供带有源代码的示例应用程序,因为它非常简单明了,并且所有成员(私有成员除外)都有 XML 注释。但是,如果您想了解如何在应用程序中使用它,请访问我的网站,我在那里用它制作了一个小型的桌面切换应用程序。

历史

版本

注释

1.0
  • 首次发布
1.1
2004 年 6 月 6 日
  • 添加了 Window 和 `WindowCollection` 类
  • 为 `GetWindows` 添加了另一个重载,它使用了 `WindowCollection`
  • 添加了 `GetInputProcesses` 方法来检索输入桌面上运行的进程
  • 将 `GetWindows` 和 `GetDesktops` 改为返回数组,而不是按引用传递。
1.2
2004 年 7 月 8 日
  • 实现了 `IDisposable`
  • 实现了 `ICloneable`
  • 重写了 `ToString` 以返回桌面名称
© . All rights reserved.