壁紙を変更するの方法ではマルチディスプレイ環境ですべてのディスプレイに対して個別に壁紙を設定することは出来ない。 また、個別に壁紙を設定するためのレジストリエントリやAPIもないようなので、それらを使わずに壁紙を設定する方法を考える。

やり方としては、

  1. すべてのディスプレイに対応した大きさのビットマップを作成し
  2. そこにディスプレイ毎の壁紙を描画し
  3. その画像を一枚の壁紙として設定する

ことでディスプレイ毎に壁紙を設定したように見せることができる。

例えば、サイズが800x600のディスプレイが2つ横に並んでいる環境の場合、サイズが1600x600のビットマップを用意し、(0,0)-(800,600)に1つ目のディスプレイ用の壁紙、(800,0)-(1600,600)に2つ目のディスプレイ用の壁紙を描画することで、2つのディスプレイそれぞれに壁紙を設定したように見せることができる。

このやり方で必要となるディスプレイ毎のサイズと位置は、Screen.AllScreensプロパティに含まれる各インスタンスのScreen.Boundsプロパティを参照することで得られるが、プライマリディスプレイとその他のディスプレイの位置関係によっては、Screen.BoundsプロパティのXおよびYの値が負になる場合がある点に注意する必要がある(プライマリディスプレイの原点は常に(0, 0)となる模様)。 また、HKEY_CURRENT_USER\Control Panel\DesktopのTileWallpaperの値は1、Wallpaperstyleの値は0(つまり「並べて表示」)に設定しないと意図したとおりに表示されない。

§1 サンプル

このコードでは、実行環境に2台のディスプレイが接続されていることを前提としている。 また、画像のアスペクト比は考慮せず、常に全体に拡大して表示する。
このコードを使ったサンプルプログラムはMultiDisplayWallpaperChangerで公開している。

using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;
using System.Runtime.InteropServices;

private void DrawWallpaper()
{
  // ディスプレイ毎の壁紙を描画したビットマップを保存するためのファイル名
  const string combinedWallpaperFile = @"wallpaper.bmp";

  // ディスプレイと設定する壁紙のファイル名
  var wallpaperMap = new[] {
    new {Screen = Screen.AllScreens[0], File = @"C:\Users\Public\Pictures\Sample Pictures\Dock.jpg"},
    new {Screen = Screen.AllScreens[1], File = @"C:\Users\Public\Pictures\Sample Pictures\Waterfall.jpg"},
  };

  // すべてのディスプレイを含む矩形を計算する
  var allScreenBounds = new Rectangle(0, 0, 0, 0);

  foreach (var screen in Screen.AllScreens) {
    allScreenBounds = Rectangle.Union(allScreenBounds, screen.Bounds);
  }

  Console.WriteLine("AllScreenBounds: {0}", allScreenBounds);

  // 計算したサイズでビットマップを作成する
  using (var allScreenBitmap = new Bitmap(allScreenBounds.Width, allScreenBounds.Height, PixelFormat.Format24bppRgb)) {
    using (var g = Graphics.FromImage(allScreenBitmap)) {
      g.Clear(Color.Black);

      // allScreenBoundsの原点が(0, 0)以外になった場合でもビットマップの左上端から描画されるように
      // Graphicsの座標系の原点を変換する
      g.TranslateTransform((float)-allScreenBounds.X, (float)-allScreenBounds.Y, MatrixOrder.Prepend);

      // ディスプレイ毎の壁紙を読み込み、作成したビットマップに描画する
      foreach (var pair in wallpaperMap) {
        using (var bitmap = new Bitmap(pair.File)) {
          g.DrawImage(bitmap, pair.Screen.Bounds);

          Console.WriteLine("{0}: {1}", pair.Screen.DeviceName, pair.Screen.Bounds);
        }
      }
    }

    // 描画したビットマップを保存する
    allScreenBitmap.Save(combinedWallpaperFile, ImageFormat.Bmp);
  }

  // 保存したビットマップを壁紙として設定する
  // WallpaperOriginXおよびWallpaperOriginYが(0, 0)のままだと表示される位置が合わなくなるため
  // allScreenBoundsの原点を表示する位置にする
  SetWallpaper(combinedWallpaperFile, allScreenBounds.X, allScreenBounds.Y);
}

[DllImport("user32.dll", SetLastError = true)] public static extern bool SystemParametersInfo(int uAction, int uParam, string lpvParam, int fuWinIini);

private void SetWallpaper(string file, int x, int y)
{
  const int SPI_SETDESKWALLPAPER  = 0x0014;
  const int SPIF_UPDATEINIFILE    = 0x0001;
  const int SPIF_SENDWININICHANGE = 0x0002;

  using (var regkeyDesktop = Registry.CurrentUser.OpenSubKey(@"Control Panel\Desktop", true)) {
    if (regkeyDesktop == null)
      throw new Win32Exception(Marshal.GetLastWin32Error());

    regkeyDesktop.SetValue("TileWallpaper", "1");
    regkeyDesktop.SetValue("Wallpaperstyle", "0");
    regkeyDesktop.SetValue("WallpaperOriginX", x.ToString());
    regkeyDesktop.SetValue("WallpaperOriginY", y.ToString());
    regkeyDesktop.SetValue("Wallpaper", file);
  }

  var flags = (Environment.OSVersion.Platform == PlatformID.Win32NT)
    ? SPIF_SENDWININICHANGE
    : SPIF_SENDWININICHANGE | SPIF_UPDATEINIFILE;

  if (!SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, file, flags))
    throw new Win32Exception(Marshal.GetLastWin32Error());
}

SetWallpaperの詳細は壁紙を変更するを参考のこと。

以下いくつかのディスプレイ配置設定で実行した際の結果とスクリーンショット。

§1.1 パターン1

プライマリディスプレイを左、2台めのディスプレイを右に配置した場合。

AllScreenBounds: {X=0,Y=0,Width=2720,Height=1024}
\\.\DISPLAY1: {X=0,Y=0,Width=1280,Height=1024}
\\.\DISPLAY2: {X=1280,Y=0,Width=1440,Height=900}
プライマリディスプレイを左、2台めのディスプレイを右に配置した場合のスクリーンショット

§1.2 パターン2

プライマリディスプレイを右、2台めのディスプレイを左に配置した場合。

AllScreenBounds: {X=-1440,Y=0,Width=2720,Height=1024}
\\.\DISPLAY1: {X=0,Y=0,Width=1280,Height=1024}
\\.\DISPLAY2: {X=-1440,Y=120,Width=1440,Height=900}
プライマリディスプレイを右、2台めのディスプレイを左に配置した場合のスクリーンショット

§1.3 パターン3

プライマリディスプレイを左上、2台めのディスプレイを右下に配置した場合。

AllScreenBounds: {X=0,Y=0,Width=1952,Height=1924}
\\.\DISPLAY1: {X=0,Y=0,Width=1280,Height=1024}
\\.\DISPLAY2: {X=512,Y=1024,Width=1440,Height=900}
プライマリディスプレイを左上、2台めのディスプレイを右下に配置した場合のスクリーンショット

§1.4 パターン4

プライマリディスプレイを左下、2台めのディスプレイを右上に配置した場合。

AllScreenBounds: {X=0,Y=-900,Width=1952,Height=1924}
\\.\DISPLAY1: {X=0,Y=0,Width=1280,Height=1024}
\\.\DISPLAY2: {X=512,Y=-900,Width=1440,Height=900}
プライマリディスプレイを左下、2台めのディスプレイを右上に配置した場合のスクリーンショット