かくすけのいろいろ作るブログ

かくすけの開発者ブログです。開発の他いろいろなモノづくりについて書きます。

【C#】WindowsFormアプリでPC画面の一部を画像として切り出す

■開発環境

 ※C#初心者です。

こんにちは。かくすけです。

前回の記事の続きです

kakusuke98.hatenablog.com

前回アプリを作り始めたところですね。
今回はやることの第一歩である「PC画面の一部を画像として取得する」部分を開発します!

目次

  • 参考ページ
  • いただいたソースの埋め込み
  • エラー対処
  • 動かしてみる!
  • カスタマイズ

参考ページ

ありがたーい参考ページ(英語)

https://www.codeproject.com/Tips/988951/Custom-Snipping-Tool-using-Csharp-WinForms

英語のページですがまさに私のやりたいことに近いです。
しかもソースまで配布されていて…本当にありがたい!
Thank you so much!!

いただいたソースの埋め込み

MySnippingTool_Source\MySnippingTool\MySnippingToolの中身を
作ったばかりのプロジェクトにいただいたファイルを追加します。

f:id:kakusuke98:20190725233138p:plain

とりあえず動きを見てみよう!
と、開始ボタンを押したのですが、ビルド失敗。。。

f:id:kakusuke98:20190725233644p:plain

エラー文のメモを忘れました。開発者としてまだまだですね。
大体の内容としては

  • MainWindow.resxは信用ならんから読み込めんよ
  • 参照が足りんよ

という内容
順番に対処しましょう

エラー対処

■MainWindow.resxは信用ならんから読み込めんよ 問題

なぜ信用ならないかというとWebページから持ってきたファイルだから。
Windowsさんが安全のためにブロックしてくれています。
対処法は2つ
(1)そのファイルの使用許可設定をする
(2)そのファイルを使わない

(1)そのファイルの使用許可設定をする
私は信用する!という方は
WindowsGUI操作で MainWindow.resx の許可設定をします。

f:id:kakusuke98:20190725235600p:plain

f:id:kakusuke98:20190725235920p:plain

この状態でまたプロジェクトに入れてあげればOK!

(2)そのファイルを使わない
やっぱりちょっと怖い...という方は
いっそのこと MainWindow.resx を使わずに進めます。
このファイル、使うアイコンの設定をしているだけなんで読み込まなくても何とかなるみたいです。

そのアイコンの読み込み部分である「MainWindow.Designer.cs」の137行目をコメントアウトします。

f:id:kakusuke98:20190726000451p:plain

これでOK!

■参照が足りんよ 問題

ソースをいただいたアプリではどうも Microsoft Word のファイルに出力する機能があるらしく
この機能を使うために特殊な参照を設定してるみたいです。

こちらも対処法は2つ
(1)参照を追加する
(2)その機能を使わない

(1)参照を追加する
Word出力機能を使いたい方はこちらの方法で対処しましょう。
[プロジェクト]→[参照の追加]で参照マネージャーウィンドウを開き
[COM]内の以下二つにチェックを付けてOKボタン

f:id:kakusuke98:20190726001421p:plain f:id:kakusuke98:20190726001707p:plain

これでWord出力もつかえるはず!(動作未確認)

(2)その機能を使わない
Word機能を使わなくていいなら、無駄な参照は省いた方が良いはず。
その呼び出しをしている部分をごっそりコメント化します!
その場所は「Utility.cs」の26~55行目です。

f:id:kakusuke98:20190726001928p:plain

メソッドそのまま消すと他に影響でそうで消せないチキンです。
もうちょっと内容を理解してから取捨選択します。

動かしてみる!

気を取り直して、再度開始ボタンをポチッ
(私は2つの問題両方(2)の方法で回避しています。)

f:id:kakusuke98:20190725233644p:plain

すると、画面全体がうっすらと白掛かりました。

f:id:kakusuke98:20190728235112p:plain

この状態で画面のどこかを左上から右下にドラッグ&ドロップすると

f:id:kakusuke98:20190728235142p:plain

シンプルなウィンドウが表示されました。
この状態で[Ctrl]+[Shift]+[Z]すると

f:id:kakusuke98:20190728235319p:plain

ドラッグ&ドロップした範囲の加画像が表示されます。
ここの表示ではちょっとつぶれた状態ですが、画像として保存したところ元の比率で保存されました。
画像保存はメニューの[File]→[Save as Images]からできます。

こんな簡単に画面キャプチャの仕組みが作れるなんて本当にありがたいです!
以降は自分用メモ

カスタマイズ

デフォルトの動作がちゃんと動いたので、自分の作りたいアプリにあわせたカスタマイズを行います。
主に以下のカスタマイズを行います。

  • 不要な機能の削除
  • デュアルモニタ対応
  • 見た目の調整

デュアルモニタ対応だけ説明を簡単に残しておきます。(説明漏れあるかも)
重要なのはCanvas.csの編集です。

using System;
using System.Drawing;
using System.Windows.Forms;

namespace MySnippingTool
{
    class Canvas : Form
    {
        Point startPos;      // mouse-down position
        Point currentPos;    // current mouse position
        Point canvasStartPos;
        bool drawing;

        public Canvas()
        {
            // モニタ情報の取得
            Screen[] screens = Screen.AllScreens;
            int left = 0;
            int top = 0;
            int right = 0;
            int bottom = 0;
            foreach (Screen screen in screens)
            {
                if (left > screen.Bounds.Left)
                {
                    left = screen.Bounds.Left; // Left
                }
                if (top > screen.Bounds.Top)
                {
                    top = screen.Bounds.Top; // Top
                }
                if (right < screen.Bounds.Right)
                {
                    right = screen.Bounds.Right; // Right
                }
                if (bottom < screen.Bounds.Bottom)
                {
                    bottom = screen.Bounds.Bottom; // Bottom
                }
            }

            canvasStartPos = new Point(left, top);
            this.Location = canvasStartPos;  // フォームの開始地点をモニタの左上に設定
            this.Size = new System.Drawing.Size( Math.Abs(left - right), Math.Abs(top - bottom));    // LeftとRightの距離, TopとBottomの距離
            this.StartPosition = FormStartPosition.Manual;
            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
            this.BackColor = Color.White;
            this.Opacity = 0.75;
            this.Cursor = Cursors.Cross;
            this.MouseDown += Canvas_MouseDown;
            this.MouseMove += Canvas_MouseMove;
            this.MouseUp += Canvas_MouseUp;
            this.Paint += Canvas_Paint;
            this.KeyDown += Canvas_KeyDown;
            this.DoubleBuffered = true;
        }

        private void Canvas_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Escape)
            {
                this.DialogResult = System.Windows.Forms.DialogResult.Cancel;
                this.Close();
            }
        }

        // 指定した範囲を返す
        public Rectangle GetRectangle()
        {
            return new Rectangle(
                Math.Min(startPos.X, currentPos.X),
                Math.Min(startPos.Y, currentPos.Y),
                Math.Abs(startPos.X - currentPos.X),
                Math.Abs(startPos.Y - currentPos.Y));
        }

        // 指定中の範囲を返す
        public Rectangle DrawRectangle()
        {
            return new Rectangle(
                Math.Min(startPos.X - canvasStartPos.X, currentPos.X - canvasStartPos.X),
                Math.Min(startPos.Y - canvasStartPos.Y, currentPos.Y - canvasStartPos.Y),
                Math.Abs(startPos.X - currentPos.X),
                Math.Abs(startPos.Y - currentPos.Y));
        }

        // 範囲指定開始地点の保持
        private void Canvas_MouseDown(object sender, MouseEventArgs e)
        {
            currentPos = startPos = e.Location + new Size(canvasStartPos.X, canvasStartPos.Y);
            drawing = true;
        }

        // 範囲指定マウスドラッグ中の処理
        private void Canvas_MouseMove(object sender, MouseEventArgs e)
        {
            currentPos = e.Location + new Size(canvasStartPos.X, canvasStartPos.Y);
            if (drawing) this.Invalidate();
        }

        // 範囲指定終了処理
        // 範囲終了位置はCanvas_MouseMoveで取得している
        private void Canvas_MouseUp(object sender, MouseEventArgs e)
        {
            this.DialogResult = System.Windows.Forms.DialogResult.OK;
            this.Close();
        }

        // 選択中範囲の描画
        private void Canvas_Paint(object sender, PaintEventArgs e)
        {
            if (drawing) e.Graphics.DrawRectangle(Pens.Red, DrawRectangle());
        }

        private void InitializeComponent()
        {
            this.SuspendLayout();
            // 
            // Canvas
            // 
            this.ClientSize = new System.Drawing.Size(284, 261);
            this.Name = "Canvas";
            this.ShowInTaskbar = false;
            this.ResumeLayout(false);

        }
    }
}

"// モニタ情報の取得"というコメントの下につらつらと追加してます。
このifの嵐を何とかしてくれ!と思われそうで怖いですw
考え方は簡単

f:id:kakusuke98:20190811230534p:plain

何画面あろうと全画面の四隅値をとってあたかも1つの画面のように扱っています。

GetRectangle()は画像の取得に使用する座標を返し、
DrawRectangle()は範囲指定のドラッグ中にマウスの動きに合わせて矩形を表示するために使っています。

        // 指定した範囲を返す
        public Rectangle GetRectangle()
        {
            return new Rectangle(
                Math.Min(startPos.X, currentPos.X),
                Math.Min(startPos.Y, currentPos.Y),
                Math.Abs(startPos.X - currentPos.X),
                Math.Abs(startPos.Y - currentPos.Y));
        }

        // 指定中の範囲を返す
        public Rectangle DrawRectangle()
        {
            return new Rectangle(
                Math.Min(startPos.X - canvasStartPos.X, currentPos.X - canvasStartPos.X),
                Math.Min(startPos.Y - canvasStartPos.Y, currentPos.Y - canvasStartPos.Y),
                Math.Abs(startPos.X - currentPos.X),
                Math.Abs(startPos.Y - currentPos.Y));
        }

これでデュアルモニタに対応できます!