728x90
반응형
728x170
■ PictureBox 클래스를 사용해 사용자 그리기 및 확대/축소하는 방법을 보여준다.
▶ 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
반응형
그리드형(광고전용)
'C# > WinForm' 카테고리의 다른 글
[C#/WINFORM] 안티-알리아싱으로 그리는 경우 투명도 사용하기 (0) | 2020.07.11 |
---|---|
[C#/WINFORM] 이미지 나선 그리기 (0) | 2020.07.10 |
[C#/WINFORM] Graphics 클래스 : DrawCurve 메소드를 사용해 곡선 그리기 (0) | 2020.07.09 |
[C#/WINFORM] 사인(Sine)과 코사인(Cosine)을 사용해 원과 타원 그리기 (0) | 2020.07.09 |
[C#/WINFORM] Bitmap 클래스 : 투명한 구멍을 갖는 비트맵 만들기 (0) | 2020.07.05 |
[C#/WINFORM] PictureBox 클래스 : 사용자 선 그리기 (0) | 2020.07.05 |
[C#/WINFORM] CommonOpenFileDialog 클래스 : 표준 윈도우즈 대화 상자를 사용해 폴더 선택하기 (0) | 2020.07.05 |
[C#/WINFORM] ListView 클래스 : 항목 인덱스 구하기 (0) | 2020.06.08 |
[C#/WINFORM] ListView 클래스 : 선택 항목 리스트 구하기 (0) | 2020.06.08 |
[C#/WINFORM] Control 클래스 : 컨트롤 비트맵 구하기 (0) | 2020.06.05 |
댓글을 달아 주세요