C# Programming

Image

GDI+ によるスクリーンセーバー

開発環境: Visual Studio 2003 

1.目次

2.目的

GDI+ によるスクリーンセーバを作ります。

3.参考書

(1) MSDN: Visual Basic .NET Code Sample: Create a Screensaver with GDI+

4.作り方

基本的な作り方は、参考書(1)に記載されていますので、そちらを参考にしてください。
ここでは、参考書(1)から、さらに簡単にした例です。
(1) Windows Forms で ScreenSaver1 というプロジェクトを作成します。
ここでは、Uchukamen.ScreenSaver 名前空間に、ScreenSaver1 というフォームを作成します。

(2) Form のプロパティを次のようにセットして、スクリーンセーバのトップ画面を作ります。
      
プロパティ説明
TopMostTrue一番上の画面でスクリーンを覆う。
WindowStateMaximized画面を最大化
FormBoarderStyleNoneウィンドウのまわりの枠は表示しない。

(3) キーを押したり、マウスを動かしたときに、スクリーンセーバを解除するイベントハンドラを追加します。
イベント動作説明
MouseDownApplicationExit()マウスボタンを押した。
MouseMoveApplicationExit()マウスを動かした。
KeyDownApplicationExit()何かキーが押された。

マウスが動いた場合のイベント処理
MSDNの例では、10ピクセル以上マウスが動いた場合、スクリーンセーバー解除するようにプログラムしています。
このために、MouseMoveイベントで、1回目のイベントで、そのときのマウスの位置を保存し、次のイベントで、移動量の絶対値が10以上であれば、スクリーンセーバを解除するようにしています。

            private bool isActive = false;
            private Point mouseLocation;

            private void ScreenSaver1_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
            {
                // If the MouseLocation still points to 0,0, move it to its actual location
                //   and save the location for later. Otherwise, check to see if the user
                //   has moved the mouse at least 10 pixels.
                if(!isActive)
                {
                    this.mouseLocation = new Point(e.X, e.Y);
                    isActive = true;
                } 
                else
                {
                    if(Math.Abs(e.X - this.mouseLocation.X) > 10 || 
                        Math.Abs(e.Y - this.mouseLocation.Y) > 10 )
                        Application.Exit();
                }            
            }
(4) 描画処理を追加します。次のような、DrawShape() メソッドを追加してください。
ここでは、MSDN のサンプルコードのオプションを削除してさらに簡単にしています。
そして、この描画処理が、タイマーで一定期間ごとに呼び出されるようにします。
そのために、ツールバーより、タイマーをフォームへドラッグアンドドロップします。
次に、フォームのロードイベントで、タイマーをイネーブルされるようにします。
そして、タイマーのTickイベントでこのDrawShape() メソッドを呼び出すようにします。
スクリーンセーバーの描画処理
MSDNの例と同じように、描画処理を追加します。
このとき、Graphics オブジェクトが必要なので、フォームがロードされたときに、CreateGraphics()で作成しておきます。
        private Graphics g = null;

        private void ScreenSaver1_Load(object sender, System.EventArgs e)
        {
            this.timer1.Enabled = true;
            g = this.CreateGraphics();
        }

        private Random random = new Random();
        private void DrawShape()
        {
            Color myColor;

            // ランダムな大きさで図形を描画するため、ウィンドウの大きさ内の(x1, y1), (x2, y2) を
            // 乱数で発生させます。
            int maxX = this.Width;
            int maxY = this.Height;

            int x1 = random.Next(0, maxX);
            int x2 = random.Next(0, maxX);

            int y1 = random.Next(0, maxY);
            int y2 = random.Next(0, maxY);

            // 上記座標より、描画する矩形を定義します。
            Rectangle myRect = new Rectangle(Math.Min(x1, x2), Math.Min(y1, y2), Math.Abs(x1 - x2), Math.Abs(y1 - y2));

            myColor = Color.FromArgb(random.Next(255), random.Next(255), random.Next(255), random.Next(255));
   
            g.FillRectangle(new SolidBrush(myColor), myRect);
        }
(6) これで実行してみましょう。
フルスクリーンで描画をはじめたと思います。
これで、基本的な描画は完成です。

(7) 次に、これをスクリーンセーバとして、Windows から起動してもらう必要があります。
そのためには、(6)で作ったファイルを .scr というファイル名にリネームし、%windir%\System32 にストアする必要があります。
しかし、それだけではだめで、スクリーンセーバとして起動される際に、パラメータとして渡されてきます。
そこで、各パラメータに従った動作をするように、起動時のパラメータの処理を追加します。      
起動時パラメータ説明
/cスクリーンセーバのオプション設定用のダイアログを表示する。
/sスクリーンセーバとして動作
/p ウィンドウハンドルプレビュー画面の表示。
しかし、ウィドウハンドルに直接描画するには、やっかいなプラットフォーム呼び出しが必要なので対応しない。
      
スクリーンセーバの起動時パラメータの処理
参考文献1のコードは、ちょっとトリッキーなコードですね。
自分のクラスのインスタンスを新たに生成し、そいつにダイアログを表示させると言う動作になります。
        [STAThread]
        static void Main(string[] args) 
        {
            if(args.Length > 0)
            {
                //  "/p" はプレビュー。ここではノンサポート。
                if(args[0].ToLower() == "/p")
                    Application.Exit();

                // "/c" はオプションダイアログ。ここではノンサポート。
                if(args[0].ToLower().Trim().Substring(0, 2) == "/c")
                {
                    OptionForm optionForm = new OptionForm();
                    optionForm.ShowDialog();
                    Application.Exit();
                }

                // "/s" スクリーンセーバの実行
                if(args[0].ToLower() == "/s")
                {
                    ScreenSaver1 form = new ScreenSaver1();
                    form.ShowDialog();

                    Application.Exit();
                }
            }
            else
            {
                ScreenSaver1 form = new ScreenSaver1();
                form.ShowDialog();

                Application.Exit();
            }
        }
(8) オプションの設定
Windowsからは、設定ボタンを押した場合に、/c オプションでスクリーンセーバが起動されます。
この場合に、ダイアログを表示して、自分の好きな設定に変えることができます。
そこで、オプションを管理すための Option クラス、およびオプションの設定を行う OptionDialog クラスを追加します。
オプションは、タイマーのスピードを変えるだけの簡単なものにします。
この値は、実行ディレクトリの”MyScreenSaver.dat”と言うファイルに決めうちで、シリアライズ、デシリアライズします。

(9)*.scr ファイルを自動的にビルドするようにする。
ビルドができたらば、ScreenSaver1.exe をSceenSaver1.scr にコピーするようにビルドコマンドを追加します。
メニュー⇒プロジェクト⇒プロパティでプロジェクトのプロパティページを開き、共通プロパティのビルドイベントで、
ビルド後のイベントコマンドラインで、copy ScreenSaver1.exe ScreenSaver1.scr というようにコピーします。
また、テスト用にcopy ScreenSaver1.scr %windir%\system32 を追加しておくと、テストが楽です。
ただし、バグがあったりして動作不良の場合、デバッグもできなくなってしまうので、危険です。
WindowState を Normal で十分テストしてから、%windir%\system32 にコピーしましょう。
Image

5.問題点

このMSDNの例では、スクリーンセーバの設定画面でのプレビューをどうやって実現するのか説明がありません。
いずれ、LongHorn で、Avalon ベースのスクリーンセーバになるので、この方法に力を入れて勉強するよりは、
Longhorn/Avalon ベースのスクリーンセーバを勉強したほうがいいですね。

6.ダウンロード

テスト環境
Windows XP Professional SP1
Microsoft Development Environment 2003
.NET Framework V1.1

ScreenSaver1 スケルトンコード V1.0 2004/6/6
ScreenSaver1.lzh
33KB
ソースファイル一式

7.ソースコード

変更履歴
2004/6/6   初版作成 
Form1.cs
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;

namespace Uchukamen.ScreenSaver
{
    /// <summary>
    /// Form1 の概要の説明です。
    /// </summary>
    public class ScreenSaver1 : System.Windows.Forms.Form
    {
        private System.ComponentModel.IContainer components;

        public ScreenSaver1()
        {
            //
            // Windows フォーム デザイナ サポートに必要です。
            //
            InitializeComponent();

            //
            // TODO: InitializeComponent 呼び出しの後に、コンストラクタ コードを追加してください。
            //
        }

        /// <summary>
        /// 使用されているリソースに後処理を実行します。
        /// </summary>
        protected override void Dispose( bool disposing )
        {
            if( disposing )
            {
                if (components != null) 
                {
                    components.Dispose();
                }
            }
            base.Dispose( disposing );
        }

        #region Windows フォーム デザイナで生成されたコード 
        /// <summary>
        /// デザイナ サポートに必要なメソッドです。このメソッドの内容を
        /// コード エディタで変更しないでください。
        /// </summary>
        private void InitializeComponent()
        {
            this.components = new System.ComponentModel.Container();
            this.timer1 = new System.Windows.Forms.Timer(this.components);
            // 
            // timer1
            // 
            this.timer1.Tick += new System.EventHandler(this.timer1_Tick);
            // 
            // ScreenSaver1
            // 
            this.AutoScaleBaseSize = new System.Drawing.Size(5, 12);
            this.ClientSize = new System.Drawing.Size(292, 266);
            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
            this.Name = "ScreenSaver1";
            this.Text = "Form1";
            this.TopMost = true;
            this.WindowState = System.Windows.Forms.FormWindowState.Maximized;
            this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.ScreenSaver1_KeyDown);
            this.MouseDown += new System.Windows.Forms.MouseEventHandler(this.ScreenSaver1_MouseDown);
            this.Load += new System.EventHandler(this.ScreenSaver1_Load);
            this.MouseMove += new System.Windows.Forms.MouseEventHandler(this.ScreenSaver1_MouseMove);

        }
        #endregion

        /// <summary>
        /// アプリケーションのメイン エントリ ポイントです。
        /// </summary>
        [STAThread]
        static void Main(string[] args) 
        {
            if(args.Length > 0)
            {
                //  "/p" はプレビュー。ここではノンサポート。
                if(args[0].ToLower() == "/p")
                    Application.Exit();

                // "/c" はオプションダイアログ。ここではノンサポート。
                if(args[0].ToLower().Trim().Substring(0, 2) == "/c")
                {
                    OptionForm optionForm = new OptionForm();
                    optionForm.ShowDialog();
                    Application.Exit();
                }

                // "/s" スクリーンセーバの実行
                if(args[0].ToLower() == "/s")
                {
                    ScreenSaver1 form = new ScreenSaver1();
                    form.ShowDialog();

                    Application.Exit();
                }
            }
            else
            {
                ScreenSaver1 form = new ScreenSaver1();
                form.ShowDialog();

                Application.Exit();
            }
        }
    
        private System.Windows.Forms.Timer timer1;
        private Random random = new Random();

        private void DrawShape()
        {
            Color myColor;

            // ランダムな大きさで図形を描画するため、ウィンドウの大きさ内の(x1, y1), (x2, y2) を
            // 乱数で発生させます。
            int maxX = this.Width;
            int maxY = this.Height;

            int x1 = random.Next(0, maxX);
            int x2 = random.Next(0, maxX);

            int y1 = random.Next(0, maxY);
            int y2 = random.Next(0, maxY);

            // 上記座標より、描画する矩形を定義します。
            Rectangle myRect = new Rectangle(Math.Min(x1, x2), Math.Min(y1, y2), Math.Abs(x1 - x2), Math.Abs(y1 - y2));

            myColor = Color.FromArgb(random.Next(255), random.Next(255), random.Next(255), random.Next(255));

            g.FillRectangle(new SolidBrush(myColor), myRect);
        }

        private Graphics g = null;
        private Options options = new Options();

        private void timer1_Tick(object sender, System.EventArgs e)
        {
            this.DrawShape();
        }

        private void ScreenSaver1_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
        {
            Application.Exit();
        }

        private void ScreenSaver1_Load(object sender, System.EventArgs e)
        {
            this.timer1.Enabled = true;
            g = this.CreateGraphics();

            
            options.LoadOptions();
            this.timer1.Interval = options.Speed;
        }

        private void ScreenSaver1_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
        {
            Application.Exit();
        }

        private bool isActive = false;
        private Point mouseLocation;

        private void ScreenSaver1_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
        {
            // MSDNの例では、10ピクセル以上マウスが動いた場合、
            // スクリーンセーバー解除するようにプログラムしています。
            // このために、MouseMoveイベントで、1回目のイベントで、
            // そのときのマウスの位置を保存し、次のイベントで、
            // 移動量の絶対値が10以上であれば、
            // スクリーンセーバを解除するようにしています。
            if(!isActive)
            {
                this.mouseLocation = new Point(e.X, e.Y);
                isActive = true;
            } 
            else
            {
                if(Math.Abs(e.X - this.mouseLocation.X) > 10 || 
                    Math.Abs(e.Y - this.mouseLocation.Y) > 10 )
                    Application.Exit();
            }            
        }
    }
}
Option.cs
using System;
using System.IO;
using System.Xml;
using System.Xml.Serialization;

namespace Uchukamen.ScreenSaver
{
    /// <summary>
    /// Options の概要の説明です。
    /// </summary>
    [Serializable()]
    public class Options
    {
        public Options()
        {
            // 
            // TODO: コンストラクタ ロジックをここに追加してください。
            //            
            speed = 500;
        }

        private string optionsPath = Environment.CurrentDirectory + "\\ScreenSaver1.dat";

        private int speed;
       
        public int Speed 
        {
            get
            {
                return speed;
            }
            set
            {
                speed = value;
            }
        }

        public bool IsOptionFileExisting()
        {
            FileInfo finfo = new System.IO.FileInfo(optionsPath);
            return finfo.Exists;
        }

        public void LoadOptions()
        {
            Options options = new Options();

            if(options.IsOptionFileExisting())
            {
                XmlSerializer xs  = new XmlSerializer(this.GetType());
                StreamReader sr = new StreamReader(optionsPath);

                System.Xml.XmlTextReader xtr = new System.Xml.XmlTextReader(sr);

                if(xs.CanDeserialize(xtr))
                    options = (Options)(xs.Deserialize(xtr));
                else              
                    options.SaveOptions();
                        
                xtr.Close();
                sr.Close();
            }

            this.Speed = options.Speed;

        }

        public void SaveOptions()
        {
            StreamWriter sw = new StreamWriter(optionsPath);
            XmlSerializer xs = new XmlSerializer(this.GetType());
            xs.Serialize(sw, this);
            sw.Close();

        }
    }
}
OptionForm.cs
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;

namespace Uchukamen.ScreenSaver
{
    /// <summary>
    /// OptionForm の概要の説明です。
    /// </summary>
    public class OptionForm : System.Windows.Forms.Form
    {
        private System.Windows.Forms.TrackBar trackBar1;
        private System.Windows.Forms.Button buttonOk;
        private System.Windows.Forms.Button buttonCancel;
        private System.Windows.Forms.Label label1;
        private System.Windows.Forms.Label label2;
        private System.Windows.Forms.Label label3;
        /// <summary>
        /// 必要なデザイナ変数です。
        /// </summary>
        private System.ComponentModel.Container components = null;

        public OptionForm()
        {
            //
            // Windows フォーム デザイナ サポートに必要です。
            //
            InitializeComponent();

            //
            // TODO: InitializeComponent 呼び出しの後に、コンストラクタ コードを追加してください。
            //
        }

        /// <summary>
        /// 使用されているリソースに後処理を実行します。
        /// </summary>
        protected override void Dispose( bool disposing )
        {
            if( disposing )
            {
                if(components != null)
                {
                    components.Dispose();
                }
            }
            base.Dispose( disposing );
        }

        #region Windows フォーム デザイナで生成されたコード 
        /// <summary>
        /// デザイナ サポートに必要なメソッドです。このメソッドの内容を
        /// コード エディタで変更しないでください。
        /// </summary>
        private void InitializeComponent()
        {
            this.trackBar1 = new System.Windows.Forms.TrackBar();
            this.buttonOk = new System.Windows.Forms.Button();
            this.buttonCancel = new System.Windows.Forms.Button();
            this.label1 = new System.Windows.Forms.Label();
            this.label2 = new System.Windows.Forms.Label();
            this.label3 = new System.Windows.Forms.Label();
            ((System.ComponentModel.ISupportInitialize)(this.trackBar1)).BeginInit();
            this.SuspendLayout();
            // 
            // trackBar1
            // 
            this.trackBar1.LargeChange = 100;
            this.trackBar1.Location = new System.Drawing.Point(8, 32);
            this.trackBar1.Maximum = 1000;
            this.trackBar1.Minimum = 100;
            this.trackBar1.Name = "trackBar1";
            this.trackBar1.Size = new System.Drawing.Size(256, 45);
            this.trackBar1.TabIndex = 0;
            this.trackBar1.TickFrequency = 100;
            this.trackBar1.Value = 100;
            // 
            // buttonOk
            // 
            this.buttonOk.Location = new System.Drawing.Point(88, 96);
            this.buttonOk.Name = "buttonOk";
            this.buttonOk.TabIndex = 1;
            this.buttonOk.Text = "Ok";
            this.buttonOk.Click += new System.EventHandler(this.buttonOk_Click);
            // 
            // buttonCancel
            // 
            this.buttonCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
            this.buttonCancel.Location = new System.Drawing.Point(168, 96);
            this.buttonCancel.Name = "buttonCancel";
            this.buttonCancel.TabIndex = 2;
            this.buttonCancel.Text = "Cancel";
            // 
            // label1
            // 
            this.label1.Location = new System.Drawing.Point(8, 8);
            this.label1.Name = "label1";
            this.label1.Size = new System.Drawing.Size(32, 24);
            this.label1.TabIndex = 3;
            this.label1.Text = "速度";
            // 
            // label2
            // 
            this.label2.Location = new System.Drawing.Point(8, 72);
            this.label2.Name = "label2";
            this.label2.Size = new System.Drawing.Size(32, 24);
            this.label2.TabIndex = 4;
            this.label2.Text = "早い";
            // 
            // label3
            // 
            this.label3.Location = new System.Drawing.Point(240, 72);
            this.label3.Name = "label3";
            this.label3.Size = new System.Drawing.Size(32, 24);
            this.label3.TabIndex = 5;
            this.label3.Text = "遅い";
            // 
            // OptionForm
            // 
            this.AcceptButton = this.buttonOk;
            this.AutoScaleBaseSize = new System.Drawing.Size(5, 12);
            this.CancelButton = this.buttonCancel;
            this.ClientSize = new System.Drawing.Size(280, 126);
            this.Controls.Add(this.label3);
            this.Controls.Add(this.label2);
            this.Controls.Add(this.label1);
            this.Controls.Add(this.buttonCancel);
            this.Controls.Add(this.buttonOk);
            this.Controls.Add(this.trackBar1);
            this.Name = "OptionForm";
            this.Text = "ScreenSaver1 Option";
            this.Load += new System.EventHandler(this.OptionForm_Load);
            ((System.ComponentModel.ISupportInitialize)(this.trackBar1)).EndInit();
            this.ResumeLayout(false);

        }
        #endregion

        private void buttonOk_Click(object sender, System.EventArgs e)
        {
            options.Speed = this.trackBar1.Value;
            options.SaveOptions();
            this.Close();
        }

        private Options options;

        private void OptionForm_Load(object sender, System.EventArgs e)
        {
            options = new Options();        
            options.LoadOptions();
            this.trackBar1.Value = options.Speed;
        }
    }
}