728x90
반응형
728x170
▶ MainWindow.xaml
<Window x:Class="TestProject.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="800"
Height="600"
Title="Canvas 클래스 : 사용자 선 그리기"
Loaded="Window_Loaded">
<DockPanel>
<Menu DockPanel.Dock="Top"
FontFamily="나눔고딕코딩"
FontSize="16">
<MenuItem Header="_File">
<MenuItem Header="_Open" Click="openMenuItem_Click" />
<MenuItem Header="_New" Click="newMenuItem_Click" />
<MenuItem Header="_Save" Click="saveMenuItem_Click" />
</MenuItem>
</Menu>
<Grid>
<Canvas Name="canvas">
<Image Name="trashImage" Canvas.Left="0" Canvas.Top="0"
Stretch="Uniform"
Width="32"
Height="32"
Source="IMAGE/trash_empty.png" />
</Canvas>
</Grid>
</DockPanel>
</Window>
728x90
▶ MainWindow.xaml.cs
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.IO;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Xml.Serialization;
namespace TestProject
{
/// <summary>
/// 메인 윈도우
/// </summary>
public partial class MainWindow : Window
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Field
////////////////////////////////////////////////////////////////////////////////////////// Private
#region Field
/// <summary>
/// 객체 반경
/// </summary>
private const int OBJECT_RADIUS = 3;
/// <summary>
/// 거리 제곱
/// </summary>
private const int DISTANCE_SQUARED = OBJECT_RADIUS * OBJECT_RADIUS;
/// <summary>
/// 선택한 라인
/// </summary>
private Line selectedLine;
/// <summary>
/// 시작/종료 점 이동 여부
/// </summary>
private bool movingStartEndPoint = false;
/// <summary>
/// 오프셋 X
/// </summary>
private double offsetX;
/// <summary>
/// 오프셋 Y
/// </summary>
private double offsetY;
/// <summary>
/// 쓰레기통 너비
/// </summary>
private double trashWidth;
/// <summary>
/// 쓰레기통 높이
/// </summary>
private double trashHeight;
/// <summary>
/// 이동 상태
/// </summary>
private MoveStatus moveStatus = MoveStatus.NotDown;
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 생성자 - MainWindow()
/// <summary>
/// 생성자
/// </summary>
public MainWindow()
{
InitializeComponent();
this.canvas.MouseDown += canvas_MouseDown;
this.canvas.MouseMove += canvas_MouseMove;
this.canvas.MouseUp += canvas_MouseUp;
}
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Method
////////////////////////////////////////////////////////////////////////////////////////// Private
//////////////////////////////////////////////////////////////////////////////// Event
#region 윈도우 로드시 처리하기 - Window_Loaded(sender, e)
/// <summary>
/// 윈도우 로드시 처리하기
/// </summary>
/// <param name="sender">이벤트 발생자</param>
/// <param name="e">이벤트 인자</param>
private void Window_Loaded(object sender, RoutedEventArgs e)
{
this.trashWidth = this.trashImage.ActualWidth;
this.trashHeight = this.trashImage.ActualHeight;
this.canvas.Background = Brushes.White;
}
#endregion
#region Open 메뉴 항목 클릭시 처리하기 - openMenuItem_Click(sender, e)
/// <summary>
/// Open 메뉴 항목 클릭시 처리하기
/// </summary>
/// <param name="sender">이벤트 발생자</param>
/// <param name="e">이벤트 인자</param>
private void openMenuItem_Click(object sender, RoutedEventArgs e)
{
OpenFileDialog dialog = new OpenFileDialog();
dialog.DefaultExt = ".xml";
dialog.Filter = "XML (*.xml)|*.xml|All Files (*.*)|*.*";
Nullable<bool> result = dialog.ShowDialog();
if(result.Value != true)
{
return;
}
ClearCanvas();
XmlSerializer serializer = new XmlSerializer(typeof(List<Segment>));
using(FileStream stream = File.Open(dialog.FileName, FileMode.Open))
{
List<Segment> segmentList = (List<Segment>)serializer.Deserialize(stream);
foreach(Segment segment in segmentList)
{
Line line = new Line();
line.X1 = segment.X1;
line.Y1 = segment.Y1;
line.X2 = segment.X2;
line.Y2 = segment.Y2;
line.Stroke = Brushes.Black;
this.canvas.Children.Add(line);
}
}
}
#endregion
#region New 메뉴 항목 클릭시 처리하기 - newMenuItem_Click(sender, e)
/// <summary>
/// New 메뉴 항목 클릭시 처리하기
/// </summary>
/// <param name="sender">이벤트 발생자</param>
/// <param name="e">이벤트 인자</param>
private void newMenuItem_Click(object sender, RoutedEventArgs e)
{
ClearCanvas();
}
#endregion
#region Save 메뉴 항목 클릭시 처리하기 - saveMenuItem_Click(sender, e)
/// <summary>
/// Save 메뉴 항목 클릭시 처리하기
/// </summary>
/// <param name="sender">이벤트 발생자</param>
/// <param name="e">이벤트 인자</param>
private void saveMenuItem_Click(object sender, RoutedEventArgs e)
{
SaveFileDialog dialog = new SaveFileDialog();
dialog.DefaultExt = ".xml";
dialog.Filter = "XML (*.xml)|*.xml|All Files (*.*)|*.*";
Nullable<bool> result = dialog.ShowDialog();
if(result.Value != true)
{
return;
}
List<Segment> segmentList = new List<Segment>();
foreach(object child in canvas.Children)
{
if(child is Line)
{
Line line = (Line)child;
segmentList.Add(new Segment(line.X1, line.Y1, line.X2, line.Y2));
}
}
XmlSerializer serializer = new XmlSerializer(typeof(List<Segment>));
using(FileStream stream = File.Create(dialog.FileName))
{
serializer.Serialize(stream, segmentList);
}
}
#endregion
#region 캔버스 마우스 DOWN 처리하기 - canvas_MouseDown(sender, e)
/// <summary>
/// 캔버스 마우스 DOWN 처리하기
/// </summary>
/// <param name="sender">이벤트 발생자</param>
/// <param name="e">이벤트 인자</param>
private void canvas_MouseDown(object sender, MouseButtonEventArgs e)
{
Point mousePoint = e.MouseDevice.GetPosition(this.canvas);
if(IsMouseOverEndpoint(mousePoint, out this.selectedLine, out this.movingStartEndPoint))
{
this.moveStatus = MoveStatus.MovingEndPoint;
Point hitLinePoint;
if(this.movingStartEndPoint)
{
hitLinePoint = new Point(this.selectedLine.X1, this.selectedLine.Y1);
}
else
{
hitLinePoint = new Point(this.selectedLine.X2, this.selectedLine.Y2);
}
this.offsetX = hitLinePoint.X - mousePoint.X;
this.offsetY = hitLinePoint.Y - mousePoint.Y;
}
else if(IsMouseOverLine(mousePoint, out this.selectedLine))
{
this.moveStatus = MoveStatus.MovingSegment;
this.offsetX = this.selectedLine.X1 - mousePoint.X;
this.offsetY = this.selectedLine.Y1 - mousePoint.Y;
}
else
{
this.moveStatus = MoveStatus.Drawing;
this.selectedLine = new Line();
this.selectedLine.Stroke = Brushes.Red;
this.selectedLine.X1 = mousePoint.X;
this.selectedLine.Y1 = mousePoint.Y;
this.selectedLine.X2 = mousePoint.X;
this.selectedLine.Y2 = mousePoint.Y;
this.canvas.Children.Add(this.selectedLine);
}
}
#endregion
#region 캔버스 마우스 이동시 처리하기 - canvas_MouseMove(sender, e)
/// <summary>
/// 캔버스 마우스 이동시 처리하기
/// </summary>
/// <param name="sender">이벤트 발생자</param>
/// <param name="e">이벤트 인자</param>
private void canvas_MouseMove(object sender, MouseEventArgs e)
{
Point mousePoint = e.MouseDevice.GetPosition(this.canvas);
if(this.moveStatus == MoveStatus.NotDown)
{
Cursor cursor = Cursors.Cross;
if(IsMouseOverEndpoint(mousePoint, out this.selectedLine, out this.movingStartEndPoint))
{
cursor = Cursors.Arrow;
}
else if(IsMouseOverLine(mousePoint, out this.selectedLine))
{
cursor = Cursors.Hand;
}
if(this.canvas.Cursor != cursor)
{
this.canvas.Cursor = cursor;
}
}
else if(this.moveStatus == MoveStatus.MovingEndPoint)
{
if(this.movingStartEndPoint)
{
this.selectedLine.X1 = mousePoint.X + this.offsetX;
this.selectedLine.Y1 = mousePoint.Y + this.offsetY;
}
else
{
this.selectedLine.X2 = mousePoint.X + this.offsetX;
this.selectedLine.Y2 = mousePoint.Y + this.offsetY;
}
}
else if(this.moveStatus == MoveStatus.Drawing)
{
this.selectedLine.X2 = mousePoint.X;
this.selectedLine.Y2 = mousePoint.Y;
}
else if(this.moveStatus == MoveStatus.MovingSegment)
{
double newX1 = mousePoint.X + this.offsetX;
double newY1 = mousePoint.Y + this.offsetY;
double deltaX = newX1 - this.selectedLine.X1;
double deltaY = newY1 - this.selectedLine.Y1;
this.selectedLine.X1 = newX1;
this.selectedLine.Y1 = newY1;
this.selectedLine.X2 += deltaX;
this.selectedLine.Y2 += deltaY;
}
}
#endregion
#region 캔버스 마우스 UP 처리하기 - canvas_MouseUp(sender, e)
/// <summary>
/// 캔버스 마우스 UP 처리하기
/// </summary>
/// <param name="sender">이벤트 발생자</param>
/// <param name="e">이벤트 인자</param>
private void canvas_MouseUp(object sender, MouseEventArgs e)
{
if(this.moveStatus == MoveStatus.Drawing)
{
this.selectedLine.Stroke = Brushes.Black;
if((this.selectedLine.X1 == this.selectedLine.X2) && (this.selectedLine.Y1 == this.selectedLine.Y2))
{
this.canvas.Children.Remove(this.selectedLine);
}
}
else if(this.moveStatus == MoveStatus.MovingSegment)
{
Point mousePoint = e.MouseDevice.GetPosition(canvas);
if((mousePoint.X >= 0) && (mousePoint.X < this.trashWidth) && (mousePoint.Y >= 0) && (mousePoint.Y < this.trashHeight))
{
if(MessageBox.Show("Delete this segment?", "Delete Segment?", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
this.canvas.Children.Remove(this.selectedLine);
}
}
}
this.moveStatus = MoveStatus.NotDown;
}
#endregion
//////////////////////////////////////////////////////////////////////////////// Function
#region 캔버스 지우기 - ClearCanvas()
/// <summary>
/// 캔버스 지우기
/// </summary>
private void ClearCanvas()
{
for(int i = this.canvas.Children.Count - 1; i >= 0; i--)
{
if(this.canvas.Children[i] is Line)
{
this.canvas.Children.RemoveAt(i);
}
}
}
#endregion
#region 특정 포인트까지 제곱 거리 찾기 - FindDistanceSquaredToPoint(point1, point2)
/// <summary>
/// 특정 포인트까지 제곱 거리 찾기
/// </summary>
/// <param name="point1">포인트 1</param>
/// <param name="point2">포인트 2</param>
/// <returns>특정 포인트까지 제곱 거리</returns>
private double FindDistanceSquaredToPoint(Point point1, Point point2)
{
double dx = point1.X - point2.X;
double dy = point1.Y - point2.Y;
return dx * dx + dy * dy;
}
#endregion
#region 특정 세그먼트까지 제곱 거리 찾기 - FindDistanceSquaredToSegment(point, point1, point2, closestPoint)
/// <summary>
/// 특정 세그먼트까지 제곱 거리 찾기
/// </summary>
/// <param name="point">포인트</param>
/// <param name="startPoint">시작 포인트</param>
/// <param name="endPoint">종료 포인트</param>
/// <param name="closestPoint">최근접 포인트</param>
/// <returns>특정 세그먼트까지 제곱 거리</returns>
private double FindDistanceSquaredToSegment(Point point, Point startPoint, Point endPoint, out Point closestPoint)
{
double deltaX = endPoint.X - startPoint.X;
double deltaY = endPoint.Y - startPoint.Y;
if((deltaX == 0) && (deltaY == 0))
{
closestPoint = startPoint;
deltaX = point.X - startPoint.X;
deltaY = point.Y - startPoint.Y;
return deltaX * deltaX + deltaY * deltaY;
}
double temporary = ((point.X - startPoint.X) * deltaX + (point.Y - startPoint.Y) * deltaY) / (deltaX * deltaX + deltaY * deltaY);
if(temporary < 0)
{
closestPoint = new Point(startPoint.X, startPoint.Y);
deltaX = point.X - startPoint.X;
deltaY = point.Y - startPoint.Y;
}
else if (temporary > 1)
{
closestPoint = new Point(endPoint.X, endPoint.Y);
deltaX = point.X - endPoint.X;
deltaY = point.Y - endPoint.Y;
}
else
{
closestPoint = new Point(startPoint.X + temporary * deltaX, startPoint.Y + temporary * deltaY);
deltaX = point.X - closestPoint.X;
deltaY = point.Y - closestPoint.Y;
}
return deltaX * deltaX + deltaY * deltaY;
}
#endregion
#region 마우스 종점 상위 여부 구하기 - IsMouseOverEndpoint(mousePoint, hitLine, isStartPoint)
/// <summary>
/// 마우스 종점 상위 여부 구하기
/// </summary>
/// <param name="mousePoint">마우스 포인트</param>
/// <param name="hitLine">히트 라인</param>
/// <param name="isStartPoint">시작점 여부</param>
/// <returns>마우스 종점 상위 여부</returns>
private bool IsMouseOverEndpoint(Point mousePoint, out Line hitLine, out bool isStartPoint)
{
foreach(object child in canvas.Children)
{
if(child is Line)
{
Line line = child as Line;
Point point = new Point(line.X1, line.Y1);
if(FindDistanceSquaredToPoint(mousePoint, point) < DISTANCE_SQUARED)
{
hitLine = line;
isStartPoint = true;
return true;
}
point = new Point(line.X2, line.Y2);
if(FindDistanceSquaredToPoint(mousePoint, point) < DISTANCE_SQUARED)
{
hitLine = line;
isStartPoint = false;
return true;
}
}
}
hitLine = null;
isStartPoint = false;
return false;
}
#endregion
#region 마우스 라인 상위 여부 구하기 - IsMouseOverLine(mousePoint, hitLine)
/// <summary>
/// 마우스 라인 상위 여부 구하기
/// </summary>
/// <param name="mousePoint">마우스 포인트</param>
/// <param name="hitLine">히트 라인</param>
/// <returns>마우스 라인 상위 여부</returns>
private bool IsMouseOverLine(Point mousePoint, out Line hitLine)
{
foreach(object child in canvas.Children)
{
if(child is Line)
{
Line line = child as Line;
Point lineClosestPoint;
Point lineStartPoint = new Point(line.X1, line.Y1);
Point lineEndPoint = new Point(line.X2, line.Y2);
if(FindDistanceSquaredToSegment(mousePoint, lineStartPoint, lineEndPoint, out lineClosestPoint) < DISTANCE_SQUARED)
{
hitLine = line;
return true;
}
}
}
hitLine = null;
return false;
}
#endregion
}
}
728x90
반응형
그리드형(광고전용)
'C# > WPF' 카테고리의 다른 글
[C#/WPF] Polygon 엘리먼트 : FillRule 속성 사용하기 (0) | 2020.07.06 |
---|---|
[C#/WPF] Viewbox 엘리먼트 : 얼굴 그리기 (0) | 2020.07.06 |
[C#/WPF] Shape 엘리먼트 : 얼굴 그리기 (0) | 2020.07.06 |
[C#/WPF] Polyline 클래스 : 아르키메데스 나선(Archimedes Spiral) 그리기 (0) | 2020.07.06 |
[C#/WPF] Polyline 클래스 : 싸인 곡선 그리기 (0) | 2020.07.06 |
[C#/WPF] WindowInteropHelper 클래스 : Handle 속성을 사용해 윈도우 핸들 구하기 (0) | 2020.05.20 |
[C#/WPF] Application 클래스 : Shutdown 메소드를 사용해 애플리케이션 종료하기 (0) | 2020.02.29 |
[C#/WPF] MeshGeometry3D 클래스 : 구체 애니메이션 만들기 (0) | 2019.09.08 |
[C#/WPF] NAUDIO 라이브러리를 사용해 음악 재생하기 (0) | 2019.09.08 |
[C#/WPF] MeshGeometry3D 클래스 : 수레바퀴 만들기 (0) | 2019.09.02 |
댓글을 달아 주세요