WinFormsで作るお絵描きアプリ ― MenuStrip編

~色変更・線幅変更・保存機能をメニューから操作する~

はじめに

前回はWinFormsお絵描きアプリ 応用編 を紹介しました。

今回はさらに発展させて、メニュー形式 (MenuStrip) を使い「色変更・線幅変更・保存」を行えるようにします。

WinForms では MenuStrip を配置するだけで、アプリ上部にメニューが並ぶ「おなじみのデスクトップアプリのUI」を簡単に実装できます。


実装機能

  1. ファイル
    • 保存(Ctrl+S ショートカット対応)
    • 終了
  2. 編集
    • クリア(Ctrl+N)で画面を白紙に戻す
  3. ブラシ
    • 色変更(カラーダイアログを開く)
    • 線幅変更(プリセット 1px~20px の選択肢)

コード例

Form1.cs

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

namespace SimplePaintMenu
{
    public class Form1 : Form
    {
        private readonly PictureBox _view = new() { Dock = DockStyle.Fill, BackColor = Color.White };

        private readonly MenuStrip _menu = new();
        private readonly ToolStripMenuItem _fileMenu = new("&ファイル");
        private readonly ToolStripMenuItem _editMenu = new("&編集");
        private readonly ToolStripMenuItem _brushMenu = new("&ブラシ");

        private Bitmap _canvas;
        private bool _drawing;
        private Point _last;

        private Color _penColor = Color.Black;
        private float _penWidth = 3f;

        public Form1()
        {
            Text = "Simple Paint (Menu Version)";
            ClientSize = new Size(900, 600);
            DoubleBuffered = true;

            BuildMenu();

            Controls.Add(_view);
            Controls.Add(_menu);

            Load += (_, __) => CreateCanvas();
            _view.Resize += (_, __) => CreateCanvas(preserve: true);

            // マウス描画処理
            _view.MouseDown += (s, e) =>
            {
                if (e.Button == MouseButtons.Left)
                {
                    _drawing = true;
                    _last = e.Location;
                }
            };
            _view.MouseMove += (s, e) =>
            {
                if (!_drawing) return;
                using (var g = Graphics.FromImage(_canvas))
                using (var pen = new Pen(_penColor, _penWidth)
                {
                    StartCap = LineCap.Round,
                    EndCap = LineCap.Round,
                    LineJoin = LineJoin.Round
                })
                {
                    g.SmoothingMode = SmoothingMode.AntiAlias;
                    g.DrawLine(pen, _last, e.Location);
                }
                _last = e.Location;
                _view.Invalidate();
            };
            _view.MouseUp += (_, __) => _drawing = false;

            _view.Paint += (s, e) =>
            {
                if (_canvas != null)
                    e.Graphics.DrawImageUnscaled(_canvas, Point.Empty);
            };
        }

        private void BuildMenu()
        {
            var saveItem = new ToolStripMenuItem("保存(&S)") { ShortcutKeys = Keys.Control | Keys.S };
            saveItem.Click += (_, __) => SaveImage();

            var exitItem = new ToolStripMenuItem("終了(&X)");
            exitItem.Click += (_, __) => Close();

            _fileMenu.DropDownItems.AddRange(new ToolStripItem[] { saveItem, new ToolStripSeparator(), exitItem });

            var clearItem = new ToolStripMenuItem("クリア(&C)") { ShortcutKeys = Keys.Control | Keys.N };
            clearItem.Click += (_, __) => ClearCanvas();
            _editMenu.DropDownItems.Add(clearItem);

            var colorItem = new ToolStripMenuItem("色変更(&C)...");
            colorItem.Click += (_, __) =>
            {
                using var cd = new ColorDialog { Color = _penColor };
                if (cd.ShowDialog() == DialogResult.OK)
                    _penColor = cd.Color;
            };

            var widthMenu = new ToolStripMenuItem("線幅(&W)");
            foreach (var w in new[] { 1, 2, 3, 5, 8, 12, 16, 20 })
            {
                var item = new ToolStripMenuItem($"{w}px");
                item.Click += (_, __) => _penWidth = w;
                widthMenu.DropDownItems.Add(item);
            }

            _brushMenu.DropDownItems.AddRange(new ToolStripItem[] { colorItem, widthMenu });

            _menu.Items.AddRange(new ToolStripItem[] { _fileMenu, _editMenu, _brushMenu });
        }

        private void CreateCanvas(bool preserve = false)
        {
            if (_view.Width <= 0 || _view.Height <= 0) return;
            var newBmp = new Bitmap(_view.Width, _view.Height);
            using (var g = Graphics.FromImage(newBmp))
            {
                g.Clear(Color.White);
                if (preserve && _canvas != null)
                    g.DrawImageUnscaled(_canvas, Point.Empty);
            }
            _canvas?.Dispose();
            _canvas = newBmp;
            _view.Invalidate();
        }

        private void ClearCanvas()
        {
            if (_canvas == null) return;
            using (var g = Graphics.FromImage(_canvas))
                g.Clear(Color.White);
            _view.Invalidate();
        }

        private void SaveImage()
        {
            if (_canvas == null) return;
            using var sfd = new SaveFileDialog
            {
                Filter = "PNGファイル|*.png|JPEGファイル|*.jpg|BMPファイル|*.bmp",
                FileName = "drawing.png"
            };
            if (sfd.ShowDialog() == DialogResult.OK)
                _canvas.Save(sfd.FileName);
        }
    }
}

実行画面イメージ

  • アプリ上部に 「ファイル」「編集」「ブラシ」 メニューが並びます。
  • ファイル → 保存 でPNG/JPEG/BMPとして保存。
  • 編集 → クリア で白紙に戻す。
  • ブラシ → 色変更 で好きな色を選択。
  • ブラシ → 線幅 で太さを変更可能。

実装のポイント

1. MenuStrip と ToolStripMenuItem

  • MenuStrip をフォームに追加し、ToolStripMenuItem を階層的に登録するだけでメニューUIが完成します。

2. ショートカットキーの付与

  • ShortcutKeys = Keys.Control | Keys.S のように指定すると、一般的なショートカット操作をすぐに追加できます。

3. ブラシ設定

  • 色は ColorDialog を利用してユーザーに選ばせます。
  • 線幅は複数の ToolStripMenuItem を並べて選択できるようにしています。

まとめ

今回の実装で「見た目も機能もデスクトップアプリらしい」お絵描きアプリに進化しました。

MenuStrip を使うことで、学習教材としても、実用ツールとしても 発展性のある土台が作れます。

次のステップとしては:

  • 消しゴムモード(背景色で上書き)
  • Undo / Redo 機能(履歴管理)
  • 複数レイヤー対応

といった拡張も考えられるでしょう。


訪問数 5 回, 今日の訪問数 5回

C#

Posted by hidepon