첨부 실행 코드는 나눔고딕코딩 폰트를 사용합니다.
728x90
반응형
728x170

■ 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
    }
}
728x90
반응형
그리드형(광고전용)
Posted by icodebroker

댓글을 달아 주세요