첨부 실행 코드는 나눔고딕코딩 폰트를 사용합니다.
유용한 소스 코드가 있으면 icodebroker@naver.com으로 보내주시면 감사합니다.
블로그 자료는 자유롭게 사용하세요.

■ PictureBox 클래스 : 사용자 그리기 및 확대/축소하기

------------------------------------------------------------------------------------------------------------------------


TestProject.zip



Polyline.cs

 

 

using System.Collections.Generic;

using System.Drawing;

using System.Drawing.Drawing2D;

using System.Xml.Serialization;

 

namespace TestProject

{

    /// <summary>

    /// 다각선

    /// </summary>

    public class Polyline

    {

        //////////////////////////////////////////////////////////////////////////////////////////////////// Field

        ////////////////////////////////////////////////////////////////////////////////////////// Public

 

        #region Field

 

        /// <summary>

        /// 색상

        /// </summary>

        [XmlIgnore]

        public Color Color = Color.Black;

 

        /// <summary>

        /// 두께

        /// </summary>

        public int Thickness = 1;

 

        /// <summary>

        /// 대시 스타일

        /// </summary>

        public DashStyle DashStyle = DashStyle.Solid;

 

        /// <summary>

        /// 포인트 리스트

        /// </summary>

        public List<Point> PointList = new List<Point>();

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Property

        ////////////////////////////////////////////////////////////////////////////////////////// Public

 

        #region ARGB - ARGB

 

        /// <summary>

        /// ARGB

        /// </summary>

        public int ARGB

        {

            get

            {

                return Color.ToArgb();

            }

            set

            {

                Color = Color.FromArgb(value);

            }

        }

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method

        ////////////////////////////////////////////////////////////////////////////////////////// Public

 

        #region 그리기 - Draw(graphics)

 

        /// <summary>

        /// 그리기

        /// </summary>

        /// <param name="graphics">그래픽스</param>

        public void Draw(Graphics graphics)

        {

            using(Pen pen = new Pen(Color, Thickness))

            {

                pen.DashStyle = DashStyle;

 

                if(DashStyle == DashStyle.Custom)

                {

                    pen.DashPattern = new float[] { 10, 2 };

                }

 

                graphics.DrawLines(pen, PointList.ToArray());

            }

        }

 

        #endregion

    }

}

 

 

MainForm.cs

 

 

using System;

using System.Collections.Generic;

using System.Drawing;

using System.Drawing.Drawing2D;

using System.IO;

using System.Windows.Forms;

using System.Xml.Serialization;

 

namespace TestProject

{

    /// <summary>

    /// 메인 폼

    /// </summary>

    public partial class MainForm : Form

    {

        //////////////////////////////////////////////////////////////////////////////////////////////////// Field

        ////////////////////////////////////////////////////////////////////////////////////////// Private

 

        #region Field

 

        /// <summary>

        /// 다각선 리스트

        /// </summary>

        private List<Polyline> polylineList = new List<Polyline>();

 

        /// <summary>

        /// 신규 다각선

        /// </summary>

        private Polyline newPolyline = null;

 

        /// <summary>

        /// 그리기 색상

        /// </summary>

        private Color drawingColor = Color.Black;

 

        /// <summary>

        /// 그리기 두께

        /// </summary>

        private int drawingThickness = 1;

 

        /// <summary>

        /// 그리기 대시 스타일

        /// </summary>

        private DashStyle drawingDashStyle = DashStyle.Solid;

 

        /// <summary>

        /// 자동 저장 파일 경로

        /// </summary>

        private string autoSaveFilePath = Path.GetTempPath() + "scribble.tmp";

 

        /// <summary>

        /// 월드 너비

        /// </summary>

        private const int WORLD_WIDTH = 200;

 

        /// <summary>

        /// 월드 높이

        /// </summary>

        private const int WORLD_HEIGHT = 200;

 

        /// <summary>

        /// 그림 스케일

        /// </summary>

        private float pictureScale = 1.0f;

 

        /// <summary>

        /// 역 변환

        /// </summary>

        private Matrix inverseTransform = new Matrix();

 

        /// <summary>

        /// UNDO 스택

        /// </summary>

        private Stack<string> undoStack = new Stack<string>();

 

        /// <summary>

        /// REDO 스택

        /// </summary>

        private Stack<string> redoStack = new Stack<string>();

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor

        ////////////////////////////////////////////////////////////////////////////////////////// Public

 

        #region 생성자 - MainForm()

 

        /// <summary>

        /// 생성자

        /// </summary>

        public MainForm()

        {

            InitializeComponent();

 

            Load                                    += Form_Load;

            FormClosing                             += Form_FormClosing;

            this.newMenuitem.Click                  += newMenuitem_Click;

            this.openMenuItem.Click                 += openMenuItem_Click;

            this.saveAsMenuItem.Click               += saveAsMenuItem_Click;

            this.exitMenuItem.Click                 += exitMenuItem_Click;

            this.undoMenuitem.Click                 += undoMenuitem_Click;

            this.redoMenuItem.Click                 += redoMenuItem_Click;

            this.blackMenuItem.Click                += colorMenuItem_Click;

            this.redMenuItem.Click                  += colorMenuItem_Click;

            this.greenMenuItem.Click                += colorMenuItem_Click;

            this.blueMenuItem.Click                 += colorMenuItem_Click;

            this.orangeMenuItem.Click               += colorMenuItem_Click;

            this.yellowMenuItem.Click               += colorMenuItem_Click;

            this.limeMenuItem.Click                 += colorMenuItem_Click;

            this.thickness1MenuItem.Click           += thicknessMenuItem_Click;

            this.thickness2MenuItem.Click           += thicknessMenuItem_Click;

            this.thickness3MenuItem.Click           += thicknessMenuItem_Click;

            this.thickness4MenuItem.Click           += thicknessMenuItem_Click;

            this.thickness5MenuItem.Click           += thicknessMenuItem_Click;

            this.solidMenuItem.Click                += styleMenuItem_Click;

            this.dashMenuItem.Click                 += styleMenuItem_Click;

            this.dotMenuItem.Click                  += styleMenuItem_Click;

            this.customMenuItem.Click               += styleMenuItem_Click;

            this.scaleComboBox.SelectedIndexChanged += scaleComboBox_SelectedIndexChanged;

            this.canvasPictureBox.Paint             += canvasPictureBox_Paint;

            this.canvasPictureBox.MouseDown         += canvasPictureBox_MouseDown;

            this.canvasPictureBox.MouseMove         += canvasPictureBox_MouseMove;

            this.canvasPictureBox.MouseUp           += canvasPictureBox_MouseUp;

        }

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method

        ////////////////////////////////////////////////////////////////////////////////////////// Private

        //////////////////////////////////////////////////////////////////////////////// Event

 

        #region 폼 로드시 처리하기 - Form_Load(sender, e)

 

        /// <summary>

        /// 폼 로드시 처리하기

        /// </summary>

        /// <param name="sender">이벤트 발생자</param>

        /// <param name="e">이벤트 인자</param>

        private void Form_Load(object sender, EventArgs e)

        {

            this.scaleComboBox.SelectedIndex = 2;

 

            if(File.Exists(this.autoSaveFilePath))

            {

                DialogResult result = MessageBox.Show

                (

                    "An auto-save file exists. Do you want to load it?",

                    "Restore?",

                    MessageBoxButtons.YesNo,

                    MessageBoxIcon.Question

                );

 

                if(result == DialogResult.Yes)

                {

                    LoadFile(this.autoSaveFilePath);

                }

            }

 

            CreateTestData();

 

            this.canvasPictureBox.Refresh();

        }

 

        #endregion

        #region 폼을 닫을 경우 처리하기 - Form_FormClosing(sender, e)

 

        /// <summary>

        /// 폼을 닫을 경우 처리하기

        /// </summary>

        /// <param name="sender">이벤트 발생자</param>

        /// <param name="e">이벤트 인자</param>

        private void Form_FormClosing(object sender, FormClosingEventArgs e)

        {

            if(File.Exists(this.autoSaveFilePath))

            {

                File.Delete(this.autoSaveFilePath);

            }

        }

 

        #endregion

        #region New 메뉴 항목 클릭시 처리하기 - newMenuitem_Click(sender, e)

 

        /// <summary>

        /// New 메뉴 항목 클릭시 처리하기

        /// </summary>

        /// <param name="sender">이벤트 발생자</param>

        /// <param name="e">이벤트 인자</param>

        private void newMenuitem_Click(object sender, EventArgs e)

        {

            this.polylineList = new List<Polyline>();

 

            this.canvasPictureBox.Refresh();

 

            this.undoStack = new Stack<string>();

            this.redoStack = new Stack<string>();

        }

 

        #endregion

        #region Open 메뉴 항목 클릭시 처리하기 - openMenuItem_Click(sender, e)

 

        /// <summary>

        /// Open 메뉴 항목 클릭시 처리하기

        /// </summary>

        /// <param name="sender">이벤트 발생자</param>

        /// <param name="e">이벤트 인자</param>

        private void openMenuItem_Click(object sender, EventArgs e)

        {

            if(this.openFileDialog.ShowDialog() == DialogResult.OK)

            {

                LoadFile(this.openFileDialog.FileName);

            }

        }

 

        #endregion

        #region Save As 메뉴 항목 클릭시 처리하기 - saveAsMenuItem_Click(sender, e)

 

        /// <summary>

        /// Save As 메뉴 항목 클릭시 처리하기

        /// </summary>

        /// <param name="sender">이벤트 발생자</param>

        /// <param name="e">이벤트 인자</param>

        private void saveAsMenuItem_Click(object sender, EventArgs e)

        {

            if(this.saveFileDialog.ShowDialog() == DialogResult.OK)

            {

                SaveFile(this.saveFileDialog.FileName);

            }

        }

 

        #endregion

        #region Exit 메뉴 항목 클릭시 처리하기 - exitMenuItem_Click(sender, e)

 

        /// <summary>

        /// Exit 메뉴 항목 클릭시 처리하기

        /// </summary>

        /// <param name="sender">이벤트 발생자</param>

        /// <param name="e">이벤트 인자</param>

        private void exitMenuItem_Click(object sender, EventArgs e)

        {

            Close();

        }

 

        #endregion

        #region Undo 메뉴 항목 클릭시 처리하기 - undoMenuitem_Click(sender, e)

 

        /// <summary>

        /// Undo 메뉴 항목 클릭시 처리하기

        /// </summary>

        /// <param name="sender">이벤트 발생자</param>

        /// <param name="e">이벤트 인자</param>

        private void undoMenuitem_Click(object sender, EventArgs e)

        {

            this.redoStack.Push(this.undoStack.Pop());

 

            RestoreTopUndoItem();

 

            EnableUndo();

        }

 

        #endregion

        #region REDO 메뉴 항목 클릭시 처리하기 - redoMenuItem_Click(sender, e)

 

        /// <summary>

        /// REDO 메뉴 항목 클릭시 처리하기

        /// </summary>

        /// <param name="sender">이벤트 발생자</param>

        /// <param name="e">이벤트 인자</param>

        private void redoMenuItem_Click(object sender, EventArgs e)

        {

            this.undoStack.Push(this.redoStack.Pop());

 

            RestoreTopUndoItem();

 

            EnableUndo();

        }

 

        #endregion

        #region 색상 메뉴 항목 클릭시 처리하기 - colorMenuItem_Click(sender, e)

 

        /// <summary>

        /// 색상 메뉴 항목 클릭시 처리하기

        /// </summary>

        /// <param name="sender">이벤트 발생자</param>

        /// <param name="e">이벤트 인자</param>

        private void colorMenuItem_Click(object sender, EventArgs e)

        {

            ToolStripMenuItem menuItem = sender as ToolStripMenuItem;

 

            this.colorButton.Image = menuItem.Image;

 

            this.drawingColor = menuItem.ForeColor;

        }

 

        #endregion

        #region 두께 메뉴 항목 클릭시 처리하기 - thicknessMenuItem_Click(sender, e)

 

        /// <summary>

        /// 두께 메뉴 항목 클릭시 처리하기

        /// </summary>

        /// <param name="sender">이벤트 발생자</param>

        /// <param name="e">이벤트 인자</param>

        private void thicknessMenuItem_Click(object sender, EventArgs e)

        {

            ToolStripMenuItem menuItem = sender as ToolStripMenuItem;

 

            this.thicknessButton.Image = menuItem.Image;

 

            this.drawingThickness = int.Parse(menuItem.Text);

        }

 

        #endregion

        #region 스타일 메뉴 항목 클릭시 처리하기 - styleMenuItem_Click(sender, e)

 

        /// <summary>

        /// 스타일 메뉴 항목 클릭시 처리하기

        /// </summary>

        /// <param name="sender">이벤트 발생자</param>

        /// <param name="e">이벤트 인자</param>

        private void styleMenuItem_Click(object sender, EventArgs e)

        {

            ToolStripMenuItem menuItem = sender as ToolStripMenuItem;

 

            this.styleButton.Image = menuItem.Image;

 

            switch(menuItem.Text)

            {

                case "Solid"  : this.drawingDashStyle = DashStyle.Solid;  break;

                case "Dash"   : this.drawingDashStyle = DashStyle.Dash;   break;

                case "Dot"    : this.drawingDashStyle = DashStyle.Dot;    break;

                case "Custom" : this.drawingDashStyle = DashStyle.Custom; break;

            }

        }

 

        #endregion

        #region 스케일 콤보 박스 선택 인덱스 변경시 처리하기 - scaleComboBox_SelectedIndexChanged(sender, e)

 

        /// <summary>

        /// 스케일 콤보 박스 선택 인덱스 변경시 처리하기

        /// </summary>

        /// <param name="sender">이벤트 발생자</param>

        /// <param name="e">이벤트 인자</param>

        private void scaleComboBox_SelectedIndexChanged(object sender, EventArgs e)

        {

            switch(this.scaleComboBox.Text)

            {

                case "x 1/4" : SetScale(0.25f); break;

                case "x 1/2" : SetScale(0.5f);  break;

                case "x 1"   : SetScale(1.0f);  break;

                case "x 2"   : SetScale(2.0f);  break;

                case "x 4"   : SetScale(4.0f);  break;

                case "x 8"   : SetScale(8.0f);  break;

            }

        }

 

        #endregion

        #region 캔버스 픽처 박스 페인트시 처리하기 - canvasPictureBox_Paint(sender, e)

 

        /// <summary>

        /// 캔버스 픽처 박스 페인트시 처리하기

        /// </summary>

        /// <param name="sender">이벤트 발생자</param>

        /// <param name="e">이벤트 인자</param>

        private void canvasPictureBox_Paint(object sender, PaintEventArgs e)

        {

            e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;

 

            e.Graphics.Clear(Color.White);

 

            e.Graphics.ScaleTransform(this.pictureScale, this.pictureScale);

 

            foreach(Polyline polyline in this.polylineList)

            {

                polyline.Draw(e.Graphics);

            }

        }

 

        #endregion

 

 

        #region 캔버스 픽처 박스 마우스 DOWN 처리하기 - canvasPictureBox_MouseDown(sender, e)

 

        /// <summary>

        /// 캔버스 픽처 박스 마우스 DOWN 처리하기

        /// </summary>

        /// <param name="sender">이벤트 발생자</param>

        /// <param name="e">이벤트 인자</param>

        private void canvasPictureBox_MouseDown(object sender, MouseEventArgs e)

        {

            this.newPolyline = new Polyline();

 

            this.newPolyline.Color     = this.drawingColor;

            this.newPolyline.Thickness = this.drawingThickness;

            this.newPolyline.DashStyle = this.drawingDashStyle;

 

            this.polylineList.Add(this.newPolyline);

 

            Point[] pointArray = { e.Location };

 

            this.inverseTransform.TransformPoints(pointArray);

 

            this.newPolyline.PointList.Add(pointArray[0]);

        }

 

        #endregion

        #region 캔버스 픽처 박스 마우스 이동시 처리하기 - canvasPictureBox_MouseMove(sender, e)

 

        /// <summary>

        /// 캔버스 픽처 박스 마우스 이동시 처리하기

        /// </summary>

        /// <param name="sender">이벤트 발생자</param>

        /// <param name="e">이벤트 인자</param>

        private void canvasPictureBox_MouseMove(object sender, MouseEventArgs e)

        {

            if(this.newPolyline == null)

            {

                return;

            }

 

            Point[] pointArray = { e.Location };

 

            this.inverseTransform.TransformPoints(pointArray);

 

            this.newPolyline.PointList.Add(pointArray[0]);

 

            this.canvasPictureBox.Refresh();

        }

 

        #endregion

        #region 캔버스 픽처 박스 마우스 UP 처리하기 - canvasPictureBox_MouseUp(sender, e)

 

        /// <summary>

        /// 캔버스 픽처 박스 마우스 UP 처리하기

        /// </summary>

        /// <param name="sender">이벤트 발생자</param>

        /// <param name="e">이벤트 인자</param>

        private void canvasPictureBox_MouseUp(object sender, MouseEventArgs e)

        {

            if(this.newPolyline == null)

            {

                return;

            }

 

            if(this.newPolyline.PointList.Count < 2)

            {

                this.polylineList.RemoveAt(this.polylineList.Count - 1);

            }

 

            this.newPolyline = null;

 

            this.canvasPictureBox.Refresh();

 

            SaveSnapshot(true);

        }

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////// Function

 

        #region 원 그리기 - DrawCircle(centerX, centerY, radius, lineCount)

 

        /// <summary>

        /// 원 그리기

        /// </summary>

        /// <param name="centerX">중심점 X</param>

        /// <param name="centerY">중심점 Y</param>

        /// <param name="radius">반경</param>

        /// <param name="lineCount">라인 카운트</param>

        /// <returns>다각선</returns>

        private Polyline DrawCircle(int centerX, int centerY, int radius, int lineCount)

        {

            Polyline polyline = new Polyline();

 

            float theta      = 0;

            float deltaTheta = (float)(2 * Math.PI / lineCount);

 

            for(int i = 0; i <= lineCount; i++)

            {

                int x = (int)(centerX + radius * Math.Cos(theta));

                int y = (int)(centerY + radius * Math.Sin(theta));

 

                theta += deltaTheta;

 

                polyline.PointList.Add(new Point(x, y));

            }

 

            return polyline;

        }

 

        #endregion

        #region 테스트 데이터 생성하기 - CreateTestData()

 

        /// <summary>

        /// 테스트 데이터 생성하기

        /// </summary>

        private void CreateTestData()

        {

            Color[] colorArray =

            {

                Color.Black,

                Color.Red,

                Color.Green,

                Color.Blue,

                Color.Lime,

                Color.Cyan,

                Color.Orange,

                Color.Yellow

            };

 

            DashStyle[] dashStyleArray =

            {

                DashStyle.Solid,

                DashStyle.Solid,

                DashStyle.Solid,

                DashStyle.Dash,

                DashStyle.DashDot

            };

 

            Random random = new Random();

 

            for(int i = 0; i < 10; i++)

            {

                int centerX = random.Next(30, WORLD_WIDTH  - 30);

                int centerY = random.Next(30, WORLD_HEIGHT - 30);

                int radius  = random.Next(10, 100              );

 

                Polyline polyline = DrawCircle(centerX, centerY, radius, 100);

 

                polyline.Color     = colorArray[random.Next(0, colorArray.Length)];

                polyline.DashStyle = dashStyleArray[random.Next(0, dashStyleArray.Length)];

                polyline.Thickness = random.Next(1, 6);

 

                this.polylineList.Add(polyline);

            }

        }

 

        #endregion

        #region 파일 로드하기 - LoadFile(filePath)

 

        /// <summary>

        /// 파일 로드하기

        /// </summary>

        /// <param name="filePath">파일 경로</param>

        private void LoadFile(string filePath)

        {

            try

            {

                XmlSerializer serializer = new XmlSerializer(this.polylineList.GetType());

 

                using(FileStream stream = new FileStream(filePath, FileMode.Open))

                {

                    List<Polyline> newPolylineList = (List<Polyline>)serializer.Deserialize(stream);

 

                    this.polylineList = newPolylineList;

 

                    this.canvasPictureBox.Refresh();

 

                    this.undoStack = new Stack<string>();

                    this.redoStack = new Stack<string>();

 

                    SaveSnapshot(false);

                }

            }

            catch(Exception exception)

            {

                MessageBox.Show(exception.Message);

            }

        }

 

        #endregion

        #region 파일 저장하기 - SaveFile(filePath)

 

        /// <summary>

        /// 파일 저장하기

        /// </summary>

        /// <param name="filePath">파일 경로</param>

        private void SaveFile(string filePath)

        {

            XmlSerializer serializer = new XmlSerializer(this.polylineList.GetType());

 

            using(StreamWriter writer = new StreamWriter(filePath))

            {

                serializer.Serialize(writer, polylineList);

 

                writer.Close();

            }

        }

 

        #endregion

        #region 자동 저장하기 - AutoSave()

 

        /// <summary>

        /// 자동 저장하기

        /// </summary>

        private void AutoSave()

        {

            SaveFile(this.autoSaveFilePath);

        }

 

        #endregion

        #region 최상휘 UNDO 항목 복구하기 - RestoreTopUndoItem()

 

        /// <summary>

        /// 최상휘 UNDO 항목 복구하기

        /// </summary>

        private void RestoreTopUndoItem()

        {

            if(this.undoStack.Count == 0)

            {

                this.polylineList = new List<Polyline>();

            }

            else

            {

                XmlSerializer serializer = new XmlSerializer(this.polylineList.GetType());

 

                using(StringReader reader = new StringReader(this.undoStack.Peek()))

                {

                    List<Polyline> newPolylineList = (List<Polyline>)serializer.Deserialize(reader);

 

                    this.polylineList = newPolylineList;

                }

            }

 

            this.canvasPictureBox.Refresh();

 

            AutoSave();

        }

 

        #endregion

        #region UNDO 이용 가능하게 하기 - EnableUndo()

 

        /// <summary>

        /// UNDO 이용 가능하게 하기

        /// </summary>

        private void EnableUndo()

        {

            this.undoMenuitem.Enabled = (this.undoStack.Count > 0);

            this.redoMenuItem.Enabled = (this.redoStack.Count > 0);

        }

 

        #endregion

        #region 스케일 설정하기 - SetScale(scale)

 

        /// <summary>

        /// 스케일 설정하기

        /// </summary>

        /// <param name="scale">스케일</param>

        private void SetScale(float scale)

        {

            this.pictureScale = scale;

 

            this.canvasPictureBox.ClientSize = new Size

            (

                (int)(WORLD_WIDTH  * this.pictureScale),

                (int)(WORLD_HEIGHT * this.pictureScale)

            );

 

            this.inverseTransform = new Matrix();

 

            this.inverseTransform.Scale(1.0f / scale, 1.0f / scale);

 

            this.canvasPictureBox.Refresh();

        }

 

        #endregion

        #region 스냅샷 구하기 - GetSnapshot()

 

        /// <summary>

        /// 스냅샷 구하기

        /// </summary>

        /// <returns>스냅샷</returns>

        private string GetSnapshot()

        {

            XmlSerializer serializer = new XmlSerializer(this.polylineList.GetType());

 

            using(StringWriter writer = new StringWriter())

            {

                serializer.Serialize(writer, this.polylineList);

 

                return writer.ToString();

            }

        }

 

        #endregion

        #region 스냅샷 저장하기 - SaveSnapshot(autoSave)

 

        /// <summary>

        /// 스냅샷 저장하기

        /// </summary>

        /// <param name="autoSave">자동 저장 여부</param>

        private void SaveSnapshot(bool autoSave)

        {

            this.undoStack.Push(GetSnapshot());

 

            if(this.redoStack.Count > 0)

            {

                this.redoStack = new Stack<string>();

            }

 

            EnableUndo();

 

            if(autoSave)

            {

                AutoSave();

            }

        }

 

        #endregion

    }

}

 

------------------------------------------------------------------------------------------------------------------------

Posted by 사용자 icodebroker

댓글을 달아 주세요